From de974eae22dd88b2a47bb415397c8c2138d3474d Mon Sep 17 00:00:00 2001 From: Mehdi Benadel Date: Thu, 23 May 2024 14:41:11 +0200 Subject: [PATCH] Better structure + channel home in LitElement format --- .../common/configuration/contexts/channel.ts | 8 ++ .../translation.ts} | 5 +- .../channel-configuration.ts} | 40 +++--- .../configuration/elements/channel-home.ts | 86 ++++++++++++ .../dynamic-table-form.ts} | 0 .../help-button.ts} | 8 +- .../plugin-configuration-row.ts} | 6 +- client/common/configuration/register.ts | 6 +- .../channel-details.ts} | 37 ++++- client/common/configuration/templates/home.ts | 132 ------------------ client/tsconfig.json | 32 ++--- package-lock.json | 29 ---- package.json | 1 - shared/lib/types.ts | 18 +++ 14 files changed, 191 insertions(+), 217 deletions(-) create mode 100644 client/common/configuration/contexts/channel.ts rename client/common/configuration/{templates/TranslationDirective.ts => directives/translation.ts} (91%) rename client/common/configuration/{templates/ChannelConfigurationElement.ts => elements/channel-configuration.ts} (89%) create mode 100644 client/common/configuration/elements/channel-home.ts rename client/common/configuration/{templates/DynamicTableFormElement.ts => elements/dynamic-table-form.ts} (100%) rename client/common/configuration/{templates/HelpButtonElement.ts => elements/help-button.ts} (86%) rename client/common/configuration/{templates/PluginConfigurationRow.ts => elements/plugin-configuration-row.ts} (86%) rename client/common/configuration/{templates/ChannelConfigurationService.ts => services/channel-details.ts} (53%) delete mode 100644 client/common/configuration/templates/home.ts diff --git a/client/common/configuration/contexts/channel.ts b/client/common/configuration/contexts/channel.ts new file mode 100644 index 00000000..befa159e --- /dev/null +++ b/client/common/configuration/contexts/channel.ts @@ -0,0 +1,8 @@ +import { createContext } from "@lit/context"; +import type { RegisterClientOptions } from "@peertube/peertube-types/client/types"; +import type { ChannelConfiguration } from "shared/lib/types"; +import { ChannelDetailsService } from "../services/channel-details"; + +export const registerClientOptionsContext = createContext(Symbol('register-client-options')); +export const channelConfigurationContext = createContext(Symbol('channel-configuration')); +export const channelDetailsServiceContext = createContext(Symbol('channel-configuration-service')); \ No newline at end of file diff --git a/client/common/configuration/templates/TranslationDirective.ts b/client/common/configuration/directives/translation.ts similarity index 91% rename from client/common/configuration/templates/TranslationDirective.ts rename to client/common/configuration/directives/translation.ts index a360ae4b..07a3bbea 100644 --- a/client/common/configuration/templates/TranslationDirective.ts +++ b/client/common/configuration/directives/translation.ts @@ -1,9 +1,8 @@ -import { PartInfo, PartType, directive } from 'lit/directive.js' +import { PartInfo, directive } from 'lit/directive.js' import { AsyncDirective } from 'lit/async-directive.js' import { RegisterClientHelpers } from '@peertube/peertube-types/client'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { html } from 'lit'; -import { unsafeStatic } from 'lit/static-html.js'; export class TranslationDirective extends AsyncDirective { @@ -34,7 +33,7 @@ export class TranslationDirective extends AsyncDirective { this._translatedValue = locId } - this._asyncUpdateTranslation() + this._asyncUpdateTranslation().then(() => {}, () => {}) return this._internalRender() } diff --git a/client/common/configuration/templates/ChannelConfigurationElement.ts b/client/common/configuration/elements/channel-configuration.ts similarity index 89% rename from client/common/configuration/templates/ChannelConfigurationElement.ts rename to client/common/configuration/elements/channel-configuration.ts index a034d2f3..a0232251 100644 --- a/client/common/configuration/templates/ChannelConfigurationElement.ts +++ b/client/common/configuration/elements/channel-configuration.ts @@ -1,22 +1,16 @@ -import { RegisterClientOptions } from '@peertube/peertube-types/client' -import { css, html, LitElement } from 'lit' -import { repeat } from 'lit-html/directives/repeat.js' +import type { RegisterClientOptions } from '@peertube/peertube-types/client' +import { html, LitElement } from 'lit' import { customElement, property, state } from 'lit/decorators.js' -import { ptTr } from './TranslationDirective' -import { localizedHelpUrl } from '../../../utils/help' -import './DynamicTableFormElement' -import './PluginConfigurationRow' -import './HelpButtonElement' -import { until } from 'async' +import { ptTr } from '../directives/translation' +import './dynamic-table-form' +import './plugin-configuration-row' +import './help-button' import { Task } from '@lit/task'; -import { ChannelConfiguration } from 'shared/lib/types' -import { ChannelConfigurationService } from './ChannelConfigurationService' -import { createContext, provide } from '@lit/context' +import type { ChannelConfiguration } from 'shared/lib/types' +import { ChannelDetailsService } from '../services/channel-details' +import { provide } from '@lit/context' import { getGlobalStyleSheets } from '../../global-styles' - -export const registerClientOptionsContext = createContext(Symbol('register-client-options')); -export const channelConfigurationContext = createContext(Symbol('channel-configuration')); -export const channelConfigurationServiceContext = createContext(Symbol('channel-configuration-service')); +import { channelConfigurationContext, channelDetailsServiceContext, registerClientOptionsContext } from '../contexts/channel' @customElement('channel-configuration') export class ChannelConfigurationElement extends LitElement { @@ -32,8 +26,8 @@ export class ChannelConfigurationElement extends LitElement { @state() public _channelConfiguration: ChannelConfiguration | undefined - @provide({ context: channelConfigurationServiceContext }) - private _configurationService: ChannelConfigurationService | undefined + @provide({ context: channelDetailsServiceContext }) + private _channelDetailsService: ChannelDetailsService | undefined static styles = [ ...getGlobalStyleSheets() @@ -46,8 +40,8 @@ export class ChannelConfigurationElement extends LitElement { task: async ([registerClientOptions], {signal}) => { if (this.registerClientOptions) { - this._configurationService = new ChannelConfigurationService(this.registerClientOptions) - this._channelConfiguration = await this._configurationService.fetchConfiguration(this.channelId ?? 0) + this._channelDetailsService = new ChannelDetailsService(this.registerClientOptions) + this._channelConfiguration = await this._channelDetailsService.fetchConfiguration(this.channelId ?? 0) } }, @@ -56,8 +50,8 @@ export class ChannelConfigurationElement extends LitElement { }); private _saveConfig = () => { - if(this._configurationService && this._channelConfiguration) { - this._configurationService.saveOptions(this._channelConfiguration.channel.id, this._channelConfiguration.configuration) + if(this._channelDetailsService && this._channelConfiguration) { + this._channelDetailsService.saveOptions(this._channelConfiguration.channel.id, this._channelConfiguration.configuration) .then((value) => { this._formStatus = { success: true } console.log(`Configuration has been updated`) @@ -326,7 +320,7 @@ export class ChannelConfigurationElement extends LitElement { : '' } - ${JSON.stringify(this._channelConfiguration)}` + ` }) } } diff --git a/client/common/configuration/elements/channel-home.ts b/client/common/configuration/elements/channel-home.ts new file mode 100644 index 00000000..415bf480 --- /dev/null +++ b/client/common/configuration/elements/channel-home.ts @@ -0,0 +1,86 @@ +import type { RegisterClientOptions } from '@peertube/peertube-types/client' +import { html, LitElement } from 'lit' +import { customElement, property, state } from 'lit/decorators.js' +import { ptTr } from '../directives/translation' +import './help-button' +import { Task } from '@lit/task'; +import type { ChannelLiveChatInfos } from 'shared/lib/types' +import { ChannelDetailsService } from '../services/channel-details' +import { provide } from '@lit/context' +import { getGlobalStyleSheets } from '../../global-styles' +import { channelDetailsServiceContext, registerClientOptionsContext } from '../contexts/channel' + +@customElement('channel-home') +export class ChannelHomeElement extends LitElement { + + @provide({ context: registerClientOptionsContext }) + @property({ attribute: false }) + public registerClientOptions: RegisterClientOptions | undefined + + @state() + public _channels: ChannelLiveChatInfos[] | undefined + + @provide({ context: channelDetailsServiceContext }) + private _channelDetailsService: ChannelDetailsService | undefined + + static styles = [ + ...getGlobalStyleSheets() + ]; + + @state() + public _formStatus: boolean | any = undefined + + private _asyncTaskRender = new Task(this, { + + task: async ([registerClientOptions], {signal}) => { + // Getting the current username in localStorage. Don't know any cleaner way to do. + const username = window.localStorage.getItem('username') + if (!username) { + throw new Error('Can\'t get the current username.') + } + + if (this.registerClientOptions) { + this._channelDetailsService = new ChannelDetailsService(this.registerClientOptions) + this._channels = await this._channelDetailsService.fetchUserChannels(username) + } + }, + + args: () => [this.registerClientOptions] + + }); + + render = () => { + return this._asyncTaskRender.render({ + complete: () => html` +
+

+ ${ptTr(LOC_LIVECHAT_CONFIGURATION_TITLE)} + + +

+

${ptTr(LOC_LIVECHAT_CONFIGURATION_DESC)}

+

${ptTr(LOC_LIVECHAT_CONFIGURATION_PLEASE_SELECT)}

+ +
+ ` + }) + } +} diff --git a/client/common/configuration/templates/DynamicTableFormElement.ts b/client/common/configuration/elements/dynamic-table-form.ts similarity index 100% rename from client/common/configuration/templates/DynamicTableFormElement.ts rename to client/common/configuration/elements/dynamic-table-form.ts diff --git a/client/common/configuration/templates/HelpButtonElement.ts b/client/common/configuration/elements/help-button.ts similarity index 86% rename from client/common/configuration/templates/HelpButtonElement.ts rename to client/common/configuration/elements/help-button.ts index 518efcc5..ce29dda5 100644 --- a/client/common/configuration/templates/HelpButtonElement.ts +++ b/client/common/configuration/elements/help-button.ts @@ -1,13 +1,13 @@ -import { css, html, LitElement } from 'lit' +import { html, LitElement } from 'lit' import { customElement, property, state } from 'lit/decorators.js' import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { helpButtonSVG } from '../../../videowatch/buttons' import { consume } from '@lit/context' -import { registerClientOptionsContext } from './ChannelConfigurationElement' -import { RegisterClientOptions } from '@peertube/peertube-types/client' +import { registerClientOptionsContext } from '../contexts/channel' +import type { RegisterClientOptions } from '@peertube/peertube-types/client' import { Task } from '@lit/task' import { localizedHelpUrl } from '../../../utils/help' -import { ptTr } from './TranslationDirective' +import { ptTr } from '../directives/translation' import { DirectiveResult } from 'lit/directive' import { getGlobalStyleSheets } from '../../global-styles' diff --git a/client/common/configuration/templates/PluginConfigurationRow.ts b/client/common/configuration/elements/plugin-configuration-row.ts similarity index 86% rename from client/common/configuration/templates/PluginConfigurationRow.ts rename to client/common/configuration/elements/plugin-configuration-row.ts index d1b5bf75..74ed56f6 100644 --- a/client/common/configuration/templates/PluginConfigurationRow.ts +++ b/client/common/configuration/elements/plugin-configuration-row.ts @@ -1,10 +1,10 @@ -import { css, html, LitElement } from 'lit' +import { html, LitElement } from 'lit' import { customElement, property } from 'lit/decorators.js' -import './HelpButtonElement' +import './help-button' import { getGlobalStyleSheets } from '../../global-styles' @customElement('plugin-configuration-row') -export class PluginConfigurationRow extends LitElement { +export class PluginConfigurationRowElement extends LitElement { @property({ attribute: false }) public title: string = `title` diff --git a/client/common/configuration/register.ts b/client/common/configuration/register.ts index 64f66a89..7435de96 100644 --- a/client/common/configuration/register.ts +++ b/client/common/configuration/register.ts @@ -3,8 +3,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { RegisterClientOptions } from '@peertube/peertube-types/client' -import { renderConfigurationHome } from './templates/home' -import './templates/ChannelConfigurationElement' +import './elements/channel-home' +import './elements/channel-configuration' import { html, render } from 'lit' /** @@ -20,7 +20,7 @@ async function registerConfiguration (clientOptions: RegisterClientOptions): Pro registerClientRoute({ route: 'livechat/configuration', onMount: async ({ rootEl }) => { - render(await renderConfigurationHome(clientOptions), rootEl) + render(html``, rootEl) } }) diff --git a/client/common/configuration/templates/ChannelConfigurationService.ts b/client/common/configuration/services/channel-details.ts similarity index 53% rename from client/common/configuration/templates/ChannelConfigurationService.ts rename to client/common/configuration/services/channel-details.ts index e7075a00..84c6cb61 100644 --- a/client/common/configuration/templates/ChannelConfigurationService.ts +++ b/client/common/configuration/services/channel-details.ts @@ -1,9 +1,9 @@ -import { RegisterClientOptions } from "@peertube/peertube-types/client" -import { ChannelConfiguration, ChannelConfigurationOptions } from "shared/lib/types" +import type { RegisterClientOptions } from "@peertube/peertube-types/client" +import { ChannelLiveChatInfos, ChannelConfiguration, ChannelConfigurationOptions } from "shared/lib/types" import { getBaseRoute } from "../../../utils/uri" -export class ChannelConfigurationService { +export class ChannelDetailsService { public _registerClientOptions: RegisterClientOptions @@ -42,6 +42,37 @@ export class ChannelConfigurationService { return await response.json() } + fetchUserChannels = async (username: string): Promise => { + // FIXME: if more than 100 channels, loop (or add a pagination) + const channels = await (await fetch( + '/api/v1/accounts/' + encodeURIComponent(username) + '/video-channels?start=0&count=100&sort=name', + { + method: 'GET', + headers: this._headers + } + )).json() + if (!channels || !('data' in channels) || !Array.isArray(channels.data)) { + throw new Error('Can\'t get the channel list.') + } + + for (const channel of channels.data) { + channel.livechatConfigurationUri = '/p/livechat/configuration/channel?channelId=' + encodeURIComponent(channel.id) + + // Note: since Peertube v6.0.0, channel.avatar is dropped, and we have to use channel.avatars. + // So, if !channel.avatar, we will search a suitable one in channel.avatars, and fill channel.avatar. + if (!channel.avatar && channel.avatars && Array.isArray(channel.avatars)) { + for (const avatar of channel.avatars) { + if (avatar.width === 120) { + channel.avatar = avatar + break + } + } + } + } + + return channels.data + } + fetchConfiguration = async (channelId: number): Promise => { const response = await fetch( getBaseRoute(this._registerClientOptions) + '/api/configuration/channel/' + encodeURIComponent(channelId), diff --git a/client/common/configuration/templates/home.ts b/client/common/configuration/templates/home.ts deleted file mode 100644 index 814421ec..00000000 --- a/client/common/configuration/templates/home.ts +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-FileCopyrightText: 2024 John Livingston -// -// SPDX-License-Identifier: AGPL-3.0-only - -import type { RegisterClientOptions } from '@peertube/peertube-types/client' -import { localizedHelpUrl } from '../../../utils/help' -import { helpButtonSVG } from '../../../videowatch/buttons' -import { TemplateResult, html } from 'lit' -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; - -interface HomeViewData { - title: string - description: string - please_select: string - channels: any[] - helpButton: TemplateResult -} - -/** - * Renders the livechat configuration setup home page. - * @param registerClientOptions Peertube client options - * @returns The page content - */ -async function renderConfigurationHome (registerClientOptions: RegisterClientOptions): Promise { - const { peertubeHelpers } = registerClientOptions - - try { - // Getting the current username in localStorage. Don't know any cleaner way to do. - const username = window.localStorage.getItem('username') - if (!username) { - throw new Error('Can\'t get the current username.') - } - - // FIXME: if more than 100 channels, loop (or add a pagination) - const channels = await (await fetch( - '/api/v1/accounts/' + encodeURIComponent(username) + '/video-channels?start=0&count=100&sort=name', - { - method: 'GET', - headers: peertubeHelpers.getAuthHeader() - } - )).json() - if (!channels || !('data' in channels) || !Array.isArray(channels.data)) { - throw new Error('Can\'t get the channel list.') - } - - for (const channel of channels.data) { - channel.livechatConfigurationUri = '/p/livechat/configuration/channel?channelId=' + encodeURIComponent(channel.id) - - // Note: since Peertube v6.0.0, channel.avatar is dropped, and we have to use channel.avatars. - // So, if !channel.avatar, we will search a suitable one in channel.avatars, and fill channel.avatar. - if (!channel.avatar && channel.avatars && Array.isArray(channel.avatars)) { - for (const avatar of channel.avatars) { - if (avatar.width === 120) { - channel.avatar = avatar - break - } - } - } - } - - const view : HomeViewData = { - title: await peertubeHelpers.translate(LOC_LIVECHAT_CONFIGURATION_TITLE), - description: await peertubeHelpers.translate(LOC_LIVECHAT_CONFIGURATION_DESC), - please_select: await peertubeHelpers.translate(LOC_LIVECHAT_CONFIGURATION_PLEASE_SELECT), - channels: channels.data, - helpButton: await _fillViewHelpButtons(registerClientOptions) - } - - - return renderConfigurationHomeFromTemplate(view) - } catch (err: any) { - peertubeHelpers.notifier.error(err.toString()) - return html`` - } -} - -async function _fillViewHelpButtons ( // TODO: refactor with the similar function in channel.ts - registerClientOptions: RegisterClientOptions -): Promise { - const title = await registerClientOptions.peertubeHelpers.translate(LOC_ONLINE_HELP) - - const button = async (page: string): Promise => { - const helpUrl = await localizedHelpUrl(registerClientOptions, { - page - }) - const helpIcon = helpButtonSVG() - return html`${unsafeHTML(helpIcon)}` - } - - return button('documentation/user/streamers/channel') -} - -function renderConfigurationHomeFromTemplate(view: HomeViewData) { - return html` -
-

- ${view.title} - ${view.helpButton} -

-

${view.description}

-

${view.please_select}

- -
- ` -} - -export { - renderConfigurationHome -} diff --git a/client/tsconfig.json b/client/tsconfig.json index d57f7112..259d61de 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -2,25 +2,25 @@ "compilerOptions": { "experimentalDecorators": true, "module": "es2022", - "moduleResolution": "node", + "moduleResolution": "node", "target": "es2022", "allowJs": true, - "sourceMap": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "alwaysStrict": true, // should already be true because of strict:true - "noImplicitAny": true, // should already be true because of strict:true - "noImplicitThis": true, // should already be true because of strict:true - "noImplicitReturns": true, - "strictBindCallApply": true, // should already be true because of strict:true - "noUnusedLocals": false, - "allowSyntheticDefaultImports": true, // Seems necessary for peertube types to work - "isolatedModules": true, // Needed by esbuild https://esbuild.github.io/content-types/#isolated-modules - "esModuleInterop": true, // Needed by esbuild https://esbuild.github.io/content-types/#es-module-interop + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "alwaysStrict": true, // should already be true because of strict:true + "noImplicitAny": true, // should already be true because of strict:true + "noImplicitThis": true, // should already be true because of strict:true + "noImplicitReturns": true, + "strictBindCallApply": true, // should already be true because of strict:true + "noUnusedLocals": true, + "allowSyntheticDefaultImports": true, // Seems necessary for peertube types to work + "isolatedModules": true, // Needed by esbuild https://esbuild.github.io/content-types/#isolated-modules + "esModuleInterop": true, // Needed by esbuild https://esbuild.github.io/content-types/#es-module-interop "outDir": "../dist/client", - "paths": { - "shared/*": ["../shared/*"] - } + "paths": { + "shared/*": ["../shared/*"] + } }, "include": [ "./**/*", diff --git a/package-lock.json b/package-lock.json index ba13ba0b..ca0a0ab2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "lit": "^3.1.3", "log-rotate": "^0.2.8", "openid-client": "^5.6.5", - "rxjs": "^7.8.1", "validate-color": "^2.2.1", "xmppjs-chat-bot": "^0.3.0" }, @@ -10535,19 +10534,6 @@ "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==", "dev": true }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, "node_modules/safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", @@ -20337,21 +20323,6 @@ "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==", "dev": true }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - } - } - }, "safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", diff --git a/package.json b/package.json index b738cbcb..bd7d658c 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "lit": "^3.1.3", "log-rotate": "^0.2.8", "openid-client": "^5.6.5", - "rxjs": "^7.8.1", "validate-color": "^2.2.1", "xmppjs-chat-bot": "^0.3.0" }, diff --git a/shared/lib/types.ts b/shared/lib/types.ts index 2bdd3ee6..b412a2bc 100644 --- a/shared/lib/types.ts +++ b/shared/lib/types.ts @@ -2,6 +2,17 @@ // // SPDX-License-Identifier: AGPL-3.0-only +// Not working for some reason +// import type { ActorImage } from '@peertube/peertube-types' + +interface ActorImage { + width: number + path: string + url?: string + createdAt: Date | string + updatedAt: Date | string +} + type ConverseJSTheme = 'peertube' | 'default' | 'concord' interface InitConverseJSParams { @@ -71,6 +82,12 @@ interface ChannelInfos { displayName: string } +interface ChannelLiveChatInfos extends ChannelInfos { + avatar: ActorImage + avatars: ActorImage[] + livechatConfigurationUri: string +} + interface ChannelConfigurationOptions { bot: { enabled: boolean @@ -134,6 +151,7 @@ export type { ProsodyListRoomsResult, ProsodyListRoomsResultRoom, ChannelInfos, + ChannelLiveChatInfos, ChannelConfigurationOptions, ChannelConfiguration, ChatIncludeMode,