From 861970c436f0c1b1ac8fb71e6b8689e2e4b2660c Mon Sep 17 00:00:00 2001 From: voju Date: Fri, 21 Jun 2024 12:29:08 +0000 Subject: [PATCH 01/25] Translated using Weblate (Japanese) Currently translated at 68.8% (170 of 247 strings) Translation: PeerTube LiveChat/Peertube Plugin LiveChat Translate-URL: https://weblate.framasoft.org/projects/peertube-livechat/peertube-plugin-livechat/ja/ --- languages/ja.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/languages/ja.yml b/languages/ja.yml index 86e427fb..8a796c03 100644 --- a/languages/ja.yml +++ b/languages/ja.yml @@ -229,3 +229,4 @@ external_auth_custom_oidc_description: "チャットへのログインに、外 次のドキュメントを参照してください:\n設定ページ\n" external_auth_custom_oidc_button_label_description: このラベルは、OIDCプロバイダーを使用して認証するボタン名としてユーザーに表示されます。 +copied: コピーしました From 6fbb9a99bb62a0a4f483f100b0e21e7e504edadc Mon Sep 17 00:00:00 2001 From: Victor Hampel Date: Sat, 22 Jun 2024 08:26:34 +0000 Subject: [PATCH 02/25] Translated using Weblate (German) Currently translated at 100.0% (247 of 247 strings) Translation: PeerTube LiveChat/Peertube Plugin LiveChat Translate-URL: https://weblate.framasoft.org/projects/peertube-livechat/peertube-plugin-livechat/de/ --- languages/de.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/languages/de.yml b/languages/de.yml index 934b0136..77a905fe 100644 --- a/languages/de.yml +++ b/languages/de.yml @@ -512,3 +512,10 @@ auth_description: "

Authentifizierung

