From ac01fdba1c172b1fe7384aa3dc809087efdd2be7 Mon Sep 17 00:00:00 2001 From: Micheline Wu <69046953+michelinewu@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:16:16 -0500 Subject: [PATCH 1/6] Revert "New RTMP target. (#5275)" This reverts commit b14a44888434344ea876f5cdb663cf80519f6cd0. --- app/app-services.ts | 3 - app/components-react/root/LiveDock.tsx | 35 +---- .../root/ShareStreamLink.m.less | 5 - .../shared/PlatformLogo.m.less | 8 -- app/components-react/shared/PlatformLogo.tsx | 1 - app/components-react/sidebar/NavTools.m.less | 4 - app/components-react/sidebar/NavTools.tsx | 3 +- .../sidebar/PlatformIndicator.m.less | 2 +- .../sidebar/PlatformIndicator.tsx | 3 +- .../windows/MultistreamChatInfo.tsx | 6 - .../windows/go-live/PlatformSettings.tsx | 2 - .../go-live/platforms/KickEditStreamInfo.tsx | 65 --------- .../platforms/PlatformSettingsLayout.tsx | 2 - .../windows/settings/Stream.tsx | 75 ++--------- app/components/shared/PlatformLogo.tsx | 1 - app/i18n/en-US/common.json | 1 - app/i18n/en-US/kick.json | 7 - app/i18n/fallback.ts | 1 - app/services/platforms/index.ts | 11 +- app/services/platforms/kick.ts | 127 ------------------ app/services/restream.ts | 14 +- .../settings/streaming/stream-settings.ts | 2 - app/services/streaming/streaming-api.ts | 3 - app/services/streaming/streaming.ts | 5 - app/services/user/index.ts | 23 ---- app/services/widgets/settings/event-list.ts | 2 +- app/services/widgets/settings/stream-boss.ts | 2 +- app/styles/buttons.less | 11 -- app/styles/colors.less | 1 - app/themes.g.less | 2 - media/images/platforms/kick-logo.png | Bin 683 -> 0 bytes 31 files changed, 20 insertions(+), 407 deletions(-) delete mode 100644 app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx delete mode 100644 app/i18n/en-US/kick.json delete mode 100644 app/services/platforms/kick.ts delete mode 100644 media/images/platforms/kick-logo.png diff --git a/app/app-services.ts b/app/app-services.ts index df041f8914af..e3513c8c0d2b 100644 --- a/app/app-services.ts +++ b/app/app-services.ts @@ -78,7 +78,6 @@ export { RestreamService } from 'services/restream'; export { TwitterService } from 'services/integrations/twitter'; export { TwitterPlatformService } from 'services/platforms/twitter'; export { InstagramService } from 'services/platforms/instagram'; -export { KickService } from 'services/platforms/kick'; export { UsageStatisticsService } from './services/usage-statistics'; export { GameOverlayService } from 'services/game-overlay'; export { SharedStorageService } from 'services/integrations/shared-storage'; @@ -206,7 +205,6 @@ import { MarkersService } from 'services/markers'; import { SharedStorageService } from 'services/integrations/shared-storage'; import { RealmService } from 'services/realm'; import { InstagramService } from 'services/platforms/instagram'; -import { KickService } from 'services/platforms/kick'; import { TwitchStudioImporterService } from 'services/ts-importer'; import { RemoteControlService } from 'services/api/remote-control-api'; import { UrlService } from 'services/hosts'; @@ -243,7 +241,6 @@ export const AppServices = { TwitchContentClassificationService, TrovoService, InstagramService, - KickService, DismissablesService, HighlighterService, GrowService, diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 41da076a33e4..279e28b769c5 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import * as remote from '@electron/remote'; import cx from 'classnames'; import Animation from 'rc-animate'; -import { Button, Menu } from 'antd'; +import { Menu } from 'antd'; import pick from 'lodash/pick'; import { initStore, useController } from 'components-react/hooks/zustand'; import { EStreamingState } from 'services/streaming'; @@ -356,33 +356,6 @@ function LiveDock(p: { onLeft: boolean }) { return <>; } - const showKickInfo = - visibleChat === 'kick' || (visibleChat === 'default' && primaryChat === 'kick'); - if (showKickInfo) { - return ( -
-
- {$t('Access chat for Kick in the Stream Dashboard.')} -
- -
- ); - } - return ( {!hideStyleBlockers && (isPlatform(['twitch', 'trovo']) || - (isStreaming && - isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && ( + (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']))) && (
{hasChatTabs && } @@ -493,8 +465,7 @@ function LiveDock(p: { onLeft: boolean }) {
)} {(!ctrl.platform || - (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']) && - !isStreaming)) && ( + (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']) && !isStreaming)) && (
{!hideStyleBlockers && {$t('Your chat is currently offline')}} diff --git a/app/components-react/root/ShareStreamLink.m.less b/app/components-react/root/ShareStreamLink.m.less index 6425af9eb042..8583cf93568b 100644 --- a/app/components-react/root/ShareStreamLink.m.less +++ b/app/components-react/root/ShareStreamLink.m.less @@ -12,9 +12,4 @@ width: 16px; height: 16px; } - - :global(i.kick) { - width: 16px; - height: 16px; - } } diff --git a/app/components-react/shared/PlatformLogo.m.less b/app/components-react/shared/PlatformLogo.m.less index 3009fb348aa5..481055810788 100644 --- a/app/components-react/shared/PlatformLogo.m.less +++ b/app/components-react/shared/PlatformLogo.m.less @@ -77,11 +77,3 @@ background-size: contain; background-repeat: no-repeat; } -.kick { - background-image: url('https://slobs-cdn.streamlabs.com/media/kick-logo.png'); - display: inline-block; - width: 40px; - height: 40px; - background-size: contain; - background-repeat: no-repeat; -} diff --git a/app/components-react/shared/PlatformLogo.tsx b/app/components-react/shared/PlatformLogo.tsx index 6d38ec5bfb57..bf63e5d3bddf 100644 --- a/app/components-react/shared/PlatformLogo.tsx +++ b/app/components-react/shared/PlatformLogo.tsx @@ -35,7 +35,6 @@ export default function PlatformLogo(p: IProps & HTMLAttributes) { twitter: 'twitter', streamlabs: 'icon-streamlabs', instagram: 'instagram', - kick: 'kick', }[p.platform]; } const size = p.size && (sizeMap[p.size] ?? p.size); diff --git a/app/components-react/sidebar/NavTools.m.less b/app/components-react/sidebar/NavTools.m.less index d12692d51031..944a90cafc39 100644 --- a/app/components-react/sidebar/NavTools.m.less +++ b/app/components-react/sidebar/NavTools.m.less @@ -156,10 +156,6 @@ width: 13px; height: 13px; } - &-kick { - width: 15px; - height: 15px; - } &-streamlabs { color: var(--teal) !important; } diff --git a/app/components-react/sidebar/NavTools.tsx b/app/components-react/sidebar/NavTools.tsx index 6ef1e8aed826..fefcf0f32f97 100644 --- a/app/components-react/sidebar/NavTools.tsx +++ b/app/components-react/sidebar/NavTools.tsx @@ -80,9 +80,8 @@ export default function SideNav() { const throttledOpenDashboard = throttle(openDashboard, 2000, { trailing: false }); // Instagram doesn't provide a username, since we're not really linked, pass undefined for a generic logout msg w/o it - // Kick doesn't provide a username, since we're not really linked, pass undefined for a generic logout msg w/o it const username = - isLoggedIn && !['instagram', 'kick'].includes(UserService.views.auth!.primaryPlatform) + isLoggedIn && UserService.views.auth!.primaryPlatform !== 'instagram' ? UserService.username : undefined; diff --git a/app/components-react/sidebar/PlatformIndicator.m.less b/app/components-react/sidebar/PlatformIndicator.m.less index a6799b5e3e8d..035e3f34cd69 100644 --- a/app/components-react/sidebar/PlatformIndicator.m.less +++ b/app/components-react/sidebar/PlatformIndicator.m.less @@ -11,7 +11,7 @@ &-facebook { color: var(--facebook) !important; } - &-trovo, &-twitter, &-tiktok, &-instagram, &-youtube, &-kick { + &-trovo, &-twitter, &-tiktok, &-instagram, &-youtube { width: 15px; height: 15px; } diff --git a/app/components-react/sidebar/PlatformIndicator.tsx b/app/components-react/sidebar/PlatformIndicator.tsx index af10f18e27f9..d44cedfa22f6 100644 --- a/app/components-react/sidebar/PlatformIndicator.tsx +++ b/app/components-react/sidebar/PlatformIndicator.tsx @@ -46,8 +46,7 @@ export default function PlatformIndicator({ platform }: IPlatformIndicatorProps) } const SinglePlatformIndicator = ({ platform }: Pick) => { - const username = - platform?.type === 'instagram' || platform?.type === 'kick' ? undefined : platform?.username; + const username = platform?.type === 'instagram' ? undefined : platform?.username; return ( <> diff --git a/app/components-react/windows/MultistreamChatInfo.tsx b/app/components-react/windows/MultistreamChatInfo.tsx index f56543772328..085dd07dd12f 100644 --- a/app/components-react/windows/MultistreamChatInfo.tsx +++ b/app/components-react/windows/MultistreamChatInfo.tsx @@ -57,12 +57,6 @@ export default function MultistreamChatInfo() { read: false, write: false, }, - { - icon: 'kick', - name: $t('Kick'), - read: true, - write: false, - }, ]; return ( diff --git a/app/components-react/windows/go-live/PlatformSettings.tsx b/app/components-react/windows/go-live/PlatformSettings.tsx index b77aa55928e0..aa0cefef76db 100644 --- a/app/components-react/windows/go-live/PlatformSettings.tsx +++ b/app/components-react/windows/go-live/PlatformSettings.tsx @@ -13,7 +13,6 @@ import { getDefined } from '../../../util/properties-type-guards'; import { TrovoEditStreamInfo } from './platforms/TrovoEditStreamInfo'; import { TwitterEditStreamInfo } from './platforms/TwitterEditStreamInfo'; import { InstagramEditStreamInfo } from './platforms/InstagramEditStreamInfo'; -import { KickEditStreamInfo } from './platforms/KickEditStreamInfo'; import AdvancedSettingsSwitch from './AdvancedSettingsSwitch'; export default function PlatformSettings() { @@ -105,7 +104,6 @@ export default function PlatformSettings() { {platform === 'tiktok' && isTikTokConnected && ( )} - {platform === 'kick' && } {platform === 'trovo' && } {platform === 'twitter' && ( diff --git a/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx b/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx deleted file mode 100644 index 58184e7f923b..000000000000 --- a/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import { $t } from 'services/i18n'; -import Form from '../../../shared/inputs/Form'; -import { createBinding } from '../../../shared/inputs'; -import { IPlatformComponentParams } from './PlatformSettingsLayout'; -import { clipboard } from 'electron'; -import { TextInput } from 'components-react/shared/inputs'; -import { Alert, Button } from 'antd'; - -/** - * Note: The implementation for this component is a light refactor of the InstagramEditStreamInfo component. - */ - -type Props = IPlatformComponentParams<'kick'> & { - isStreamSettingsWindow?: boolean; -}; - -export function KickEditStreamInfo(p: Props) { - const bind = createBinding(p.value, updatedSettings => - p.onChange({ ...p.value, ...updatedSettings }), - ); - - const { isStreamSettingsWindow } = p; - const streamKeyLabel = $t(isStreamSettingsWindow ? 'Stream Key' : 'Kick Stream Key'); - const streamUrlLabel = $t(isStreamSettingsWindow ? 'Stream URL' : 'Kick Stream URL'); - - return ( -
- } - /> - - } - /> - {!isStreamSettingsWindow && ( - - )} - - ); -} - -function PasteButton({ onPaste }: { onPaste: (text: string) => void }) { - return ( - - ); -} diff --git a/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx b/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx index 0efbbe59e47b..1bf753460fa5 100644 --- a/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx +++ b/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx @@ -5,7 +5,6 @@ import { IYoutubeStartStreamOptions } from '../../../../services/platforms/youtu import { IFacebookStartStreamOptions } from '../../../../services/platforms/facebook'; import { ITikTokStartStreamOptions } from '../../../../services/platforms/tiktok'; import { ITrovoStartStreamOptions } from '../../../../services/platforms/trovo'; -import { IKickStartStreamOptions } from '../../../../services/platforms/kick'; export type TLayoutMode = 'singlePlatform' | 'multiplatformAdvanced' | 'multiplatformSimple'; @@ -37,7 +36,6 @@ export interface IPlatformSettings extends Partial> { facebook?: IFacebookStartStreamOptions; tiktok?: ITikTokStartStreamOptions; trovo?: ITrovoStartStreamOptions; - kick?: IKickStartStreamOptions; } export interface IPlatformComponentParams { diff --git a/app/components-react/windows/settings/Stream.tsx b/app/components-react/windows/settings/Stream.tsx index 91316fdab51c..5291cac81238 100644 --- a/app/components-react/windows/settings/Stream.tsx +++ b/app/components-react/windows/settings/Stream.tsx @@ -19,8 +19,6 @@ import Translate from 'components-react/shared/Translate'; import * as remote from '@electron/remote'; import { InstagramEditStreamInfo } from '../go-live/platforms/InstagramEditStreamInfo'; import { IInstagramStartStreamOptions } from 'services/platforms/instagram'; -import { KickEditStreamInfo } from '../go-live/platforms/KickEditStreamInfo'; -import { IKickStartStreamOptions } from 'services/platforms/kick'; import { metadata } from 'components-react/shared/inputs/metadata'; import FormFactory, { TInputValue } from 'components-react/shared/inputs/FormFactory'; import { alertAsync } from '../../modals'; @@ -413,14 +411,13 @@ function SLIDBlock() { */ function Platform(p: { platform: TPlatform }) { const platform = p.platform; - const { UserService, StreamingService, InstagramService, KickService } = Services; + const { UserService, StreamingService, InstagramService } = Services; const { canEditSettings, platformMergeInline, platformUnlink } = useStreamSettings(); - const { isLoading, authInProgress, instagramSettings, kickSettings } = useVuex(() => ({ + const { isLoading, authInProgress, instagramSettings } = useVuex(() => ({ isLoading: UserService.state.authProcessState === EAuthProcessState.Loading, authInProgress: UserService.state.authProcessState === EAuthProcessState.InProgress, instagramSettings: InstagramService.state.settings, - kickSettings: KickService.state.settings, })); const isMerged = StreamingService.views.isPlatformLinked(platform); @@ -439,11 +436,7 @@ function Platform(p: { platform: TPlatform }) { */ const isInstagram = platform === 'instagram'; const [showInstagramFields, setShowInstagramFields] = useState(isInstagram && isMerged); - - const isKick = platform === 'kick'; - const [showKickFields, setShowKickFields] = useState(isKick && isMerged); - - const shouldShowUsername = !isInstagram && !isKick; + const shouldShowUsername = !isInstagram; const usernameOrBlank = shouldShowUsername ? ( <> @@ -473,13 +466,13 @@ function Platform(p: { platform: TPlatform }) { const ConnectButton = () => (
); } - - if (isKick && showKickFields) { - return ( -
- -
- ); - } - return null; }; @@ -583,7 +523,10 @@ function Platform(p: { platform: TPlatform }) {
{shouldShowConnectBtn && } {shouldShowUnlinkBtn && ( - )} diff --git a/app/components/shared/PlatformLogo.tsx b/app/components/shared/PlatformLogo.tsx index 8ea14242f941..5973a3327733 100644 --- a/app/components/shared/PlatformLogo.tsx +++ b/app/components/shared/PlatformLogo.tsx @@ -23,7 +23,6 @@ export default class PlatformLogo extends TsxComponent { trovo: 'trovo', twitter: 'twitter', instagram: 'instagram', - kick: 'kick', }[this.props.platform]; } diff --git a/app/i18n/en-US/common.json b/app/i18n/en-US/common.json index 8933f53c62a8..bc231c707383 100644 --- a/app/i18n/en-US/common.json +++ b/app/i18n/en-US/common.json @@ -154,7 +154,6 @@ "Instagram": "Instagram", "Instagram Live": "Instagram Live", "X (Twitter)": "X (Twitter)", - "Kick": "Kick", "Facebook Profiles": "Facebook Profiles", "Facebook Pages": "Facebook Pages", "Alert Box": "Alert Box", diff --git a/app/i18n/en-US/kick.json b/app/i18n/en-US/kick.json deleted file mode 100644 index 14a2a825b5df..000000000000 --- a/app/i18n/en-US/kick.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Access chat for Kick in the Stream Dashboard.": "Access chat for Kick in the Stream Dashboard.", - "Open Kick Chat": "Open Kick Chat", - "Kick Stream Key": "Kick Stream Key", - "Kick Stream URL": "Kick Stream URL", - "Remember to open Kick in browser and enter your Stream URL and Key to start streaming!": "Remember to open Kick in browser and enter your Stream URL and Key to start streaming!" -} diff --git a/app/i18n/fallback.ts b/app/i18n/fallback.ts index 236c040629b8..221fcff56146 100644 --- a/app/i18n/fallback.ts +++ b/app/i18n/fallback.ts @@ -65,7 +65,6 @@ const fallbackDictionary = { ...require('./en-US/widget-game.json'), ...require('./en-US/loader.json'), ...require('./en-US/guest-cam.json'), - ...require('./en-US/kick.json'), }; export default fallbackDictionary; diff --git a/app/services/platforms/index.ts b/app/services/platforms/index.ts index 37dc4e966cad..a53d9cb1044f 100644 --- a/app/services/platforms/index.ts +++ b/app/services/platforms/index.ts @@ -3,7 +3,6 @@ import { IYoutubeStartStreamOptions, YoutubeService } from './youtube'; import { FacebookService, IFacebookStartStreamOptions } from './facebook'; import { ITikTokStartStreamOptions, TikTokService } from './tiktok'; import { InstagramService, IInstagramStartStreamOptions } from './instagram'; -import { KickService, IKickStartStreamOptions } from './kick'; import { TwitterPlatformService } from './twitter'; import { TTwitchOAuthScope } from './twitch/index'; import { IGoLiveSettings } from 'services/streaming'; @@ -152,8 +151,7 @@ export type TStartStreamOptions = | Partial | Partial | Partial - | Partial - | Partial; + | Partial; // state applicable for all platforms export interface IPlatformState { @@ -244,7 +242,6 @@ export enum EPlatform { Trovo = 'trovo', Twitter = 'twitter', Instagram = 'instagram', - Kick = 'kick', } export type TPlatform = @@ -254,8 +251,7 @@ export type TPlatform = | 'tiktok' | 'trovo' | 'twitter' - | 'instagram' - | 'kick'; + | 'instagram'; export const platformList = [ EPlatform.Facebook, @@ -265,7 +261,6 @@ export const platformList = [ EPlatform.YouTube, EPlatform.Twitter, EPlatform.Instagram, - EPlatform.Kick, ]; export const platformLabels = (platform: TPlatform | string) => @@ -278,7 +273,6 @@ export const platformLabels = (platform: TPlatform | string) => // TODO: translate [EPlatform.Twitter]: 'Twitter', [EPlatform.Instagram]: $t('Instagram'), - [EPlatform.Kick]: $t('Kick'), }[platform]); export function getPlatformService(platform: TPlatform): IPlatformService { @@ -290,7 +284,6 @@ export function getPlatformService(platform: TPlatform): IPlatformService { trovo: TrovoService.instance, twitter: TwitterPlatformService.instance, instagram: InstagramService.instance, - kick: KickService.instance, }[platform]; } diff --git a/app/services/platforms/kick.ts b/app/services/platforms/kick.ts deleted file mode 100644 index 4258916daca6..000000000000 --- a/app/services/platforms/kick.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { getDefined } from 'util/properties-type-guards'; -import { - IPlatformRequest, - IPlatformService, - IPlatformState, - EPlatformCallResult, - TStartStreamOptions, - IGame, - TPlatformCapability, -} from '.'; -import { BasePlatformService } from './base-platform'; -import { IGoLiveSettings } from 'services/streaming'; -import { TDisplayType } from 'services/settings-v2'; -import { InheritMutations } from 'services/core'; -import { WidgetType } from 'services/widgets'; - -/** - * Note: The implementation for this service is a light refactor of the Instagram service. - */ -interface IKickServiceState extends IPlatformState { - settings: IKickStartStreamSettings; -} - -interface IKickStartStreamSettings { - title: string; - streamUrl: string; - streamKey: string; -} - -export interface IKickStartStreamOptions { - title: string; - streamUrl: string; - streamKey: string; -} - -@InheritMutations() -export class KickService - extends BasePlatformService - implements IPlatformService { - static initialState: IKickServiceState = { - ...BasePlatformService.initialState, - settings: { title: '', streamUrl: '', streamKey: '' }, - }; - - searchGames?: (searchString: string) => Promise; - scheduleStream?: (startTime: number, info: TStartStreamOptions) => Promise; - getHeaders: (req: IPlatformRequest, useToken?: string | boolean) => Dictionary; - streamPageUrl: string; - widgetsWhitelist?: WidgetType[]; - - readonly apiBase = ''; - readonly platform = 'kick'; - readonly displayName = 'Kick'; - readonly capabilities = new Set(['resolutionPreset']); - - readonly authWindowOptions = {}; - readonly authUrl = ''; - - fetchNewToken() { - return Promise.resolve(); - } - - protected init() { - this.syncSettingsWithLocalStorage(); - - this.userService.userLogout.subscribe(() => { - this.updateSettings({ title: this.state.settings.title, streamUrl: '', streamKey: '' }); - }); - } - - async beforeGoLive(goLiveSettings: IGoLiveSettings, context?: TDisplayType) { - const settings = getDefined(goLiveSettings.platforms.kick); - - if (!this.streamingService.views.isMultiplatformMode) { - this.streamSettingsService.setSettings( - { - streamType: 'rtmp_custom', - key: settings.streamKey, - server: settings.streamUrl, - }, - context, - ); - } - - this.SET_STREAM_KEY(settings.streamKey); - this.UPDATE_STREAM_SETTINGS(settings); - this.setPlatformContext('kick'); - } - /** - * prepopulate channel info and save it to the store - */ - async prepopulateInfo(): Promise { - this.SET_PREPOPULATED(true); - } - - async validatePlatform() { - /* - * TODO: this validation isn't needed, but doesn't hurt to be safe, in case we decide to persist - * stream URL as part of "linking". Maybe also validate stream keys, they seem to start with IG-* - */ - if (!this.state.settings.streamKey.length || !this.state.settings.streamUrl.length) { - return EPlatformCallResult.Error; - } - - return EPlatformCallResult.Success; - } - - async putChannelInfo(): Promise { - // N/A - } - - updateSettings(settings: IKickStartStreamOptions) { - this.UPDATE_STREAM_SETTINGS(settings); - } - - unlink() { - this.userService.UNLINK_PLATFORM('kick'); - } - - get liveDockEnabled(): boolean { - return true; - } - - get chatUrl(): string { - return 'https://dashboard.kick.com/stream'; - } -} diff --git a/app/services/restream.ts b/app/services/restream.ts index 3ae9f5ab7a15..b7811f255152 100644 --- a/app/services/restream.ts +++ b/app/services/restream.ts @@ -12,7 +12,6 @@ import { StreamingService } from './streaming'; import { FacebookService } from './platforms/facebook'; import { TikTokService } from './platforms/tiktok'; import { TrovoService } from './platforms/trovo'; -import { KickService } from './platforms/kick'; import * as remote from '@electron/remote'; import { VideoSettingsService, TDisplayType } from './settings-v2/video'; import { DualOutputService } from './dual-output'; @@ -57,7 +56,6 @@ export class RestreamService extends StatefulService { @Inject('TikTokService') tiktokService: TikTokService; @Inject() trovoService: TrovoService; @Inject() instagramService: InstagramService; - @Inject() kickService: KickService; @Inject() videoSettingsService: VideoSettingsService; @Inject() dualOutputService: DualOutputService; @Inject('TwitterPlatformService') twitterService: TwitterPlatformService; @@ -311,23 +309,13 @@ export class RestreamService extends StatefulService { // treat instagram as a custom destination const instagramTarget = newTargets.find(t => t.platform === 'instagram'); if (instagramTarget) { - instagramTarget.platform = 'relay' as 'relay'; + instagramTarget.platform = 'relay'; instagramTarget.streamKey = `${this.instagramService.state.settings.streamUrl}${this.instagramService.state.streamKey}`; instagramTarget.mode = isDualOutputMode ? this.dualOutputService.views.getPlatformMode('instagram') : 'landscape'; } - // treat kick as a custom destination - const kickTarget = newTargets.find(t => t.platform === 'kick'); - if (kickTarget) { - kickTarget.platform = 'relay'; - kickTarget.streamKey = `${this.kickService.state.settings.streamUrl}/${this.kickService.state.settings.streamKey}`; - kickTarget.mode = isDualOutputMode - ? this.dualOutputService.views.getPlatformMode('kick') - : 'landscape'; - } - await this.createTargets(newTargets); } diff --git a/app/services/settings/streaming/stream-settings.ts b/app/services/settings/streaming/stream-settings.ts index 801edae58002..abd15911d18b 100644 --- a/app/services/settings/streaming/stream-settings.ts +++ b/app/services/settings/streaming/stream-settings.ts @@ -22,7 +22,6 @@ interface ISavedGoLiveSettings { youtube?: IPlatformFlags; trovo?: IPlatformFlags; tiktok?: IPlatformFlags; - kick?: IPlatformFlags; }; customDestinations?: ICustomStreamDestination[]; advancedMode: boolean; @@ -98,7 +97,6 @@ const platformToServiceNameMap: { [key in TPlatform]: string } = { tiktok: 'Custom', twitter: 'Custom', instagram: 'Custom', - kick: 'Custom', }; /** diff --git a/app/services/streaming/streaming-api.ts b/app/services/streaming/streaming-api.ts index a7fc92c22acb..c49898d5af15 100644 --- a/app/services/streaming/streaming-api.ts +++ b/app/services/streaming/streaming-api.ts @@ -10,7 +10,6 @@ import { ITrovoStartStreamOptions } from '../platforms/trovo'; import { IVideo } from 'obs-studio-node'; import { ITwitterStartStreamOptions } from 'services/platforms/twitter'; import { IInstagramStartStreamOptions } from 'services/platforms/instagram'; -import { IKickStartStreamOptions } from 'services/platforms/kick'; import { TDisplayType } from 'services/settings-v2'; export enum EStreamingState { @@ -52,7 +51,6 @@ export interface IStreamInfo { youtube: TGoLiveChecklistItemState; facebook: TGoLiveChecklistItemState; tiktok: TGoLiveChecklistItemState; - kick: TGoLiveChecklistItemState; trovo: TGoLiveChecklistItemState; twitter: TGoLiveChecklistItemState; instagram: TGoLiveChecklistItemState; @@ -73,7 +71,6 @@ export interface IStreamSettings { trovo?: IPlatformFlags & ITrovoStartStreamOptions; twitter?: IPlatformFlags & ITwitterStartStreamOptions; instagram?: IPlatformFlags & IInstagramStartStreamOptions; - kick?: IPlatformFlags & IKickStartStreamOptions; }; customDestinations: ICustomStreamDestination[]; advancedMode: boolean; diff --git a/app/services/streaming/streaming.ts b/app/services/streaming/streaming.ts index 8c5ea70d749d..d57266367d84 100644 --- a/app/services/streaming/streaming.ts +++ b/app/services/streaming/streaming.ts @@ -139,7 +139,6 @@ export class StreamingService youtube: 'not-started', facebook: 'not-started', tiktok: 'not-started', - kick: 'not-started', trovo: 'not-started', twitter: 'not-started', instagram: 'not-started', @@ -641,10 +640,6 @@ export class StreamingService if (settings.platforms.instagram?.enabled) { this.usageStatisticsService.recordFeatureUsage('StreamToInstagram'); } - - if (settings.platforms.kick?.enabled) { - this.usageStatisticsService.recordFeatureUsage('StreamToKick'); - } } /** diff --git a/app/services/user/index.ts b/app/services/user/index.ts index 88e55c632812..727b7292af99 100644 --- a/app/services/user/index.ts +++ b/app/services/user/index.ts @@ -1287,29 +1287,6 @@ export class UserService extends PersistentStatefulService { return EPlatformCallResult.Success; } - if (platform === 'kick') { - const auth = { - widgetToken: '', - apiToken: '', - primaryPlatform: 'kick' as TPlatform, - platforms: { - kick: { - type: 'kick' as TPlatform, - // HACK: faking kick username - username: 'linked', - token: '', - id: 'kick', - }, - }, - hasRelogged: true, - }; - - this.UPDATE_PLATFORM( - (auth.platforms as Record)[auth.primaryPlatform], - ); - return EPlatformCallResult.Success; - } - this.SET_AUTH_STATE(EAuthProcessState.Loading); const onWindowShow = () => this.SET_AUTH_STATE( diff --git a/app/services/widgets/settings/event-list.ts b/app/services/widgets/settings/event-list.ts index df7d5666525f..fcebcfd8fc10 100644 --- a/app/services/widgets/settings/event-list.ts +++ b/app/services/widgets/settings/event-list.ts @@ -95,7 +95,7 @@ export class EventListService extends WidgetSettingsService { eventsByPlatform(): { key: string; title: string }[] { const platform = this.userService.platform.type as Exclude< TPlatform, - 'tiktok' | 'twitter' | 'instagram' | 'kick' + 'tiktok' | 'twitter' | 'instagram' >; return { twitch: [ diff --git a/app/services/widgets/settings/stream-boss.ts b/app/services/widgets/settings/stream-boss.ts index b66592144b8c..2d1100e8ffd2 100644 --- a/app/services/widgets/settings/stream-boss.ts +++ b/app/services/widgets/settings/stream-boss.ts @@ -169,7 +169,7 @@ export class StreamBossService extends BaseGoalService; return { twitch: [ diff --git a/app/styles/buttons.less b/app/styles/buttons.less index 38238b7ccfdf..78eacf38c3b0 100644 --- a/app/styles/buttons.less +++ b/app/styles/buttons.less @@ -271,17 +271,6 @@ button { } } -.square-button--kick, -.button--kick { - background: var(--kick); - color: black; - - &:hover, - &:active { - background: var(--kick-hover) !important; - } -} - .button--dlive { background: #ffd300; diff --git a/app/styles/colors.less b/app/styles/colors.less index e4753a133613..a944124b7ff7 100644 --- a/app/styles/colors.less +++ b/app/styles/colors.less @@ -67,4 +67,3 @@ @tiktok: white; @trovo: #19D06D; @instagram: white; -@kick: #54fc1f; diff --git a/app/themes.g.less b/app/themes.g.less index 91046582f0f9..7fe2d56beb16 100644 --- a/app/themes.g.less +++ b/app/themes.g.less @@ -94,8 +94,6 @@ --instagram-hover: lighten(@instagram, 4%); --trovo: @trovo; --trovo-hover: lighten(@trovo, 4%); - --kick: @kick; - --kick-hover: lighten(@kick, 4%); } .night-theme { diff --git a/media/images/platforms/kick-logo.png b/media/images/platforms/kick-logo.png deleted file mode 100644 index d29fa0dadfd63943183959c17df65665bec2c2b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 683 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$farfvL#T#WAFU@$KD>xzdp$4j2FX ziY_SFI`K~DHs(WFDcdKlJ;I&Ks(19vlQVh;j+nC^U&b4IVj25$7GqB3bF6U&{PoW) z9{>A%cK7ACd4Jmac7OeqSZC1_|Dycg|9N}%R?WHp`|N7Q#s&`-7bOKD#)%vqE)FV8 zN`eBNs6ye*`Bk@`M;71ddmi=qSBYe_+5+Qwk7jDKZ24F*Pa+YU79>+9on2S_rtkCp z-;d|dciVXE+4ss-y6>6a9{=)`SsWCKR(3yB4}@vy!^BF!#fIy)jR9@@2|(-a?~BOo7kXty{>%D z?~gI@{o9M3B&Yjg4IgAT{C0@mdhG4&$7g>F{y8De&$=b?v&cpK?w+K(-=|ihxK8S* z)bGnrt3LS7=6$gv`oGPYhKD%iqFU(uOanz|mH? Date: Mon, 16 Dec 2024 12:49:22 -0500 Subject: [PATCH 2/6] Added new target. --- app/app-services.ts | 3 + .../pages/onboarding/Connect.tsx | 10 +- .../onboarding/PrimaryPlatformSelect.tsx | 7 +- app/components-react/root/LiveDock.tsx | 13 +- .../root/ShareStreamLink.m.less | 5 + .../shared/PlatformLogo.m.less | 8 + app/components-react/shared/PlatformLogo.tsx | 1 + app/components-react/sidebar/NavTools.m.less | 4 + .../sidebar/PlatformIndicator.m.less | 7 +- .../windows/MultistreamChatInfo.tsx | 6 + .../windows/go-live/PlatformSettings.tsx | 2 + .../go-live/platforms/KickEditStreamInfo.tsx | 36 ++ .../platforms/PlatformSettingsLayout.tsx | 2 + .../windows/settings/Stream.tsx | 4 +- app/components/shared/PlatformLogo.m.less | 9 +- app/components/shared/PlatformLogo.tsx | 1 + app/i18n/en-US/common.json | 1 + app/services/platforms/index.ts | 11 +- app/services/platforms/kick.ts | 324 ++++++++++++++++++ app/services/restream.ts | 12 + .../settings/streaming/stream-settings.ts | 2 + app/services/streaming/streaming-api.ts | 5 +- app/services/streaming/streaming.ts | 1 + app/services/user/index.ts | 16 +- app/services/widgets/settings/event-list.ts | 2 +- app/services/widgets/settings/stream-boss.ts | 2 +- app/styles/buttons.less | 11 + app/styles/colors.less | 1 + app/themes.g.less | 2 + media/images/platforms/kick-logo.png | Bin 0 -> 683 bytes 30 files changed, 490 insertions(+), 18 deletions(-) create mode 100644 app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx create mode 100644 app/services/platforms/kick.ts create mode 100644 media/images/platforms/kick-logo.png diff --git a/app/app-services.ts b/app/app-services.ts index e3513c8c0d2b..5f9777cd7560 100644 --- a/app/app-services.ts +++ b/app/app-services.ts @@ -74,6 +74,7 @@ export { export { FacebookService } from 'services/platforms/facebook'; export { TikTokService } from 'services/platforms/tiktok'; export { TrovoService } from 'services/platforms/trovo'; +export { KickService } from 'services/platforms/kick'; export { RestreamService } from 'services/restream'; export { TwitterService } from 'services/integrations/twitter'; export { TwitterPlatformService } from 'services/platforms/twitter'; @@ -208,6 +209,7 @@ import { InstagramService } from 'services/platforms/instagram'; import { TwitchStudioImporterService } from 'services/ts-importer'; import { RemoteControlService } from 'services/api/remote-control-api'; import { UrlService } from 'services/hosts'; +import { KickService } from 'services/platforms/kick'; export const AppServices = { AppService, @@ -240,6 +242,7 @@ export const AppServices = { TwitchTagsService, TwitchContentClassificationService, TrovoService, + KickService, InstagramService, DismissablesService, HighlighterService, diff --git a/app/components-react/pages/onboarding/Connect.tsx b/app/components-react/pages/onboarding/Connect.tsx index d70690d6d088..f65c62f050ba 100644 --- a/app/components-react/pages/onboarding/Connect.tsx +++ b/app/components-react/pages/onboarding/Connect.tsx @@ -63,7 +63,7 @@ export function Connect() { // streamlabs and trovo are added separarely on markup below const platforms = RecordingModeService.views.isRecordingModeEnabled ? ['youtube'] - : ['twitch', 'youtube', 'facebook', 'twitter', 'tiktok']; + : ['twitch', 'youtube', 'tiktok', 'kick', 'facebook', 'twitter']; const shouldAddTrovo = !RecordingModeService.views.isRecordingModeEnabled; @@ -132,7 +132,9 @@ export function Connect() { loading={loading} onClick={() => authPlatform(platform, afterLogin)} key={platform} - logoSize={['twitter', 'tiktok', 'youtube'].includes(platform) ? 15 : undefined} + logoSize={ + ['twitter', 'tiktok', 'youtube', 'kick'].includes(platform) ? 15 : undefined + } > %{platform}', { @@ -263,7 +265,9 @@ export class LoginModule { const result = await this.UserService.startAuth( platform, - ['youtube', 'twitch', 'twitter', 'tiktok'].includes(platform) ? 'external' : 'internal', + ['youtube', 'twitch', 'twitter', 'tiktok', 'kick'].includes(platform) + ? 'external' + : 'internal', merge, ); diff --git a/app/components-react/pages/onboarding/PrimaryPlatformSelect.tsx b/app/components-react/pages/onboarding/PrimaryPlatformSelect.tsx index c6fb8d9a1451..37ea80031882 100644 --- a/app/components-react/pages/onboarding/PrimaryPlatformSelect.tsx +++ b/app/components-react/pages/onboarding/PrimaryPlatformSelect.tsx @@ -22,7 +22,7 @@ export function PrimaryPlatformSelect() { isPrime: UserService.state.isPrime, })); const { loading, authInProgress, authPlatform, finishSLAuth } = useModule(LoginModule); - const platforms = ['twitch', 'youtube', 'facebook', 'twitter', 'tiktok', 'trovo']; + const platforms = ['twitch', 'youtube', 'tiktok', 'kick', 'facebook', 'twitter', 'trovo']; const platformOptions = [ { value: 'twitch', @@ -54,6 +54,11 @@ export function PrimaryPlatformSelect() { label: 'TikTok', image: , }, + { + value: 'kick', + label: 'Kick', + image: , + }, ].filter(opt => { return linkedPlatforms.includes(opt.value as TPlatform); }); diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index 279e28b769c5..bec04ffb2125 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -27,6 +27,7 @@ class LiveDockController { private youtubeService = Services.YoutubeService; private facebookService = Services.FacebookService; private trovoService = Services.TrovoService; + private kickService = Services.KickService; private tiktokService = Services.TikTokService; private userService = Services.UserService; private customizationService = Services.CustomizationService; @@ -159,6 +160,7 @@ class LiveDockController { // Twitter & Tiktok don't support editing title after going live if (this.isPlatform('twitter') && !this.isRestreaming) return false; if (this.isPlatform('tiktok') && !this.isRestreaming) return false; + if (this.isPlatform('kick') && !this.isRestreaming) return false; return ( this.streamingService.views.isMidStreamMode || @@ -181,6 +183,7 @@ class LiveDockController { if (this.platform === 'youtube') url = this.youtubeService.streamPageUrl; if (this.platform === 'facebook') url = this.facebookService.streamPageUrl; if (this.platform === 'trovo') url = this.trovoService.streamPageUrl; + if (this.platform === 'kick') url = this.kickService.streamPageUrl; if (this.platform === 'tiktok') url = this.tiktokService.streamPageUrl; remote.shell.openExternal(url); } @@ -419,7 +422,7 @@ function LiveDock(p: { onLeft: boolean }) { ctrl.showEditStreamInfo()} className="icon-edit" /> )} - {isPlatform(['youtube', 'facebook', 'trovo', 'tiktok']) && isStreaming && ( + {isPlatform(['youtube', 'facebook', 'trovo', 'tiktok', 'kick']) && isStreaming && (
- {(isPlatform(['twitch', 'trovo', 'facebook']) || + {(isPlatform(['twitch', 'trovo', 'facebook', 'kick']) || (isPlatform(['youtube', 'twitter']) && isStreaming) || (isPlatform(['tiktok']) && isRestreaming)) && ( ctrl.refreshChat()}>{$t('Refresh Chat')} @@ -449,7 +452,8 @@ function LiveDock(p: { onLeft: boolean }) {
{!hideStyleBlockers && (isPlatform(['twitch', 'trovo']) || - (isStreaming && isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']))) && ( + (isStreaming && + isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']))) && (
{hasChatTabs && } @@ -465,7 +469,8 @@ function LiveDock(p: { onLeft: boolean }) {
)} {(!ctrl.platform || - (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok']) && !isStreaming)) && ( + (isPlatform(['youtube', 'facebook', 'twitter', 'tiktok', 'kick']) && + !isStreaming)) && (
{!hideStyleBlockers && {$t('Your chat is currently offline')}} diff --git a/app/components-react/root/ShareStreamLink.m.less b/app/components-react/root/ShareStreamLink.m.less index 8583cf93568b..6425af9eb042 100644 --- a/app/components-react/root/ShareStreamLink.m.less +++ b/app/components-react/root/ShareStreamLink.m.less @@ -12,4 +12,9 @@ width: 16px; height: 16px; } + + :global(i.kick) { + width: 16px; + height: 16px; + } } diff --git a/app/components-react/shared/PlatformLogo.m.less b/app/components-react/shared/PlatformLogo.m.less index 481055810788..3009fb348aa5 100644 --- a/app/components-react/shared/PlatformLogo.m.less +++ b/app/components-react/shared/PlatformLogo.m.less @@ -77,3 +77,11 @@ background-size: contain; background-repeat: no-repeat; } +.kick { + background-image: url('https://slobs-cdn.streamlabs.com/media/kick-logo.png'); + display: inline-block; + width: 40px; + height: 40px; + background-size: contain; + background-repeat: no-repeat; +} diff --git a/app/components-react/shared/PlatformLogo.tsx b/app/components-react/shared/PlatformLogo.tsx index bf63e5d3bddf..6d38ec5bfb57 100644 --- a/app/components-react/shared/PlatformLogo.tsx +++ b/app/components-react/shared/PlatformLogo.tsx @@ -35,6 +35,7 @@ export default function PlatformLogo(p: IProps & HTMLAttributes) { twitter: 'twitter', streamlabs: 'icon-streamlabs', instagram: 'instagram', + kick: 'kick', }[p.platform]; } const size = p.size && (sizeMap[p.size] ?? p.size); diff --git a/app/components-react/sidebar/NavTools.m.less b/app/components-react/sidebar/NavTools.m.less index 944a90cafc39..d12692d51031 100644 --- a/app/components-react/sidebar/NavTools.m.less +++ b/app/components-react/sidebar/NavTools.m.less @@ -156,6 +156,10 @@ width: 13px; height: 13px; } + &-kick { + width: 15px; + height: 15px; + } &-streamlabs { color: var(--teal) !important; } diff --git a/app/components-react/sidebar/PlatformIndicator.m.less b/app/components-react/sidebar/PlatformIndicator.m.less index 035e3f34cd69..d7372cb02fae 100644 --- a/app/components-react/sidebar/PlatformIndicator.m.less +++ b/app/components-react/sidebar/PlatformIndicator.m.less @@ -11,7 +11,12 @@ &-facebook { color: var(--facebook) !important; } - &-trovo, &-twitter, &-tiktok, &-instagram, &-youtube { + &-trovo, + &-twitter, + &-tiktok, + &-instagram, + &-youtube, + &-kick { width: 15px; height: 15px; } diff --git a/app/components-react/windows/MultistreamChatInfo.tsx b/app/components-react/windows/MultistreamChatInfo.tsx index 085dd07dd12f..f56543772328 100644 --- a/app/components-react/windows/MultistreamChatInfo.tsx +++ b/app/components-react/windows/MultistreamChatInfo.tsx @@ -57,6 +57,12 @@ export default function MultistreamChatInfo() { read: false, write: false, }, + { + icon: 'kick', + name: $t('Kick'), + read: true, + write: false, + }, ]; return ( diff --git a/app/components-react/windows/go-live/PlatformSettings.tsx b/app/components-react/windows/go-live/PlatformSettings.tsx index aa0cefef76db..b77aa55928e0 100644 --- a/app/components-react/windows/go-live/PlatformSettings.tsx +++ b/app/components-react/windows/go-live/PlatformSettings.tsx @@ -13,6 +13,7 @@ import { getDefined } from '../../../util/properties-type-guards'; import { TrovoEditStreamInfo } from './platforms/TrovoEditStreamInfo'; import { TwitterEditStreamInfo } from './platforms/TwitterEditStreamInfo'; import { InstagramEditStreamInfo } from './platforms/InstagramEditStreamInfo'; +import { KickEditStreamInfo } from './platforms/KickEditStreamInfo'; import AdvancedSettingsSwitch from './AdvancedSettingsSwitch'; export default function PlatformSettings() { @@ -104,6 +105,7 @@ export default function PlatformSettings() { {platform === 'tiktok' && isTikTokConnected && ( )} + {platform === 'kick' && } {platform === 'trovo' && } {platform === 'twitter' && ( diff --git a/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx b/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx new file mode 100644 index 000000000000..25a0722b75e4 --- /dev/null +++ b/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { CommonPlatformFields } from '../CommonPlatformFields'; +import Form from '../../../shared/inputs/Form'; +import { createBinding, InputComponent } from '../../../shared/inputs'; +import PlatformSettingsLayout, { IPlatformComponentParams } from './PlatformSettingsLayout'; +import { IKickStartStreamOptions } from '../../../../services/platforms/kick'; + +/*** + * Stream Settings for Kick + */ +export const KickEditStreamInfo = InputComponent((p: IPlatformComponentParams<'kick'>) => { + function updateSettings(patch: Partial) { + p.onChange({ ...kickSettings, ...patch }); + } + + const kickSettings = p.value; + const bind = createBinding(kickSettings, newKickSettings => updateSettings(newKickSettings)); + + return ( +
+ + } + requiredFields={
} + /> + + ); +}); diff --git a/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx b/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx index 1bf753460fa5..0efbbe59e47b 100644 --- a/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx +++ b/app/components-react/windows/go-live/platforms/PlatformSettingsLayout.tsx @@ -5,6 +5,7 @@ import { IYoutubeStartStreamOptions } from '../../../../services/platforms/youtu import { IFacebookStartStreamOptions } from '../../../../services/platforms/facebook'; import { ITikTokStartStreamOptions } from '../../../../services/platforms/tiktok'; import { ITrovoStartStreamOptions } from '../../../../services/platforms/trovo'; +import { IKickStartStreamOptions } from '../../../../services/platforms/kick'; export type TLayoutMode = 'singlePlatform' | 'multiplatformAdvanced' | 'multiplatformSimple'; @@ -36,6 +37,7 @@ export interface IPlatformSettings extends Partial> { facebook?: IFacebookStartStreamOptions; tiktok?: ITikTokStartStreamOptions; trovo?: ITrovoStartStreamOptions; + kick?: IKickStartStreamOptions; } export interface IPlatformComponentParams { diff --git a/app/components-react/windows/settings/Stream.tsx b/app/components-react/windows/settings/Stream.tsx index 5291cac81238..67c1fa05e58f 100644 --- a/app/components-react/windows/settings/Stream.tsx +++ b/app/components-react/windows/settings/Stream.tsx @@ -217,7 +217,7 @@ class StreamSettingsModule { } async platformMergeInline(platform: TPlatform) { - const mode = ['youtube', 'twitch', 'twitter', 'tiktok'].includes(platform) + const mode = ['youtube', 'twitch', 'twitter', 'tiktok', 'kick'].includes(platform) ? 'external' : 'internal'; @@ -472,7 +472,7 @@ function Platform(p: { platform: TPlatform }) { style={{ backgroundColor: `var(--${platform})`, borderColor: 'transparent', - color: ['trovo', 'instagram'].includes(platform) ? 'black' : 'inherit', + color: ['trovo', 'instagram', 'kick'].includes(platform) ? 'black' : 'inherit', }} > {$t('Connect')} diff --git a/app/components/shared/PlatformLogo.m.less b/app/components/shared/PlatformLogo.m.less index b82d7edd2005..57afedab6ace 100644 --- a/app/components/shared/PlatformLogo.m.less +++ b/app/components/shared/PlatformLogo.m.less @@ -36,4 +36,11 @@ background-size: contain; background-repeat: no-repeat; } - +.kick { + background-image: url('https://slobs-cdn.streamlabs.com/media/kick-logo.png'); + display: inline-block; + width: 40px; + height: 40px; + background-size: contain; + background-repeat: no-repeat; +} diff --git a/app/components/shared/PlatformLogo.tsx b/app/components/shared/PlatformLogo.tsx index 5973a3327733..a7a1bf7e4814 100644 --- a/app/components/shared/PlatformLogo.tsx +++ b/app/components/shared/PlatformLogo.tsx @@ -21,6 +21,7 @@ export default class PlatformLogo extends TsxComponent { nimotv: 'nimotv', streamlabs: 'icon-streamlabs', trovo: 'trovo', + kick: 'kick', twitter: 'twitter', instagram: 'instagram', }[this.props.platform]; diff --git a/app/i18n/en-US/common.json b/app/i18n/en-US/common.json index bc231c707383..8933f53c62a8 100644 --- a/app/i18n/en-US/common.json +++ b/app/i18n/en-US/common.json @@ -154,6 +154,7 @@ "Instagram": "Instagram", "Instagram Live": "Instagram Live", "X (Twitter)": "X (Twitter)", + "Kick": "Kick", "Facebook Profiles": "Facebook Profiles", "Facebook Pages": "Facebook Pages", "Alert Box": "Alert Box", diff --git a/app/services/platforms/index.ts b/app/services/platforms/index.ts index a53d9cb1044f..6040680f4924 100644 --- a/app/services/platforms/index.ts +++ b/app/services/platforms/index.ts @@ -10,6 +10,7 @@ import { WidgetType } from '../widgets'; import { ITrovoStartStreamOptions, TrovoService } from './trovo'; import { TDisplayType } from 'services/settings-v2'; import { $t } from 'services/i18n'; +import { KickService, IKickStartStreamOptions } from './kick'; export type Tag = string; export interface IGame { @@ -151,7 +152,8 @@ export type TStartStreamOptions = | Partial | Partial | Partial - | Partial; + | Partial + | Partial; // state applicable for all platforms export interface IPlatformState { @@ -242,6 +244,7 @@ export enum EPlatform { Trovo = 'trovo', Twitter = 'twitter', Instagram = 'instagram', + Kick = 'kick', } export type TPlatform = @@ -251,7 +254,8 @@ export type TPlatform = | 'tiktok' | 'trovo' | 'twitter' - | 'instagram'; + | 'instagram' + | 'kick'; export const platformList = [ EPlatform.Facebook, @@ -261,6 +265,7 @@ export const platformList = [ EPlatform.YouTube, EPlatform.Twitter, EPlatform.Instagram, + EPlatform.Kick, ]; export const platformLabels = (platform: TPlatform | string) => @@ -273,6 +278,7 @@ export const platformLabels = (platform: TPlatform | string) => // TODO: translate [EPlatform.Twitter]: 'Twitter', [EPlatform.Instagram]: $t('Instagram'), + [EPlatform.Kick]: $t('Kick'), }[platform]); export function getPlatformService(platform: TPlatform): IPlatformService { @@ -282,6 +288,7 @@ export function getPlatformService(platform: TPlatform): IPlatformService { facebook: FacebookService.instance, tiktok: TikTokService.instance, trovo: TrovoService.instance, + kick: KickService.instance, twitter: TwitterPlatformService.instance, instagram: InstagramService.instance, }[platform]; diff --git a/app/services/platforms/kick.ts b/app/services/platforms/kick.ts new file mode 100644 index 000000000000..6961f32fcbb4 --- /dev/null +++ b/app/services/platforms/kick.ts @@ -0,0 +1,324 @@ +import { InheritMutations, Inject, mutation } from '../core'; +import { BasePlatformService } from './base-platform'; +import { IPlatformRequest, IPlatformService, IPlatformState, TPlatformCapability } from './index'; +import { authorizedHeaders, jfetch } from '../../util/requests'; +import { throwStreamError } from '../streaming/stream-error'; +import { platformAuthorizedRequest } from './utils'; +import { IGoLiveSettings } from '../streaming'; +import { TOutputOrientation } from 'services/restream'; +import { IVideo } from 'obs-studio-node'; +import { TDisplayType } from 'services/settings-v2'; +import { I18nService } from 'services/i18n'; +import { getDefined } from 'util/properties-type-guards'; +import { WindowsService } from 'services/windows'; +import { DiagnosticsService } from 'services/diagnostics'; + +interface IKickStartStreamResponse { + id?: string; + key: string; + rtmp: string; + chat_url: string; + broadcast_id?: string; + channel_name: string; + platform_id: string; + region?: string; + chat_id?: string; +} +interface IKickEndStreamResponse { + id: string; +} + +interface IKickError { + success: boolean; + error: boolean; + message: string; + data: any[]; +} +interface IKickServiceState extends IPlatformState { + settings: IKickStartStreamSettings; + broadcastId: string; + ingest: string; + chatUrl: string; + channelName: string; + platformId?: string; +} + +interface IKickStartStreamSettings { + title: string; + display: TDisplayType; + video?: IVideo; + mode?: TOutputOrientation; +} + +export interface IKickStartStreamOptions { + title: string; +} + +interface IKickRequestHeaders extends Dictionary { + Accept: string; + 'Content-Type': string; + Authorization: string; +} + +@InheritMutations() +export class KickService + extends BasePlatformService + implements IPlatformService { + static initialState: IKickServiceState = { + ...BasePlatformService.initialState, + settings: { + title: '', + display: 'horizontal', + mode: 'landscape', + }, + broadcastId: '', + ingest: '', + chatUrl: '', + channelName: '', + }; + + @Inject() windowsService: WindowsService; + @Inject() diagnosticsService: DiagnosticsService; + + readonly apiBase = ''; + readonly domain = 'https://kick.com'; + readonly platform = 'kick'; + readonly displayName = 'Kick'; + readonly capabilities = new Set(['title', 'viewerCount']); + + authWindowOptions: Electron.BrowserWindowConstructorOptions = { + width: 600, + height: 800, + }; + + private get oauthToken() { + return this.userService.views.state.auth?.platforms?.kick?.token; + } + + /** + * Kick's API currently does not provide viewer count. + * To prevent errors, return 0 for now; + */ + get viewersCount(): number { + return 0; + } + + async beforeGoLive(goLiveSettings: IGoLiveSettings, display?: TDisplayType) { + const kickSettings = getDefined(goLiveSettings.platforms.kick); + const context = display ?? kickSettings?.display; + + try { + const streamInfo = await this.startStream( + goLiveSettings.platforms.kick ?? this.state.settings, + ); + + if (!streamInfo.broadcast_id) { + throwStreamError('PLATFORM_REQUEST_FAILED', { + status: 403, + statusText: 'Kick Error: no broadcast ID returned, unable to start stream.', + }); + } + + this.SET_BROADCAST_ID(streamInfo.broadcast_id); + this.SET_INGEST(streamInfo.rtmp); + this.SET_STREAM_KEY(streamInfo.key); + this.SET_CHAT_URL(streamInfo.chat_url); + this.SET_PLATFORM_ID(streamInfo.platform_id); + + if (!this.streamingService.views.isMultiplatformMode) { + this.streamSettingsService.setSettings( + { + streamType: 'rtmp_custom', + key: streamInfo.key, + server: streamInfo.rtmp, + }, + context, + ); + } + + await this.putChannelInfo(kickSettings); + this.setPlatformContext('kick'); + } catch (e: unknown) { + console.error('Error starting stream: ', e); + throwStreamError('PLATFORM_REQUEST_FAILED', e as any); + } + } + + async afterStopStream(): Promise { + if (this.state.broadcastId) { + await this.endStream(this.state.broadcastId); + } + + // clear server url and stream key + this.SET_INGEST(''); + this.SET_STREAM_KEY(''); + } + + // Note, this needs to be here but should never be called, because we + // currently don't make any calls directly to Kick + async fetchNewToken(): Promise { + const host = this.hostsService.streamlabs; + const url = `https://${host}/api/v5/slobs/kick/refresh`; + const headers = authorizedHeaders(this.userService.apiToken!); + const request = new Request(url, { headers }); + + return jfetch<{ access_token: string }>(request) + .then(response => { + return this.userService.updatePlatformToken('kick', response.access_token); + }) + .catch(e => { + console.error('Error fetching new token.'); + return Promise.reject(e); + }); + } + + /** + * Request Kick API and wrap failed response to a unified error model + */ + async requestKick(reqInfo: IPlatformRequest | string): Promise { + try { + return await platformAuthorizedRequest('kick', reqInfo); + } catch (e: unknown) { + const code = (e as any).result?.error?.code; + + const details = (e as any).result?.error + ? `${(e as any).result.error.type} ${(e as any).result.error.message}` + : 'Connection failed'; + + console.error('Error fetching Kick API: ', details, code); + + return Promise.reject(e); + } + } + + /** + * Starts the stream + * @remark If a user is live and attempts to go live via another + * another streaming method such as Kick's app, this stream will continue + * and the other stream will be prevented from going live. If another instance + * of Streamlabs attempts to go live to Kick, the first stream will be ended + * and Desktop will enter a reconnecting state, which eventually times out. + */ + async startStream(opts: IKickStartStreamOptions): Promise { + const host = this.hostsService.streamlabs; + const url = `https://${host}/api/v5/slobs/kick/stream/start`; + const headers = authorizedHeaders(this.userService.apiToken!); + + const body = new FormData(); + body.append('title', opts.title); + + const request = new Request(url, { headers, method: 'POST', body }); + + return jfetch(request).catch((e: unknown) => { + console.error('Error starting Kick stream: ', e); + + // check if the error is an IKickError + if (e.hasOwnProperty('success')) { + const error = e as IKickError; + + throwStreamError('PLATFORM_REQUEST_FAILED', { + status: 403, + statusText: `Unable to start Kick stream. ${error.message}`, + }); + } + + throwStreamError('PLATFORM_REQUEST_FAILED', e); + }); + } + + async endStream(id: string) { + const host = this.hostsService.streamlabs; + const url = `https://${host}/api/v5/slobs/kick/stream/${id}/end`; + const headers = authorizedHeaders(this.userService.apiToken!); + const request = new Request(url, { headers, method: 'POST' }); + + return jfetch(request); + } + + async fetchViewerCount(): Promise { + return 0; + } + + /** + * prepopulate channel info and save it to the store + */ + async prepopulateInfo(): Promise { + this.SET_PREPOPULATED(true); + } + + async putChannelInfo(settings: IKickStartStreamOptions): Promise { + this.SET_STREAM_SETTINGS(settings); + } + + getHeaders(req: IPlatformRequest, useToken?: string | boolean): IKickRequestHeaders { + return { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.oauthToken}`, + }; + } + + getErrorMessage(error?: any) { + switch (error) { + case error?.message: + return error?.message; + case error?.error_description: + return error?.error_description; + case error?.http_status_code: + return error?.http_status_code; + default: + return 'Error processing Kick request.'; + } + } + + get authUrl() { + const host = this.hostsService.streamlabs; + const query = `_=${Date.now()}&skip_splash=true&external=electron&kick&force_verify&origin=slobs`; + return `https://${host}/slobs/login?${query}`; + } + + get mergeUrl(): string { + const host = this.hostsService.streamlabs; + return `https://${host}/dashboard#/settings/account-settings/platforms`; + } + + get liveDockEnabled(): boolean { + return true; + } + + get chatUrl(): string { + return this.state.chatUrl; + } + + get streamPageUrl(): string { + const username = this.userService.state.auth?.platforms?.kick?.username; + if (!username) return ''; + + return `${this.domain}/${username}`; + } + + get locale(): string { + return I18nService.instance.state.locale; + } + + @mutation() + SET_BROADCAST_ID(broadcastId?: string) { + if (!broadcastId) return; + this.state.broadcastId = broadcastId; + } + + @mutation() + SET_INGEST(ingest: string) { + this.state.ingest = ingest; + } + + @mutation() + SET_CHAT_URL(chatUrl: string) { + this.state.chatUrl = chatUrl; + } + + @mutation() + SET_PLATFORM_ID(platformId: string) { + this.state.platformId = platformId; + } +} diff --git a/app/services/restream.ts b/app/services/restream.ts index b7811f255152..aeaf4024f761 100644 --- a/app/services/restream.ts +++ b/app/services/restream.ts @@ -12,6 +12,7 @@ import { StreamingService } from './streaming'; import { FacebookService } from './platforms/facebook'; import { TikTokService } from './platforms/tiktok'; import { TrovoService } from './platforms/trovo'; +import { KickService } from './platforms/kick'; import * as remote from '@electron/remote'; import { VideoSettingsService, TDisplayType } from './settings-v2/video'; import { DualOutputService } from './dual-output'; @@ -55,6 +56,7 @@ export class RestreamService extends StatefulService { @Inject() facebookService: FacebookService; @Inject('TikTokService') tiktokService: TikTokService; @Inject() trovoService: TrovoService; + @Inject() kickService: KickService; @Inject() instagramService: InstagramService; @Inject() videoSettingsService: VideoSettingsService; @Inject() dualOutputService: DualOutputService; @@ -316,6 +318,16 @@ export class RestreamService extends StatefulService { : 'landscape'; } + // treat kick as a custom destination + const kickTarget = newTargets.find(t => t.platform === 'kick'); + if (kickTarget) { + kickTarget.platform = 'relay'; + kickTarget.streamKey = `${this.kickService.state.ingest}/${this.kickService.state.streamKey}`; + kickTarget.mode = isDualOutputMode + ? this.dualOutputService.views.getPlatformMode('kick') + : 'landscape'; + } + await this.createTargets(newTargets); } diff --git a/app/services/settings/streaming/stream-settings.ts b/app/services/settings/streaming/stream-settings.ts index abd15911d18b..801edae58002 100644 --- a/app/services/settings/streaming/stream-settings.ts +++ b/app/services/settings/streaming/stream-settings.ts @@ -22,6 +22,7 @@ interface ISavedGoLiveSettings { youtube?: IPlatformFlags; trovo?: IPlatformFlags; tiktok?: IPlatformFlags; + kick?: IPlatformFlags; }; customDestinations?: ICustomStreamDestination[]; advancedMode: boolean; @@ -97,6 +98,7 @@ const platformToServiceNameMap: { [key in TPlatform]: string } = { tiktok: 'Custom', twitter: 'Custom', instagram: 'Custom', + kick: 'Custom', }; /** diff --git a/app/services/streaming/streaming-api.ts b/app/services/streaming/streaming-api.ts index c49898d5af15..9baec256eb06 100644 --- a/app/services/streaming/streaming-api.ts +++ b/app/services/streaming/streaming-api.ts @@ -7,9 +7,10 @@ import { IStreamError } from './stream-error'; import { ICustomStreamDestination } from '../settings/streaming'; import { ITikTokStartStreamOptions } from '../platforms/tiktok'; import { ITrovoStartStreamOptions } from '../platforms/trovo'; -import { IVideo } from 'obs-studio-node'; +import { IKickStartStreamOptions } from 'services/platforms/kick'; import { ITwitterStartStreamOptions } from 'services/platforms/twitter'; import { IInstagramStartStreamOptions } from 'services/platforms/instagram'; +import { IVideo } from 'obs-studio-node'; import { TDisplayType } from 'services/settings-v2'; export enum EStreamingState { @@ -52,6 +53,7 @@ export interface IStreamInfo { facebook: TGoLiveChecklistItemState; tiktok: TGoLiveChecklistItemState; trovo: TGoLiveChecklistItemState; + kick: TGoLiveChecklistItemState; twitter: TGoLiveChecklistItemState; instagram: TGoLiveChecklistItemState; setupMultistream: TGoLiveChecklistItemState; @@ -69,6 +71,7 @@ export interface IStreamSettings { facebook?: IPlatformFlags & IFacebookStartStreamOptions; tiktok?: IPlatformFlags & ITikTokStartStreamOptions; trovo?: IPlatformFlags & ITrovoStartStreamOptions; + kick?: IPlatformFlags & IKickStartStreamOptions; twitter?: IPlatformFlags & ITwitterStartStreamOptions; instagram?: IPlatformFlags & IInstagramStartStreamOptions; }; diff --git a/app/services/streaming/streaming.ts b/app/services/streaming/streaming.ts index d57266367d84..4b2ddc9f471e 100644 --- a/app/services/streaming/streaming.ts +++ b/app/services/streaming/streaming.ts @@ -140,6 +140,7 @@ export class StreamingService facebook: 'not-started', tiktok: 'not-started', trovo: 'not-started', + kick: 'not-started', twitter: 'not-started', instagram: 'not-started', setupMultistream: 'not-started', diff --git a/app/services/user/index.ts b/app/services/user/index.ts index 727b7292af99..bffd6772184b 100644 --- a/app/services/user/index.ts +++ b/app/services/user/index.ts @@ -116,6 +116,7 @@ interface ILinkedPlatformsResponse { youtube_account?: ILinkedPlatform; tiktok_account?: ILinkedPlatform; trovo_account?: ILinkedPlatform; + kick_account?: ILinkedPlatform; streamlabs_account?: ILinkedPlatform; twitter_account?: ILinkedPlatform; user_id: number; @@ -753,6 +754,17 @@ export class UserService extends PersistentStatefulService { this.UNLINK_PLATFORM('trovo'); } + if (linkedPlatforms.kick_account) { + this.UPDATE_PLATFORM({ + type: 'kick', + username: linkedPlatforms.kick_account.platform_name, + id: linkedPlatforms.kick_account.platform_id, + token: linkedPlatforms.kick_account.access_token, + }); + } else if (this.state.auth.primaryPlatform !== 'kick') { + this.UNLINK_PLATFORM('kick'); + } + if (linkedPlatforms.streamlabs_account) { this.SET_SLID({ id: linkedPlatforms.streamlabs_account.platform_id, @@ -1283,7 +1295,9 @@ export class UserService extends PersistentStatefulService { hasRelogged: true, }; - this.UPDATE_PLATFORM(auth.platforms[auth.primaryPlatform]); + this.UPDATE_PLATFORM( + (auth.platforms as Record)[auth.primaryPlatform], + ); return EPlatformCallResult.Success; } diff --git a/app/services/widgets/settings/event-list.ts b/app/services/widgets/settings/event-list.ts index fcebcfd8fc10..df7d5666525f 100644 --- a/app/services/widgets/settings/event-list.ts +++ b/app/services/widgets/settings/event-list.ts @@ -95,7 +95,7 @@ export class EventListService extends WidgetSettingsService { eventsByPlatform(): { key: string; title: string }[] { const platform = this.userService.platform.type as Exclude< TPlatform, - 'tiktok' | 'twitter' | 'instagram' + 'tiktok' | 'twitter' | 'instagram' | 'kick' >; return { twitch: [ diff --git a/app/services/widgets/settings/stream-boss.ts b/app/services/widgets/settings/stream-boss.ts index 2d1100e8ffd2..b66592144b8c 100644 --- a/app/services/widgets/settings/stream-boss.ts +++ b/app/services/widgets/settings/stream-boss.ts @@ -169,7 +169,7 @@ export class StreamBossService extends BaseGoalService; return { twitch: [ diff --git a/app/styles/buttons.less b/app/styles/buttons.less index 78eacf38c3b0..38238b7ccfdf 100644 --- a/app/styles/buttons.less +++ b/app/styles/buttons.less @@ -271,6 +271,17 @@ button { } } +.square-button--kick, +.button--kick { + background: var(--kick); + color: black; + + &:hover, + &:active { + background: var(--kick-hover) !important; + } +} + .button--dlive { background: #ffd300; diff --git a/app/styles/colors.less b/app/styles/colors.less index a944124b7ff7..a40a26acf00b 100644 --- a/app/styles/colors.less +++ b/app/styles/colors.less @@ -66,4 +66,5 @@ @twitter: #1DA1F2; @tiktok: white; @trovo: #19D06D; +@kick: #54FC1F; @instagram: white; diff --git a/app/themes.g.less b/app/themes.g.less index 7fe2d56beb16..91046582f0f9 100644 --- a/app/themes.g.less +++ b/app/themes.g.less @@ -94,6 +94,8 @@ --instagram-hover: lighten(@instagram, 4%); --trovo: @trovo; --trovo-hover: lighten(@trovo, 4%); + --kick: @kick; + --kick-hover: lighten(@kick, 4%); } .night-theme { diff --git a/media/images/platforms/kick-logo.png b/media/images/platforms/kick-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d29fa0dadfd63943183959c17df65665bec2c2b5 GIT binary patch literal 683 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$farfvL#T#WAFU@$KD>xzdp$4j2FX ziY_SFI`K~DHs(WFDcdKlJ;I&Ks(19vlQVh;j+nC^U&b4IVj25$7GqB3bF6U&{PoW) z9{>A%cK7ACd4Jmac7OeqSZC1_|Dycg|9N}%R?WHp`|N7Q#s&`-7bOKD#)%vqE)FV8 zN`eBNs6ye*`Bk@`M;71ddmi=qSBYe_+5+Qwk7jDKZ24F*Pa+YU79>+9on2S_rtkCp z-;d|dciVXE+4ss-y6>6a9{=)`SsWCKR(3yB4}@vy!^BF!#fIy)jR9@@2|(-a?~BOo7kXty{>%D z?~gI@{o9M3B&Yjg4IgAT{C0@mdhG4&$7g>F{y8De&$=b?v&cpK?w+K(-=|ihxK8S* z)bGnrt3LS7=6$gv`oGPYhKD%iqFU(uOanz|mH? Date: Mon, 16 Dec 2024 14:14:30 -0500 Subject: [PATCH 3/6] Add test and error handling. --- app/services/platforms/kick.ts | 27 ++++++++++++++++------- test/data/dummy-accounts.ts | 24 +++++++++++++++++++-- test/regular/streaming/kick.ts | 39 ++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 test/regular/streaming/kick.ts diff --git a/app/services/platforms/kick.ts b/app/services/platforms/kick.ts index 6961f32fcbb4..e351fadb1f94 100644 --- a/app/services/platforms/kick.ts +++ b/app/services/platforms/kick.ts @@ -209,20 +209,31 @@ export class KickService const request = new Request(url, { headers, method: 'POST', body }); - return jfetch(request).catch((e: unknown) => { + return jfetch(request).catch((e: IKickError | unknown) => { console.error('Error starting Kick stream: ', e); + const defaultError = { + status: 403, + statusText: 'Unable to start Kick stream.', + }; + + if (!e) throwStreamError('PLATFORM_REQUEST_FAILED', defaultError); + // check if the error is an IKickError - if (e.hasOwnProperty('success')) { + if (typeof e === 'object' && e.hasOwnProperty('success')) { const error = e as IKickError; - - throwStreamError('PLATFORM_REQUEST_FAILED', { - status: 403, - statusText: `Unable to start Kick stream. ${error.message}`, - }); + throwStreamError( + 'PLATFORM_REQUEST_FAILED', + { + ...error, + status: 403, + statusText: error.message, + }, + defaultError.statusText, + ); } - throwStreamError('PLATFORM_REQUEST_FAILED', e); + throwStreamError('PLATFORM_REQUEST_FAILED', e as any, defaultError.statusText); }); } diff --git a/test/data/dummy-accounts.ts b/test/data/dummy-accounts.ts index 05c030ca3489..1b66c02f2b13 100644 --- a/test/data/dummy-accounts.ts +++ b/test/data/dummy-accounts.ts @@ -3,7 +3,7 @@ import { ITestUser } from '../helpers/webdriver/user'; import { TPlatform } from 'services/platforms'; // update this list for platforms that use dummy user accounts for tests -const platforms = ['twitter', 'instagram', 'tiktok'] as const; +const platforms = ['twitter', 'instagram', 'tiktok', 'kick'] as const; type DummyUserPlatforms = typeof platforms; export type TTestDummyUserPlatforms = DummyUserPlatforms[number]; @@ -105,7 +105,7 @@ export const instagramUser1: IDummyTestUser = { }; /** - * Twitter + * X (Twitter) */ export const twitterUser1: IDummyTestUser = { @@ -122,6 +122,24 @@ export const twitterUser1: IDummyTestUser = { widgetToken: 'twitterWidgetToken1', }; +/** + * Kick + */ + +export const kickUser1: IDummyTestUser = { + email: 'kickUser1@email.com', + workerId: 'kickWorkerId1', + updated: 'kickUpdatedId1', + username: 'kickUser1', + type: 'kick', + id: 'kickId1', + token: 'kickToken1', + apiToken: 'kickApiToken1', + ingest: 'rtmps://kickIngestUrl:443/rtmp/', + streamKey: 'kickStreamKey1', + widgetToken: 'kickWidgetToken1', +}; + /** * Check if platform should use a dummy account with tests * @param platform platform for login @@ -146,6 +164,8 @@ export function getDummyUser( if (platform === 'twitter') return twitterUser1; + if (platform === 'kick') return kickUser1; + if (platform === 'tiktok') { switch (tikTokLiveScope) { case 'approved': diff --git a/test/regular/streaming/kick.ts b/test/regular/streaming/kick.ts new file mode 100644 index 000000000000..f360fbe52f06 --- /dev/null +++ b/test/regular/streaming/kick.ts @@ -0,0 +1,39 @@ +import { test, useWebdriver } from '../../helpers/webdriver'; +import { + clickGoLive, + prepareToGoLive, + stopStream, + submit, + waitForSettingsWindowLoaded, + waitForStreamStart, +} from '../../helpers/modules/streaming'; +import { addDummyAccount, withUser } from '../../helpers/webdriver/user'; +import { fillForm } from '../../helpers/modules/forms'; +import { waitForDisplayed } from '../../helpers/modules/core'; + +// not a react hook +// eslint-disable-next-line react-hooks/rules-of-hooks +useWebdriver(); + +test('Streaming to Kick', withUser('twitch', { multistream: true }), async t => { + await addDummyAccount('kick'); + + await prepareToGoLive(); + await clickGoLive(); + await waitForSettingsWindowLoaded(); + await fillForm({ + kick: true, + }); + await waitForSettingsWindowLoaded(); + await waitForDisplayed('div[data-name="kick-settings"]'); + + await fillForm({ + title: 'Test stream', + twitchGame: 'Fortnite', + }); + await submit(); + await waitForDisplayed('span=Update settings for Kick'); + await waitForStreamStart(); + await stopStream(); + t.pass(); +}); From 3d3d932113acc906e7dbfa1f0d9e486b09002879 Mon Sep 17 00:00:00 2001 From: Micheline Wu <69046953+michelinewu@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:14:43 -0500 Subject: [PATCH 4/6] WIP: title banner --- .../go-live/platforms/KickEditStreamInfo.tsx | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx b/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx index 25a0722b75e4..c49b11995c85 100644 --- a/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx +++ b/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx @@ -1,9 +1,15 @@ import React from 'react'; import { CommonPlatformFields } from '../CommonPlatformFields'; +import { Services } from '../../../service-provider'; +import { $t } from 'services/i18n'; import Form from '../../../shared/inputs/Form'; import { createBinding, InputComponent } from '../../../shared/inputs'; import PlatformSettingsLayout, { IPlatformComponentParams } from './PlatformSettingsLayout'; import { IKickStartStreamOptions } from '../../../../services/platforms/kick'; +import InfoBanner from 'components-react/shared/InfoBanner'; +import * as remote from '@electron/remote'; +import InputWrapper from 'components-react/shared/inputs/InputWrapper'; +import { TextInput } from 'components-react/shared/inputs'; /*** * Stream Settings for Kick @@ -21,16 +27,45 @@ export const KickEditStreamInfo = InputComponent((p: IPlatformComponentParams<'k { + remote.shell.openExternal(Services.KickService.streamPageUrl); + }} + type="warning" + style={{ marginTop: '5px', marginBottom: '5px' }} + /> + } /> } requiredFields={
} /> + {/* + + { + remote.shell.openExternal(Services.KickService.streamPageUrl); + }} + type="warning" + style={{ marginTop: '5px', marginBottom: '5px' }} + /> + + } + /> */} ); }); From 2a894917db16159e453746e42878c3fb3a9a6387 Mon Sep 17 00:00:00 2001 From: Micheline Wu <69046953+michelinewu@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:10:31 -0500 Subject: [PATCH 5/6] Go live polishes. --- app/components-react/root/LiveDock.tsx | 1 + .../windows/go-live/CommonPlatformFields.tsx | 19 ++++++-- .../windows/go-live/GoLiveChecklist.m.less | 1 + .../go-live/platforms/KickEditStreamInfo.tsx | 47 +++---------------- app/i18n/en-US/kick.json | 3 ++ app/i18n/fallback.ts | 1 + app/services/platforms/kick.ts | 17 ++----- 7 files changed, 30 insertions(+), 59 deletions(-) create mode 100644 app/i18n/en-US/kick.json diff --git a/app/components-react/root/LiveDock.tsx b/app/components-react/root/LiveDock.tsx index bec04ffb2125..278d5a82e618 100644 --- a/app/components-react/root/LiveDock.tsx +++ b/app/components-react/root/LiveDock.tsx @@ -193,6 +193,7 @@ class LiveDockController { if (this.platform === 'youtube') url = this.youtubeService.dashboardUrl; if (this.platform === 'facebook') url = this.facebookService.streamDashboardUrl; if (this.platform === 'tiktok') url = this.tiktokService.dashboardUrl; + if (this.platform === 'kick') url = this.kickService.dashboardUrl; remote.shell.openExternal(url); } diff --git a/app/components-react/windows/go-live/CommonPlatformFields.tsx b/app/components-react/windows/go-live/CommonPlatformFields.tsx index d76d2d4f014a..56ee9afeee2d 100644 --- a/app/components-react/windows/go-live/CommonPlatformFields.tsx +++ b/app/components-react/windows/go-live/CommonPlatformFields.tsx @@ -1,6 +1,6 @@ import { TPlatform } from '../../../services/platforms'; import { $t } from '../../../services/i18n'; -import React from 'react'; +import React, { useMemo } from 'react'; import { CheckboxInput, InputComponent, TextAreaInput, TextInput } from '../../shared/inputs'; import { assertIsDefined } from '../../../util/properties-type-guards'; import InputWrapper from '../../shared/inputs/InputWrapper'; @@ -90,6 +90,18 @@ export const CommonPlatformFields = InputComponent((rawProps: IProps) => { maxCharacters = 140; } + const titleTooltip = useMemo(() => { + if (enabledPlatforms.includes('tiktok')) { + return $t('Only 32 characters of your title will display on TikTok'); + } + + if (enabledPlatforms.length === 1 && p?.platform === 'kick') { + return $t('Edit your stream title on Kick after going live.'); + } + + return undefined; + }, [enabledPlatforms]); + return (
{/* USE CUSTOM CHECKBOX */} @@ -115,10 +127,7 @@ export const CommonPlatformFields = InputComponent((rawProps: IProps) => { label={$t('Title')} required={true} max={maxCharacters} - tooltip={ - enabledPlatforms.includes('tiktok') && - $t('Only 32 characters of your title will display on TikTok') - } + tooltip={titleTooltip} /> {/*DESCRIPTION*/} diff --git a/app/components-react/windows/go-live/GoLiveChecklist.m.less b/app/components-react/windows/go-live/GoLiveChecklist.m.less index a42fe4763bb0..1efbcd9069e4 100644 --- a/app/components-react/windows/go-live/GoLiveChecklist.m.less +++ b/app/components-react/windows/go-live/GoLiveChecklist.m.less @@ -6,6 +6,7 @@ align-items: center; justify-content: center; height: 100%; + margin: 0px 20px; } // make timeline icons and text bigger diff --git a/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx b/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx index c49b11995c85..25a0722b75e4 100644 --- a/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx +++ b/app/components-react/windows/go-live/platforms/KickEditStreamInfo.tsx @@ -1,15 +1,9 @@ import React from 'react'; import { CommonPlatformFields } from '../CommonPlatformFields'; -import { Services } from '../../../service-provider'; -import { $t } from 'services/i18n'; import Form from '../../../shared/inputs/Form'; import { createBinding, InputComponent } from '../../../shared/inputs'; import PlatformSettingsLayout, { IPlatformComponentParams } from './PlatformSettingsLayout'; import { IKickStartStreamOptions } from '../../../../services/platforms/kick'; -import InfoBanner from 'components-react/shared/InfoBanner'; -import * as remote from '@electron/remote'; -import InputWrapper from 'components-react/shared/inputs/InputWrapper'; -import { TextInput } from 'components-react/shared/inputs'; /*** * Stream Settings for Kick @@ -27,45 +21,16 @@ export const KickEditStreamInfo = InputComponent((p: IPlatformComponentParams<'k { - remote.shell.openExternal(Services.KickService.streamPageUrl); - }} - type="warning" - style={{ marginTop: '5px', marginBottom: '5px' }} - /> - } + } requiredFields={
} /> - {/* - - { - remote.shell.openExternal(Services.KickService.streamPageUrl); - }} - type="warning" - style={{ marginTop: '5px', marginBottom: '5px' }} - /> - - } - /> */} ); }); diff --git a/app/i18n/en-US/kick.json b/app/i18n/en-US/kick.json new file mode 100644 index 000000000000..37b7dc85eb43 --- /dev/null +++ b/app/i18n/en-US/kick.json @@ -0,0 +1,3 @@ +{ + "Edit your stream title on Kick after going live.": "Edit your stream title on Kick after going live." +} diff --git a/app/i18n/fallback.ts b/app/i18n/fallback.ts index 221fcff56146..236c040629b8 100644 --- a/app/i18n/fallback.ts +++ b/app/i18n/fallback.ts @@ -65,6 +65,7 @@ const fallbackDictionary = { ...require('./en-US/widget-game.json'), ...require('./en-US/loader.json'), ...require('./en-US/guest-cam.json'), + ...require('./en-US/kick.json'), }; export default fallbackDictionary; diff --git a/app/services/platforms/kick.ts b/app/services/platforms/kick.ts index e351fadb1f94..c7ba16ca3a66 100644 --- a/app/services/platforms/kick.ts +++ b/app/services/platforms/kick.ts @@ -269,19 +269,6 @@ export class KickService }; } - getErrorMessage(error?: any) { - switch (error) { - case error?.message: - return error?.message; - case error?.error_description: - return error?.error_description; - case error?.http_status_code: - return error?.http_status_code; - default: - return 'Error processing Kick request.'; - } - } - get authUrl() { const host = this.hostsService.streamlabs; const query = `_=${Date.now()}&skip_splash=true&external=electron&kick&force_verify&origin=slobs`; @@ -301,6 +288,10 @@ export class KickService return this.state.chatUrl; } + get dashboardUrl(): string { + return `https://dashboard.${this.domain.split('//')[1]}/stream`; + } + get streamPageUrl(): string { const username = this.userService.state.auth?.platforms?.kick?.username; if (!username) return ''; From 87a4afd77e3a54b160c5f4209e087c523fa76fc6 Mon Sep 17 00:00:00 2001 From: Micheline Wu <69046953+michelinewu@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:22:15 -0500 Subject: [PATCH 6/6] Fix broadcast id and test. --- app/services/platforms/kick.ts | 36 ++-------------------------------- test/regular/streaming/kick.ts | 15 ++++---------- 2 files changed, 6 insertions(+), 45 deletions(-) diff --git a/app/services/platforms/kick.ts b/app/services/platforms/kick.ts index c7ba16ca3a66..81667f59ad61 100644 --- a/app/services/platforms/kick.ts +++ b/app/services/platforms/kick.ts @@ -18,7 +18,7 @@ interface IKickStartStreamResponse { key: string; rtmp: string; chat_url: string; - broadcast_id?: string; + broadcast_id?: string | null; channel_name: string; platform_id: string; region?: string; @@ -36,7 +36,6 @@ interface IKickError { } interface IKickServiceState extends IPlatformState { settings: IKickStartStreamSettings; - broadcastId: string; ingest: string; chatUrl: string; channelName: string; @@ -71,7 +70,6 @@ export class KickService display: 'horizontal', mode: 'landscape', }, - broadcastId: '', ingest: '', chatUrl: '', channelName: '', @@ -84,7 +82,7 @@ export class KickService readonly domain = 'https://kick.com'; readonly platform = 'kick'; readonly displayName = 'Kick'; - readonly capabilities = new Set(['title', 'viewerCount']); + readonly capabilities = new Set(['chat']); authWindowOptions: Electron.BrowserWindowConstructorOptions = { width: 600, @@ -95,14 +93,6 @@ export class KickService return this.userService.views.state.auth?.platforms?.kick?.token; } - /** - * Kick's API currently does not provide viewer count. - * To prevent errors, return 0 for now; - */ - get viewersCount(): number { - return 0; - } - async beforeGoLive(goLiveSettings: IGoLiveSettings, display?: TDisplayType) { const kickSettings = getDefined(goLiveSettings.platforms.kick); const context = display ?? kickSettings?.display; @@ -112,14 +102,6 @@ export class KickService goLiveSettings.platforms.kick ?? this.state.settings, ); - if (!streamInfo.broadcast_id) { - throwStreamError('PLATFORM_REQUEST_FAILED', { - status: 403, - statusText: 'Kick Error: no broadcast ID returned, unable to start stream.', - }); - } - - this.SET_BROADCAST_ID(streamInfo.broadcast_id); this.SET_INGEST(streamInfo.rtmp); this.SET_STREAM_KEY(streamInfo.key); this.SET_CHAT_URL(streamInfo.chat_url); @@ -145,10 +127,6 @@ export class KickService } async afterStopStream(): Promise { - if (this.state.broadcastId) { - await this.endStream(this.state.broadcastId); - } - // clear server url and stream key this.SET_INGEST(''); this.SET_STREAM_KEY(''); @@ -246,10 +224,6 @@ export class KickService return jfetch(request); } - async fetchViewerCount(): Promise { - return 0; - } - /** * prepopulate channel info and save it to the store */ @@ -303,12 +277,6 @@ export class KickService return I18nService.instance.state.locale; } - @mutation() - SET_BROADCAST_ID(broadcastId?: string) { - if (!broadcastId) return; - this.state.broadcastId = broadcastId; - } - @mutation() SET_INGEST(ingest: string) { this.state.ingest = ingest; diff --git a/test/regular/streaming/kick.ts b/test/regular/streaming/kick.ts index f360fbe52f06..4cd0ad503e11 100644 --- a/test/regular/streaming/kick.ts +++ b/test/regular/streaming/kick.ts @@ -1,4 +1,4 @@ -import { test, useWebdriver } from '../../helpers/webdriver'; +import { skipCheckingErrorsInLog, test, useWebdriver } from '../../helpers/webdriver'; import { clickGoLive, prepareToGoLive, @@ -9,7 +9,7 @@ import { } from '../../helpers/modules/streaming'; import { addDummyAccount, withUser } from '../../helpers/webdriver/user'; import { fillForm } from '../../helpers/modules/forms'; -import { waitForDisplayed } from '../../helpers/modules/core'; +import { isDisplayed, waitForDisplayed } from '../../helpers/modules/core'; // not a react hook // eslint-disable-next-line react-hooks/rules-of-hooks @@ -21,19 +21,12 @@ test('Streaming to Kick', withUser('twitch', { multistream: true }), async t => await prepareToGoLive(); await clickGoLive(); await waitForSettingsWindowLoaded(); + + // because streaming cannot be tested, check that Kick can be toggled on await fillForm({ kick: true, }); await waitForSettingsWindowLoaded(); - await waitForDisplayed('div[data-name="kick-settings"]'); - await fillForm({ - title: 'Test stream', - twitchGame: 'Fortnite', - }); - await submit(); - await waitForDisplayed('span=Update settings for Kick'); - await waitForStreamStart(); - await stopStream(); t.pass(); });