\n" livechat_token_disabled_label: Livechat-Token deaktivieren share_chat_dock: Dock token_date: Datum +too_many_entries: Zu viele Einträge +livechat_configuration_channel_mute_anonymous_label: Anonyme Benutzer stummschalten +muted_anonymous_message: Nur registrierte Benutzer können Nachrichten versenden. +livechat_configuration_channel_mute_anonymous_desc: "Standardwert für neue Chaträume.\n + Für bestehende Chaträume können Sie die Funktion im Raumkonfigurationsformular ändern.\n + Wenn diese Funktion aktiviert ist, können anonyme Benutzer den Chat nur lesen, aber + keine Nachrichten senden.\n" From a0430fdaa12026fb4005cc9f6aabf956f65e67ba Mon Sep 17 00:00:00 2001 From: Victor Hampel Date: Sat, 22 Jun 2024 08:30:08 +0000 Subject: [PATCH 03/25] Translated using Weblate (German) Currently translated at 100.0% (742 of 742 strings) Translation: PeerTube LiveChat/Peertube Plugin Livechat Documentation Translate-URL: https://weblate.framasoft.org/projects/peertube-livechat/peertube-plugin-livechat-documentation/de/ --- support/documentation/po/livechat.de.po | 78 ++++++++++++++----------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/support/documentation/po/livechat.de.po b/support/documentation/po/livechat.de.po index 2fc531ff..5e275ec2 100644 --- a/support/documentation/po/livechat.de.po +++ b/support/documentation/po/livechat.de.po @@ -8,9 +8,11 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2024-06-21 12:07+0200\n" -"PO-Revision-Date: 2024-06-20 11:44+0000\n" -"Last-Translator: Victor Hampel \n" -"Language-Team: German \n" +"PO-Revision-Date: 2024-06-22 11:54+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" @@ -2593,10 +2595,12 @@ msgstr "Sie können OBS \"Benutzerdefinierte Browser-Docks\" verwenden, um den C #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -#, fuzzy -#| msgid "To do so, just use the \"{{% livechat_label share_chat_link %}}\", and open the \"{{% livechat_label share_chat_dock %}}\" tab. From there, you can create a new token using the \"+\" button." msgid "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." -msgstr "Verwenden Sie dazu einfach \"{{% livechat_label share_chat_link %}}\", und öffnen Sie die Registerkarte \"{{% livechat_label share_chat_dock %}}\". Von dort aus können Sie mit der Schaltfläche \"+\" ein neuen Token erstellen." +msgstr "" +"Verwenden Sie dazu einfach die \"{{% livechat_label share_chat_link %}}\" " +"Funktion, und öffnen Sie die Registerkarte \"{{% livechat_label " +"share_chat_dock %}}\". Von dort aus können Sie mit der Schaltfläche \"+\" " +"ein neuen Token erstellen." #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -3064,10 +3068,11 @@ msgstr "Hier können Sie konfigurieren:" #. type: Bullet: '* ' #: support/documentation/content/en/documentation/user/streamers/channel.md -#, fuzzy -#| msgid "Please refer to the [moderation documentation](/peertube-plugin-livechat/documentation/user/streamers/moderation)." msgid "[{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}](/peertube-plugin-livechat/documentation/user/streamers/moderation) default value" -msgstr "Bitte lesen Sie die [Moderationsdokumentation](/peertube-plugin-livechat/de/documentation/user/streamers/moderation)." +msgstr "" +"[{{% livechat_label livechat_configuration_channel_mute_anonymous_label " +"%}}](/peertube-plugin-livechat/de/documentation/user/streamers/moderation) " +"Standardwert" #. type: Bullet: '* ' #: support/documentation/content/en/documentation/user/streamers/channel.md @@ -3145,14 +3150,7 @@ msgstr "Sie können auch eine Datei generieren, die Sie aus einer anderen Quelle #. type: Fenced code block (json) #: build/documentation/pot_in/documentation/user/streamers/emojis.md -#, fuzzy, no-wrap -#| msgid "" -#| "[\n" -#| " {\n" -#| " \"sn\": \":short_name\",\n" -#| " \"url\": \"https://example.com/image.png\"\n" -#| " }\n" -#| "]\n" +#, no-wrap msgid "" "[\n" " {\n" @@ -3163,7 +3161,7 @@ msgid "" msgstr "" "[\n" " {\n" -" \"sn\": \":short_name\",\n" +" \"sn\": \":short_name:\",\n" " \"url\": \"https://example.com/image.png\"\n" " }\n" "]\n" @@ -3245,58 +3243,72 @@ msgstr "Sie können [ConverseJS Moderationsbefehle](https://conversejs.org/docs/ #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -#, fuzzy -#| msgid "This feature comes with the livechat plugin version 10.1.0." msgid "This feature comes with the livechat plugin version 10.2.0." -msgstr "Diese Funktion wird mit dem Livechatplugin Version 10.1.0 verfügbar sein." +msgstr "" +"Diese Funktion wird mit dem Livechatplugin Version 10.2.0 verfügbar sein." #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md msgid "You can prevent anonymous users to send messages. In such case, only registered users will be able to talk in the chat." msgstr "" +"Sie können anonyme Benutzer daran hindern, Nachrichten zu senden. In diesem " +"Fall können nur registrierte Benutzer im Chat schreiben." #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -#, fuzzy -#| msgid "On the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel), open the \"{{% livechat_label livechat_configuration_channel_emojis_title %}}\" tab:" 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_mute_anonymous_label %}}\" checkbox." -msgstr "Öffnen Sie auf der [Kanal Konfigurationsseite](/peertube-plugin-livechat/de/documentation/user/streamers/channel) die Registerkarte \"{{% livechat_label livechat_configuration_channel_emojis_title %}}\":" +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_mute_anonymous_label %}}\"." #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -#, fuzzy -#| msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" -msgstr "![Chat mit einem anonymen Benutzer](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgstr "" +"![Raumkonfiguration / Anonyme Benutzer stummschalten](/peertube-" +"plugin-livechat/images/configure_mute_anonymous." +"png?classes=shadow,border&height=400px)" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md msgid "Anonymous users won't have the message field, and will see following prompt: \"{{% livechat_label muted_anonymous_message %}}\"" msgstr "" +"Anonyme Benutzer haben das Nachrichtenfeld nicht und sehen folgende " +"Aufforderung: \"{{% livechat_label muted_anonymous_message %}}\"" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -#, fuzzy -#| msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" -msgstr "![Chat mit einem anonymen Benutzer](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgstr "" +"![Raumkonfiguration / Stummgeschaltete anonyme Benutzer](/peertube-" +"plugin-livechat/images/anonymous_muted." +"png?classes=shadow,border&height=400px)" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md msgid "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." msgstr "" +"Wenn diese Funktion aktiviert ist, wird anonymen Benutzern die Rolle " +"\"Besucher\" zugewiesen. Sie können deren Rolle in \"Teilnehmer\" ändern, " +"wenn Sie einigen von ihnen erlauben wollen, zu schreiben." #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md msgid "If you change the room configuration, all anonymous users will be muted or unmuted." msgstr "" +"Wenn Sie die Raumkonfiguration ändern, werden alle anonymen Benutzer " +"stummgeschaltet oder die Stummschaltung aufgehoben." #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -#, fuzzy -#| msgid "To access this page, check the [channel configuration documentation](/peertube-plugin-livechat/documentation/user/streamers/channel)." msgid "You can choose to enable or disable this feature for new chatrooms on the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel)." -msgstr "Um auf diese Seite zuzugreifen, sehen Sie sich die [Kanal Konfigurations Dokumentation](/peertube-plugin-livechat/de/documentation/user/streamers/channel) an." +msgstr "" +"Sie können diese Funktion für neue Chaträume auf der [Kanal-" +"Konfigurationsseite](/peertube-plugin-livechat/de/documentation/user/" +"streamers/channel) aktivieren oder deaktivieren." #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md From 579c1fe8f81a551f1ac24f1d7b6e56dfafed2441 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Sun, 23 Jun 2024 07:48:53 +0000 Subject: [PATCH 04/25] Translated using Weblate (French) Currently translated at 100.0% (742 of 742 strings) Translation: PeerTube LiveChat/Peertube Plugin Livechat Documentation Translate-URL: https://weblate.framasoft.org/projects/peertube-livechat/peertube-plugin-livechat-documentation/fr/ --- support/documentation/po/livechat.fr.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/support/documentation/po/livechat.fr.po b/support/documentation/po/livechat.fr.po index 33baea4e..4eca3d57 100644 --- a/support/documentation/po/livechat.fr.po +++ b/support/documentation/po/livechat.fr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2024-06-21 12:07+0200\n" -"PO-Revision-Date: 2024-06-21 10:14+0000\n" +"PO-Revision-Date: 2024-06-23 08:04+0000\n" "Last-Translator: John Livingston \n" "Language-Team: French \n" @@ -3160,7 +3160,7 @@ msgstr "Vous pouvez également générer un fichier à importer à partir de n'i #. type: Fenced code block (json) #: build/documentation/pot_in/documentation/user/streamers/emojis.md -#, fuzzy, no-wrap +#, no-wrap msgid "" "[\n" " {\n" From 45a63eaecdcdb4d3a6093284adfa6dd3f2316c73 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Fri, 21 Jun 2024 18:18:11 +0200 Subject: [PATCH 05/25] Terms&Conditions (#18): * new settings for instance's terms * new channel option for channel's terms --- CHANGELOG.md | 1 + client/@types/global.d.ts | 3 ++ .../elements/channel-configuration.ts | 12 ++++++++ .../templates/channel-configuration.ts | 30 +++++++++++++++++++ .../configuration/services/channel-details.ts | 5 ++++ .../common/lib/elements/dynamic-table-form.ts | 4 +++ client/common/lib/models/validation.ts | 3 +- languages/en.yml | 9 ++++++ server/lib/configuration/channel/sanitize.ts | 14 +++++++++ server/lib/configuration/channel/storage.ts | 3 +- server/lib/settings.ts | 8 +++++ shared/lib/constants.ts | 1 + shared/lib/types.ts | 3 +- 13 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 shared/lib/constants.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f1ca7e..e7c54f17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### New features * #233: new option to [mute anonymous users](https://livingston.frama.io/peertube-plugin-livechat/fr/documentation/user/streamers/moderation/). +* #18: terms & conditions. You can configure terms&conditions on your instance that will be shown to each joining users. Streamers can also add terms&conditions in their channels options. ## 10.1.2 diff --git a/client/@types/global.d.ts b/client/@types/global.d.ts index d398383c..1398da00 100644 --- a/client/@types/global.d.ts +++ b/client/@types/global.d.ts @@ -83,6 +83,8 @@ declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_BOT_NICKNAME: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FOR_MORE_INFO: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_MUTE_ANONYMOUS_LABEL: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_MUTE_ANONYMOUS_DESC: string +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_TERMS_LABEL: string +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_TERMS_DESC: string declare const LOC_VALIDATION_ERROR: string declare const LOC_TOO_MANY_ENTRIES: string @@ -93,6 +95,7 @@ declare const LOC_INVALID_VALUE_WRONG_FORMAT: string declare const LOC_INVALID_VALUE_NOT_IN_RANGE: string declare const LOC_INVALID_VALUE_FILE_TOO_BIG: string declare const LOC_INVALID_VALUE_DUPLICATE: string +declare const LOC_INVALID_VALUE_TOO_LONG: string declare const LOC_CHATROOM_NOT_ACCESSIBLE: string diff --git a/client/common/configuration/elements/channel-configuration.ts b/client/common/configuration/elements/channel-configuration.ts index a5e5bb14..7e6edd66 100644 --- a/client/common/configuration/elements/channel-configuration.ts +++ b/client/common/configuration/elements/channel-configuration.ts @@ -14,6 +14,7 @@ import { customElement, property, state } from 'lit/decorators.js' import { ptTr } from '../../lib/directives/translation' import { Task } from '@lit/task' import { provide } from '@lit/context' +import { channelTermsMaxLength } from 'shared/lib/constants' @customElement('livechat-channel-configuration') export class ChannelConfigurationElement extends LivechatElement { @@ -51,6 +52,10 @@ export class ChannelConfigurationElement extends LivechatElement { }) } + public termsMaxLength (): number { + return channelTermsMaxLength + } + /** * Resets the form by reloading data from backend. */ @@ -120,7 +125,11 @@ export class ChannelConfigurationElement extends LivechatElement { const validationErrorTypes: ValidationErrorType[] | undefined = this.validationError?.properties[`${propertyName}`] ?? undefined + // FIXME: this code is duplicated in dymamic table form if (validationErrorTypes && validationErrorTypes.length !== 0) { + if (validationErrorTypes.includes(ValidationErrorType.Missing)) { + errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_MISSING)}`) + } if (validationErrorTypes.includes(ValidationErrorType.WrongType)) { errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_WRONG_TYPE)}`) } @@ -130,6 +139,9 @@ export class ChannelConfigurationElement extends LivechatElement { if (validationErrorTypes.includes(ValidationErrorType.NotInRange)) { errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_NOT_IN_RANGE)}`) } + if (validationErrorTypes.includes(ValidationErrorType.TooLong)) { + errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_TOO_LONG)}`) + } return html`
${errorMessages}
` } else { diff --git a/client/common/configuration/elements/templates/channel-configuration.ts b/client/common/configuration/elements/templates/channel-configuration.ts index 9459e0fc..118e05bc 100644 --- a/client/common/configuration/elements/templates/channel-configuration.ts +++ b/client/common/configuration/elements/templates/channel-configuration.ts @@ -128,6 +128,36 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ
+ + +
+ + ${el.renderFeedback('peertube-livechat-terms-feedback', 'terms')} +
+ => { const propertiesError: ValidationError['properties'] = {} + if (channelConfigurationOptions.terms && channelConfigurationOptions.terms.length > channelTermsMaxLength) { + propertiesError.terms = [ValidationErrorType.TooLong] + } + const botConf = channelConfigurationOptions.bot const slowModeDuration = channelConfigurationOptions.slowMode.duration diff --git a/client/common/lib/elements/dynamic-table-form.ts b/client/common/lib/elements/dynamic-table-form.ts index 5170b79d..bc0099e5 100644 --- a/client/common/lib/elements/dynamic-table-form.ts +++ b/client/common/lib/elements/dynamic-table-form.ts @@ -672,6 +672,7 @@ export class DynamicTableFormElement extends LivechatElement { const validationErrorTypes: ValidationErrorType[] | undefined = this.validation?.[`${this.validationPrefix}.${originalIndex}.${propertyName}`] + // FIXME: this code is duplicated in channel-configuration if (validationErrorTypes !== undefined && validationErrorTypes.length !== 0) { if (validationErrorTypes.includes(ValidationErrorType.Missing)) { errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_MISSING)}`) @@ -688,6 +689,9 @@ export class DynamicTableFormElement extends LivechatElement { if (validationErrorTypes.includes(ValidationErrorType.Duplicate)) { errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_DUPLICATE)}`) } + if (validationErrorTypes.includes(ValidationErrorType.TooLong)) { + errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_TOO_LONG)}`) + } return html`
${errorMessages}
` } else { diff --git a/client/common/lib/models/validation.ts b/client/common/lib/models/validation.ts index da146ab0..b034b693 100644 --- a/client/common/lib/models/validation.ts +++ b/client/common/lib/models/validation.ts @@ -7,7 +7,8 @@ export enum ValidationErrorType { WrongType, WrongFormat, NotInRange, - Duplicate + Duplicate, + TooLong } export class ValidationError extends Error { diff --git a/languages/en.yml b/languages/en.yml index 7ba36fa3..b70ec513 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -45,6 +45,11 @@ diagnostic: | chat_title: "

Chat

" +chat_terms_label: "Terms & Conditions" +chat_terms_description: | + These terms & conditions will be shown to all users when then join chatrooms. + Streamers can also configure terms & conditions for their channels, that will be shown right after these global terms & conditions. + list_rooms_label: "List existing rooms" list_rooms_description: | List rooms @@ -455,6 +460,7 @@ invalid_value_wrong_format: "Value is in the wrong format." invalid_value_not_in_range: "Value is not in authorized range." invalid_value_file_too_big: "File size is too big (max size: %s)." invalid_value_duplicate: "Duplicate value" +invalid_value_too_long: "Value too long" too_many_entries: "Too many entries" slow_mode_info: "Slow mode is enabled, users can send a message every %1$s seconds." @@ -553,3 +559,6 @@ livechat_configuration_channel_mute_anonymous_desc: | Default value for new chatrooms. For existing chatrooms, you can change the feature in the room configuration form. When this feature is enabled, anonymous users can only read the chat, and not send messages. +livechat_configuration_channel_terms_label: "Channel's chat terms & conditions" +livechat_configuration_channel_terms_desc: | + You can configure a "terms & conditions" message that will be shown to users joining your chatrooms. diff --git a/server/lib/configuration/channel/sanitize.ts b/server/lib/configuration/channel/sanitize.ts index 2067e463..1167263f 100644 --- a/server/lib/configuration/channel/sanitize.ts +++ b/server/lib/configuration/channel/sanitize.ts @@ -4,6 +4,7 @@ import type { RegisterServerOptions } from '@peertube/peertube-types' import type { ChannelConfigurationOptions } from '../../../../shared/lib/types' +import { channelTermsMaxLength } from '../../../../shared/lib/constants' /** * Sanitize data so that they can safely be used/stored for channel configuration configuration. @@ -43,6 +44,16 @@ async function sanitizeChannelConfigurationOptions ( throw new Error('Invalid data.mute data type') } + // terms not present in livechat <= 10.2.0 + let terms = data.terms + if (terms !== undefined && (typeof terms !== 'string')) { + throw new Error('Invalid data.terms data type') + } + if (terms && terms.length > channelTermsMaxLength) { + throw new Error('data.terms value too long') + } + if (terms === '') { terms = undefined } + const result: ChannelConfigurationOptions = { bot: { enabled: _readBoolean(botData, 'enabled'), @@ -59,6 +70,9 @@ async function sanitizeChannelConfigurationOptions ( anonymous: _readBoolean(mute, 'anonymous') } } + if (terms !== undefined) { + result.terms = terms + } return result } diff --git a/server/lib/configuration/channel/storage.ts b/server/lib/configuration/channel/storage.ts index 0836db46..1c6c4ce3 100644 --- a/server/lib/configuration/channel/storage.ts +++ b/server/lib/configuration/channel/storage.ts @@ -52,7 +52,8 @@ function getDefaultChannelConfigurationOptions (_options: RegisterServerOptions) }, mute: { anonymous: false - } + }, + terms: undefined } } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 8e8502d5..71bf1981 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -148,6 +148,14 @@ function initChatSettings ({ registerSetting }: RegisterServerOptions): void { private: true, descriptionHTML: loc('chat_title') }) + registerSetting({ + name: 'chat-terms', + private: true, + label: loc('chat_terms_label'), + type: 'input-textarea', + default: '', + descriptionHTML: loc('chat_terms_description') + }) registerSetting({ name: 'prosody-list-rooms', label: loc('list_rooms_label'), diff --git a/shared/lib/constants.ts b/shared/lib/constants.ts new file mode 100644 index 00000000..23231517 --- /dev/null +++ b/shared/lib/constants.ts @@ -0,0 +1 @@ +export const channelTermsMaxLength = 400 diff --git a/shared/lib/types.ts b/shared/lib/types.ts index ad9cdf4f..20ff605e 100644 --- a/shared/lib/types.ts +++ b/shared/lib/types.ts @@ -101,11 +101,12 @@ interface ChannelConfigurationOptions { slowMode: { duration: number } - mute: { + mute: { // comes with Livechat 10.2.0 anonymous: boolean // TODO: https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/127 // nonFollowers: boolean (or a number of seconds?) } + terms?: string // comes with Livechat 10.2.0 } interface ChannelForbiddenWords { From b11045602993faeb4d322fe60daa8d4505a7c92c Mon Sep 17 00:00:00 2001 From: John Livingston Date: Tue, 25 Jun 2024 09:59:46 +0200 Subject: [PATCH 06/25] Terms&Conditions (#18) WIP: * Converse module to display terms. * Prosody module to send terms. --- conversejs/custom/index.js | 2 + .../plugins/terms/components/muc-terms.js | 62 +++++++++++++++++ conversejs/custom/plugins/terms/index.js | 48 ++++++++++++++ .../plugins/terms/styles/muc-terms.scss | 41 ++++++++++++ .../custom/templates/muc-bottom-panel.js | 2 +- conversejs/custom/templates/muc.js | 27 ++++++++ conversejs/custom/webpack.livechat.js | 1 + .../mod_muc_peertubelivechat_terms/README.md | 34 ++++++++++ .../mod_muc_peertubelivechat_terms.lua | 66 +++++++++++++++++++ server/lib/prosody/config.ts | 8 ++- server/lib/prosody/config/content.ts | 39 ++++++++++- 11 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 conversejs/custom/plugins/terms/components/muc-terms.js create mode 100644 conversejs/custom/plugins/terms/index.js create mode 100644 conversejs/custom/plugins/terms/styles/muc-terms.scss create mode 100644 conversejs/custom/templates/muc.js create mode 100644 prosody-modules/mod_muc_peertubelivechat_terms/README.md create mode 100644 prosody-modules/mod_muc_peertubelivechat_terms/mod_muc_peertubelivechat_terms.lua diff --git a/conversejs/custom/index.js b/conversejs/custom/index.js index df11239c..d5fda0a1 100644 --- a/conversejs/custom/index.js +++ b/conversejs/custom/index.js @@ -46,6 +46,7 @@ import './plugins/fullscreen/index.js' import '../custom/plugins/size/index.js' import '../custom/plugins/tasks/index.js' +import '../custom/plugins/terms/index.js' /* END: Removable components */ import { CORE_PLUGINS } from './headless/shared/constants.js' @@ -53,6 +54,7 @@ import { ROOM_FEATURES } from './headless/plugins/muc/constants.js' // We must add our custom plugins to CORE_PLUGINS (so it is white listed): CORE_PLUGINS.push('livechat-converse-size') CORE_PLUGINS.push('livechat-converse-tasks') +CORE_PLUGINS.push('livechat-converse-terms') // 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') diff --git a/conversejs/custom/plugins/terms/components/muc-terms.js b/conversejs/custom/plugins/terms/components/muc-terms.js new file mode 100644 index 00000000..ac57d74a --- /dev/null +++ b/conversejs/custom/plugins/terms/components/muc-terms.js @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' +import { api } from '@converse/headless/core' +import { html } from 'lit' +import { __ } from 'i18n' + +import '../styles/muc-terms.scss' + +export default class MUCTermsView extends CustomElement { + static get properties () { + return { + model: { type: Object, attribute: true }, + termstype: { type: String, attribute: true } + } + } + + async initialize () { + if (!this.model) { + return + } + this.listenTo(this.model, 'change:x_livechat_terms_' + this.termstype, () => this.requestUpdate()) + } + + render () { + const terms = this.model?.get('x_livechat_terms_' + this.termstype) + return html` + ${terms && terms.body && !this._hideInfoBox(terms.body) + ? html` +
+ + + + +
` + : '' + }` + } + + closeInfoBox (ev) { + ev.preventDefault() + const terms = this.model?.get('x_livechat_terms_' + this.termstype) + if (terms) { + localStorage?.setItem('x_livechat_terms_' + this.termstype + '_hidden', terms.body) + } + this.requestUpdate() + } + + _hideInfoBox (body) { + // When hiding the infobox, we store in localStorage the current body, so we will show it again if message change. + // Note: for termstype=global we don't store the MUC server, so if user join chat from different instances, + // it will show terms again + // Note: same for termstype=muc, we don't store the MUC JID, so if user changes channel, + // it will probably show terms again + const lsHideInfoBox = localStorage?.getItem('x_livechat_terms_' + this.termstype + '_hidden') + return lsHideInfoBox === body + } +} + +api.elements.define('livechat-converse-muc-terms', MUCTermsView) diff --git a/conversejs/custom/plugins/terms/index.js b/conversejs/custom/plugins/terms/index.js new file mode 100644 index 00000000..838820d8 --- /dev/null +++ b/conversejs/custom/plugins/terms/index.js @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { converse, api } from '../../../src/headless/core.js' +import './components/muc-terms.js' + +const { sizzle } = converse.env + +converse.plugins.add('livechat-converse-terms', { + dependencies: ['converse-muc'], + initialize () { + api.listen.on('parseMUCMessage', (stanza, attrs) => { + const livechatTerms = sizzle('x-livechat-terms', stanza) + if (!livechatTerms.length) { + return attrs + } + return Object.assign( + attrs, + { + x_livechat_terms: livechatTerms[0].getAttribute('type') + } + ) + }) + }, + overrides: { + ChatRoom: { + onMessage: function onMessage (attrs) { + if (!attrs.x_livechat_terms) { + return this.__super__.onMessage(attrs) + } + // We received a x-livechat-terms message, we don't forward it to standard onMessage, + // but we just update the room attribute. + const type = attrs.x_livechat_terms + if (type !== 'global' && type !== 'muc') { + console.error('Invalid x-livechat-terms type: ', type) + return + } + // console.info('Received a x-livechat-terms message', attrs) + const options = {} + options['x_livechat_terms_' + type] = attrs + this.set(options) + // this will be displayed by the livechat-converse-muc-terms custom element, + // which is inserted in the DOM by the muc.js template overload. + } + } + } +}) diff --git a/conversejs/custom/plugins/terms/styles/muc-terms.scss b/conversejs/custom/plugins/terms/styles/muc-terms.scss new file mode 100644 index 00000000..27f7ccef --- /dev/null +++ b/conversejs/custom/plugins/terms/styles/muc-terms.scss @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + livechat-converse-muc-terms { + background-color: var(--peertube-main-background); + color: var(--peertube-main-foreground); + + div { + align-items: center; + border: 1px solid var(--peertube-menu-background); + display: flex; + flex-flow: row; + justify-content: space-between; + margin: 5px; + padding: 5px; + + converse-rich-text { + flex-grow: 2; + max-height: 5em; + overflow-y: scroll; + white-space: pre-wrap; + } + + .livechat-hide-terms-info-box { + cursor: pointer; + font-size: var(--font-size-small); + flex-shrink: 2; + } + } + } +} + +.livechat-readonly .conversejs { + livechat-converse-muc-terms { + display: none !important; + } +} diff --git a/conversejs/custom/templates/muc-bottom-panel.js b/conversejs/custom/templates/muc-bottom-panel.js index ea5ecb8b..cfac6d2c 100644 --- a/conversejs/custom/templates/muc-bottom-panel.js +++ b/conversejs/custom/templates/muc-bottom-panel.js @@ -63,7 +63,7 @@ class SlowMode extends CustomElement { LOC_slow_mode_info, this.model.config.get('slow_mode_duration') )} - + ` diff --git a/conversejs/custom/templates/muc.js b/conversejs/custom/templates/muc.js new file mode 100644 index 00000000..bb9820c6 --- /dev/null +++ b/conversejs/custom/templates/muc.js @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2013-2024 JC Brand +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: AGPL-3.0-only + +// Must import the original muc.js, because it imports some custom elements files. +import '../../src/plugins/muc-views/templates/muc.js' +import { getChatRoomBodyTemplate } from '../../src/plugins/muc-views/utils.js' +import { html } from 'lit' + +// Overloading the original muc.js, to add some custom elements. +export default (o) => { + return html` +
+ + ${ + o.model + ? html` + + + + +
${getChatRoomBodyTemplate(o)}
` + : ''} +
` +} diff --git a/conversejs/custom/webpack.livechat.js b/conversejs/custom/webpack.livechat.js index 85e68895..bf6e041c 100644 --- a/conversejs/custom/webpack.livechat.js +++ b/conversejs/custom/webpack.livechat.js @@ -40,6 +40,7 @@ module.exports = merge(prod, { alias: { './templates/muc-bottom-panel.js': path.resolve('custom/templates/muc-bottom-panel.js'), './templates/muc-head.js': path.resolve(__dirname, 'custom/templates/muc-head.js'), + './templates/muc.js': path.resolve(__dirname, 'custom/templates/muc.js'), '../../templates/background_logo.js$': path.resolve(__dirname, 'custom/templates/background_logo.js'), './templates/muc-chatarea.js': path.resolve('custom/templates/muc-chatarea.js'), diff --git a/prosody-modules/mod_muc_peertubelivechat_terms/README.md b/prosody-modules/mod_muc_peertubelivechat_terms/README.md new file mode 100644 index 00000000..0c2229f5 --- /dev/null +++ b/prosody-modules/mod_muc_peertubelivechat_terms/README.md @@ -0,0 +1,34 @@ + + +# mod_muc_peertubelivechat_terms + +This module is a custom module to handle Terms&Conditions in the livechat Peertube plugin. + +This module is part of peertube-plugin-livechat, and is under the same LICENSE. + +## Features + +When a new occupant session is created for a MUC, this module will send to the user the global terms, +and the MUC-specific terms (if defined). + +This is done by sending groupchat messages. +These messages will contain a "x-livechat-terms" tag, so that livechat front-end can detect these messages, and display them differently. +For standard XMPP clients, these messages will show as standard MUC message coming from a specific nickname. + +## Configuration + +This modules take following options. + +### muc_terms_service_nickname + +The nickname that will be used by service messages. +This module reserves the nickname, so than nobody can use it in MUC rooms +(we don't want any user to spoof this nickname). + +### muc_terms + +The global terms. diff --git a/prosody-modules/mod_muc_peertubelivechat_terms/mod_muc_peertubelivechat_terms.lua b/prosody-modules/mod_muc_peertubelivechat_terms/mod_muc_peertubelivechat_terms.lua new file mode 100644 index 00000000..be4272ca --- /dev/null +++ b/prosody-modules/mod_muc_peertubelivechat_terms/mod_muc_peertubelivechat_terms.lua @@ -0,0 +1,66 @@ +-- mod_muc_peertubelivechat_terms +-- +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only +-- +-- This file is AGPL-v3 licensed. +-- Please see the Peertube livechat plugin copyright information. +-- https://livingston.frama.io/peertube-plugin-livechat/credits/ +-- +local jid_escape = require "util.jid".escape; +local jid_resource = require "util.jid".resource; +local st = require "util.stanza"; +local id = require "util.id"; + +local service_nickname = module:get_option_string("muc_terms_service_nickname", "Service"); +local global_terms = module:get_option_string("muc_terms", ""); + +-- send the terms when joining: +function send_terms(event) + local origin = event.origin; + local room = event.room; + local occupant = event.occupant; + if global_terms then + local from = room.jid .. '/' .. jid_escape(service_nickname); + module:log("debug", "Sending global terms to %s from %s (room %s)", occupant.jid, from, room); + local message = st.message({ + type = "groupchat", + to = occupant.jid, + from = from, + id = id.medium() + }, global_terms) + :tag('x-livechat-terms', { type = "global" }):up(); -- adding a custom tag to specify that it is a "terms" message, so that frontend can display it with a special template. + origin.send(message); + end +end +-- Note: we could do that on muc-occupant-joined or muc-occupant-session-new. +-- The first will not send it to multiple clients, the second will. +-- After some reflexion, i will try muc-occupant-session-new, and see if it works as expected. +module:hook("muc-occupant-session-new", send_terms); + +-- reserve the service_nickname: +function enforce_nick_policy(event) + local origin, stanza = event.origin, event.stanza; + local requested_nick = jid_resource(stanza.attr.to); + local room = event.room; + if not room then return; end + + if requested_nick == service_nickname then + module:log("debug", "Occupant tried to use the %s reserved nickname, blocking it.", service_nickname); + local reply = st.error_reply(stanza, "cancel", "conflict", nil, room.jid):up(); + origin.send(reply); + return true; + end +end +module:hook("muc-occupant-pre-join", enforce_nick_policy); +module:hook("muc-occupant-pre-change", enforce_nick_policy); + +-- security check: we must remove all "x-livechat-terms" tag, to be sure nobody tries to spoof terms! +module:hook("muc-occupant-groupchat", function(event) + event.stanza:maptags(function (child) + if child.name == 'x-livechat-terms' then + return nil; + end + return child; + end); +end, 100); diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index 90e00c0b..d8f8bd54 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -175,7 +175,8 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise() @@ -199,6 +200,9 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise[2]>() + const prosodyRoomUpdates = new Map[2]>() try { const data = this._serializeData() // must be atomic @@ -355,12 +352,12 @@ class RoomChannel { await BotConfiguration.singleton().updateRoom(roomJID, botConf) - // // Now we also must update some room metadata (slow mode duration, ...) - // // This can be done without waiting for the API call to finish, but we don't want to send thousands of - // // API calls at the same time. So storing data in a map, and we well launch it sequentially at the end - // prosodyRoomUpdates.set(roomJID, { - // slow_mode_duration: channelConfigurationOptions.slowMode.duration - // }) + // Now we also must update some room metadata on Prosody side (livechat_muc_terms, ...) + // This can be done without waiting for the API call to finish, but we don't want to send thousands of + // API calls at the same time. So storing data in a map, and we well launch it sequentially at the end + prosodyRoomUpdates.set(roomJID, { + livechat_muc_terms: channelConfigurationOptions.terms + }) this.roomConfToUpdate.delete(roomJID) } @@ -374,22 +371,22 @@ class RoomChannel { this.isWriting = false } - // if (prosodyRoomUpdates.size) { - // // Here we don't have to wait. - // // If it fails (for example because we are turning off prosody), it is not a big deal. - // // Does not worth the cost to wait. - // // eslint-disable-next-line @typescript-eslint/no-misused-promises - // setTimeout(async () => { - // this.logger.info('Syncing done, but still some data to send to Prosody') - // for (const [roomJID, data] of prosodyRoomUpdates.entries()) { - // try { - // await updateProsodyRoom(this.options, roomJID, data) - // } catch (err) { - // this.logger.error(`Failed updating prosody room info: "${err as string}".`) - // } - // } - // }, 0) - // } + if (prosodyRoomUpdates.size) { + // Here we don't have to wait. + // If it fails (for example because we are turning off prosody), it is not a big deal. + // Does not worth the cost to wait. + // eslint-disable-next-line @typescript-eslint/no-misused-promises + setTimeout(async () => { + this.logger.info('Syncing done, but still some data to send to Prosody') + for (const [roomJID, data] of prosodyRoomUpdates.entries()) { + try { + await updateProsodyRoom(this.options, roomJID, data) + } catch (err) { + this.logger.error(`Failed updating prosody room info: "${err as string}".`) + } + } + }, 0) + } } /** diff --git a/server/lib/routers/api/room.ts b/server/lib/routers/api/room.ts index 57a740b8..11dc10a6 100644 --- a/server/lib/routers/api/room.ts +++ b/server/lib/routers/api/room.ts @@ -37,6 +37,7 @@ interface RoomDefaults { // Following fields are specific to livechat (for now), and requires a customized version for mod_muc_http_defaults. slow_mode_duration?: number mute_anonymous?: boolean + livechat_muc_terms?: string } affiliations?: Affiliations } @@ -50,7 +51,8 @@ async function _getChannelSpecificOptions ( return { slow_mode_duration: channelOptions.slowMode.duration, - mute_anonymous: channelOptions.mute.anonymous + mute_anonymous: channelOptions.mute.anonymous, + livechat_muc_terms: channelOptions.terms } } From d14c2868fd724bfefe6145807a5d8938ec0baee3 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Tue, 25 Jun 2024 11:36:07 +0200 Subject: [PATCH 08/25] Terms&Conditions (#18) WIP: * hidding the terms before anonymous user has chosen his nickname --- conversejs/custom/plugins/terms/styles/muc-terms.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conversejs/custom/plugins/terms/styles/muc-terms.scss b/conversejs/custom/plugins/terms/styles/muc-terms.scss index 27f7ccef..53ed4eed 100644 --- a/conversejs/custom/plugins/terms/styles/muc-terms.scss +++ b/conversejs/custom/plugins/terms/styles/muc-terms.scss @@ -34,7 +34,8 @@ } } -.livechat-readonly .conversejs { +.livechat-readonly, +body[livechat-viewer-mode="on"] { livechat-converse-muc-terms { display: none !important; } From af04f70611516c973a9693e85ffa35bd50b67231 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Tue, 25 Jun 2024 12:19:36 +0200 Subject: [PATCH 09/25] Terms&Conditions (#18) WIP: * user documentation * settings documentation --- .../templates/channel-configuration.ts | 2 +- .../en/documentation/admin/settings.md | 10 +++++ .../documentation/user/streamers/channel.md | 1 + .../en/documentation/user/streamers/emojis.md | 2 +- .../documentation/user/streamers/slow_mode.md | 2 +- .../en/documentation/user/streamers/terms.md | 42 ++++++++++++++++++ .../en/images/channel_terms_config.png | Bin 0 -> 116649 bytes .../documentation/content/en/images/terms.png | Bin 0 -> 45768 bytes 8 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 support/documentation/content/en/documentation/user/streamers/terms.md create mode 100644 support/documentation/content/en/images/channel_terms_config.png create mode 100644 support/documentation/content/en/images/terms.png diff --git a/client/common/configuration/elements/templates/channel-configuration.ts b/client/common/configuration/elements/templates/channel-configuration.ts index 118e05bc..23dcee40 100644 --- a/client/common/configuration/elements/templates/channel-configuration.ts +++ b/client/common/configuration/elements/templates/channel-configuration.ts @@ -131,7 +131,7 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ + .helpPage=${'documentation/user/streamers/terms'}>