mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
feat: WIP recent events
This commit is contained in:
parent
5f4a909017
commit
f9db1475b8
2 changed files with 560 additions and 24 deletions
468
frontend/src/lib/eventSub.ts
Normal file
468
frontend/src/lib/eventSub.ts
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
export enum EventSubNotificationType {
|
||||||
|
ChannelUpdated = 'channel.update',
|
||||||
|
UserUpdated = 'user.update',
|
||||||
|
Cheered = 'channel.cheer',
|
||||||
|
Raided = 'channel.raid',
|
||||||
|
CustomRewardAdded = 'channel.channel_points_custom_reward.add',
|
||||||
|
CustomRewardRemoved = 'channel.channel_points_custom_reward.remove',
|
||||||
|
CustomRewardUpdated = 'channel.channel_points_custom_reward.update',
|
||||||
|
CustomRewardRedemptionAdded = 'channel.channel_points_custom_reward_redemption.add',
|
||||||
|
CustomRewardRedemptionUpdated = 'channel.channel_points_custom_reward_redemption.update',
|
||||||
|
Followed = 'channel.follow',
|
||||||
|
GoalBegan = 'channel.goal.begin',
|
||||||
|
GoalEnded = 'channel.goal.end',
|
||||||
|
GoalProgress = 'channel.goal.progress',
|
||||||
|
HypeTrainBegan = 'channel.hype_train.begin',
|
||||||
|
HypeTrainEnded = 'channel.hype_train.end',
|
||||||
|
HypeTrainProgress = 'channel.hype_train.progress',
|
||||||
|
ModeratorAdded = 'channel.moderator.add',
|
||||||
|
ModeratorRemoved = 'channel.moderator.remove',
|
||||||
|
PollBegan = 'channel.poll.begin',
|
||||||
|
PollEnded = 'channel.poll.end',
|
||||||
|
PollProgress = 'channel.poll.progress',
|
||||||
|
PredictionBegan = 'channel.prediction.begin',
|
||||||
|
PredictionEnded = 'channel.prediction.end',
|
||||||
|
PredictionLocked = 'channel.prediction.lock',
|
||||||
|
PredictionProgress = 'channel.prediction.progress',
|
||||||
|
StreamWentOffline = 'stream.offline',
|
||||||
|
StreamWentOnline = 'stream.online',
|
||||||
|
Subscription = 'channel.subscribe',
|
||||||
|
SubscriptionEnded = 'channel.subscription.end',
|
||||||
|
SubscriptionGifted = 'channel.subscription.gift',
|
||||||
|
SubscriptionWithMessage = 'channel.subscription.message',
|
||||||
|
ViewerBanned = 'channel.ban',
|
||||||
|
ViewerUnbanned = 'channel.unban',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventSubSubscription {
|
||||||
|
id: string;
|
||||||
|
type: EventSubNotificationType;
|
||||||
|
version: string;
|
||||||
|
status: string;
|
||||||
|
created_at: string;
|
||||||
|
cost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventSubNotification {
|
||||||
|
subscription: EventSubSubscription;
|
||||||
|
event: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unwrapEvent = (message: EventSubNotification) =>
|
||||||
|
({
|
||||||
|
type: message.subscription.type,
|
||||||
|
subscription: message.subscription,
|
||||||
|
event: message.event,
|
||||||
|
} as EventSubMessage);
|
||||||
|
|
||||||
|
interface TypedEventSubNotification<
|
||||||
|
T extends EventSubNotificationType,
|
||||||
|
Payload,
|
||||||
|
> {
|
||||||
|
type: T;
|
||||||
|
subscription: EventSubSubscription;
|
||||||
|
event: Payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventSubMessage =
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.ChannelUpdated,
|
||||||
|
ChannelUpdatedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.UserUpdated,
|
||||||
|
UserUpdatedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<EventSubNotificationType.Cheered, CheerEventData>
|
||||||
|
| TypedEventSubNotification<EventSubNotificationType.Raided, RaidEventData>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.CustomRewardAdded,
|
||||||
|
ChannelRewardEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.CustomRewardRemoved,
|
||||||
|
ChannelRewardEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.CustomRewardUpdated,
|
||||||
|
ChannelRewardEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.CustomRewardRedemptionAdded,
|
||||||
|
ChannelRedemptionEventData<false>
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.CustomRewardRedemptionUpdated,
|
||||||
|
ChannelRedemptionEventData<true>
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.Followed,
|
||||||
|
FollowEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<EventSubNotificationType.GoalBegan, GoalEventData>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.GoalEnded,
|
||||||
|
GoalEndedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.GoalProgress,
|
||||||
|
GoalEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.HypeTrainBegan,
|
||||||
|
HypeTrainEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.HypeTrainProgress,
|
||||||
|
HypeTrainEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.HypeTrainEnded,
|
||||||
|
HypeTrainEndedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.ModeratorAdded,
|
||||||
|
ModeratorEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.ModeratorRemoved,
|
||||||
|
ModeratorEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.PollBegan,
|
||||||
|
PollEventData<false>
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.PollProgress,
|
||||||
|
PollEventData<true>
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.PollEnded,
|
||||||
|
PollEndedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.PredictionBegan,
|
||||||
|
PredictionEventData<false>
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.PredictionProgress,
|
||||||
|
PredictionEventData<true>
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.PredictionLocked,
|
||||||
|
PredictionLockedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.PredictionEnded,
|
||||||
|
PredictionEndedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.StreamWentOffline,
|
||||||
|
StreamEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.StreamWentOnline,
|
||||||
|
StreamWentOnlineEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.Subscription,
|
||||||
|
SubscriptionEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.SubscriptionEnded,
|
||||||
|
SubscriptionEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.SubscriptionGifted,
|
||||||
|
SubscriptionGiftedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.SubscriptionWithMessage,
|
||||||
|
SubscriptionMessageEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.ViewerBanned,
|
||||||
|
UserBannedEventData
|
||||||
|
>
|
||||||
|
| TypedEventSubNotification<
|
||||||
|
EventSubNotificationType.ViewerUnbanned,
|
||||||
|
UserUnbannedEventData
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface StreamEventData {
|
||||||
|
broadcaster_user_id: string;
|
||||||
|
broadcaster_user_login: string;
|
||||||
|
broadcaster_user_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StreamWentOnlineEventData extends StreamEventData {
|
||||||
|
id: string;
|
||||||
|
type: 'live' | 'playlist' | 'watch_party' | 'premiere' | 'rerun';
|
||||||
|
started_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Optional<field extends string, Extra> =
|
||||||
|
| ({ [key in field]: true } & Extra)
|
||||||
|
| { [key in field]: false };
|
||||||
|
|
||||||
|
type UserBannedEventData = StreamEventData & {
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
moderator_user_id: string;
|
||||||
|
moderator_user_login: string;
|
||||||
|
moderator_user_name: string;
|
||||||
|
reason: string;
|
||||||
|
banned_at: string;
|
||||||
|
} & Optional<'is_permanent', { ends_at: string }>;
|
||||||
|
|
||||||
|
export interface UserUnbannedEventData {
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
moderator_user_id: string;
|
||||||
|
moderator_user_login: string;
|
||||||
|
moderator_user_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelUpdatedEventData extends StreamEventData {
|
||||||
|
title: string;
|
||||||
|
language: string;
|
||||||
|
category_id: string;
|
||||||
|
category_name: string;
|
||||||
|
is_mature: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FollowEventData extends StreamEventData {
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
followed_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserUpdatedEventData {
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
email?: string;
|
||||||
|
email_verified: boolean;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheerEventData extends StreamEventData {
|
||||||
|
is_anonymous: boolean;
|
||||||
|
user_id: string | null;
|
||||||
|
user_login: string | null;
|
||||||
|
user_name: string | null;
|
||||||
|
message: string;
|
||||||
|
bits: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RaidEventData {
|
||||||
|
from_broadcaster_user_id: string;
|
||||||
|
from_broadcaster_user_login: string;
|
||||||
|
from_broadcaster_user_name: string;
|
||||||
|
to_broadcaster_user_id: string;
|
||||||
|
to_broadcaster_user_login: string;
|
||||||
|
to_broadcaster_user_name: string;
|
||||||
|
viewers: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelRewardEventData extends StreamEventData {
|
||||||
|
id: string;
|
||||||
|
is_enabled: boolean;
|
||||||
|
is_paused: boolean;
|
||||||
|
is_in_stock: boolean;
|
||||||
|
title: string;
|
||||||
|
cost: number;
|
||||||
|
prompt: string;
|
||||||
|
is_user_input_required: boolean;
|
||||||
|
should_redemptions_skip_request_queue: boolean;
|
||||||
|
cooldown_expires_at: string | null;
|
||||||
|
redemptions_redeemed_current_stream: number | null;
|
||||||
|
max_per_stream: Optional<'is_enabled', { value: number }>;
|
||||||
|
max_per_user_per_stream: Optional<'is_enabled', { value: number }>;
|
||||||
|
global_cooldown: Optional<'is_enabled', { seconds: number }>;
|
||||||
|
background_color: string;
|
||||||
|
image: {
|
||||||
|
url_1x: string;
|
||||||
|
url_2x: string;
|
||||||
|
url_4x: string;
|
||||||
|
} | null;
|
||||||
|
default_image: {
|
||||||
|
url_1x: string;
|
||||||
|
url_2x: string;
|
||||||
|
url_4x: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelRedemptionEventData<Updated extends boolean>
|
||||||
|
extends StreamEventData {
|
||||||
|
id: string;
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
user_input: string;
|
||||||
|
status: Updated extends true
|
||||||
|
? 'fulfilled' | 'canceled'
|
||||||
|
: 'unfulfilled' | 'unknown' | 'fulfilled' | 'canceled';
|
||||||
|
reward: ChannelRewardEventData;
|
||||||
|
redeemed_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GoalEventData extends StreamEventData {
|
||||||
|
id: string;
|
||||||
|
type: 'follower' | 'subscription';
|
||||||
|
description: string;
|
||||||
|
current_amount: number;
|
||||||
|
target_amount: number;
|
||||||
|
started_at: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GoalEndedEventData extends GoalEventData {
|
||||||
|
is_achieved: boolean;
|
||||||
|
ended_at: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HypeTrainContribution {
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
type: 'bits' | 'subscription' | 'other';
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HypeTrainBaseData extends StreamEventData {
|
||||||
|
id: string;
|
||||||
|
level: number;
|
||||||
|
total: number;
|
||||||
|
top_contributions:
|
||||||
|
| [HypeTrainContribution]
|
||||||
|
| [HypeTrainContribution, HypeTrainContribution]
|
||||||
|
| null;
|
||||||
|
started_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HypeTrainEventData extends HypeTrainBaseData {
|
||||||
|
progress: number;
|
||||||
|
goal: number;
|
||||||
|
last_contribution: HypeTrainContribution;
|
||||||
|
expires_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HypeTrainEndedEventData extends HypeTrainBaseData {
|
||||||
|
ended_at: string;
|
||||||
|
cooldown_ends_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModeratorEventData extends StreamEventData {
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PollBaseData<Running extends boolean> extends StreamEventData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
choices: Running extends true
|
||||||
|
? {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
bits_votes: number;
|
||||||
|
channel_points_votes: number;
|
||||||
|
votes: number;
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
bits_voting: Optional<'is_enabled', { amount_per_vote: number }>;
|
||||||
|
channel_points_voting: Optional<'is_enabled', { amount_per_vote: number }>;
|
||||||
|
started_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PollEventData<Running extends boolean>
|
||||||
|
extends PollBaseData<Running> {
|
||||||
|
started_at: string;
|
||||||
|
ends_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PollEndedEventData extends PollBaseData<true> {
|
||||||
|
status: 'completed' | 'archived' | 'terminated';
|
||||||
|
ended_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PredictionColor = 'blue' | 'pink';
|
||||||
|
interface Outcome<Color extends PredictionColor> {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
color: Color;
|
||||||
|
}
|
||||||
|
interface RunningOutcome<Color extends PredictionColor> extends Outcome<Color> {
|
||||||
|
users: number;
|
||||||
|
channel_points: number;
|
||||||
|
top_predictors: {
|
||||||
|
user_name: string;
|
||||||
|
user_login: string;
|
||||||
|
user_id: string;
|
||||||
|
channel_points_won: number | null;
|
||||||
|
channel_points_used: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnorderedTuple<A, B> = [A, B] | [B, A];
|
||||||
|
|
||||||
|
interface PredictionBaseData<Running extends boolean> extends StreamEventData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
started_at: string;
|
||||||
|
outcomes: Running extends true
|
||||||
|
? UnorderedTuple<RunningOutcome<'blue'>, RunningOutcome<'pink'>>
|
||||||
|
: UnorderedTuple<Outcome<'blue'>, Outcome<'pink'>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PredictionEventData<Running extends boolean>
|
||||||
|
extends PredictionBaseData<Running> {
|
||||||
|
locks_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PredictionLockedEventData extends PredictionBaseData<true> {
|
||||||
|
locked_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PredictionEndedEventData extends PredictionBaseData<true> {
|
||||||
|
winning_outcome_id: string | null;
|
||||||
|
status: 'resolved' | 'canceled';
|
||||||
|
ended_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubscriptionBaseData extends StreamEventData {
|
||||||
|
user_id: string;
|
||||||
|
user_login: string;
|
||||||
|
user_name: string;
|
||||||
|
tier: '1000' | '2000' | '3000';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionEventData extends SubscriptionBaseData {
|
||||||
|
is_gift: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionGiftedEventData extends SubscriptionBaseData {
|
||||||
|
total: number;
|
||||||
|
cumulative_total: number | null;
|
||||||
|
is_anonymous: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionMessageEventData extends SubscriptionBaseData {
|
||||||
|
message: {
|
||||||
|
text: string;
|
||||||
|
emotes: {
|
||||||
|
begin: number;
|
||||||
|
end: number;
|
||||||
|
id: string;
|
||||||
|
}[]; // Oh god not this again
|
||||||
|
};
|
||||||
|
cumulative_months: number;
|
||||||
|
streak_months: number | null;
|
||||||
|
duration_months: number;
|
||||||
|
}
|
|
@ -2,9 +2,15 @@ import { CircleIcon } from '@radix-ui/react-icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLiveKey } from '~/lib/react';
|
import { useLiveKey } from '~/lib/react';
|
||||||
|
import {
|
||||||
|
EventSubNotification,
|
||||||
|
EventSubNotificationType,
|
||||||
|
unwrapEvent,
|
||||||
|
} from '~/lib/eventSub';
|
||||||
import { PageContainer, SectionHeader, styled, TextBlock } from '../theme';
|
import { PageContainer, SectionHeader, styled, TextBlock } from '../theme';
|
||||||
import WIPNotice from '../components/utils/WIPNotice';
|
import WIPNotice from '../components/utils/WIPNotice';
|
||||||
import BrowserLink from '../components/BrowserLink';
|
import BrowserLink from '../components/BrowserLink';
|
||||||
|
import Scrollbar from '../components/utils/Scrollbar';
|
||||||
|
|
||||||
interface StreamInfo {
|
interface StreamInfo {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -65,41 +71,103 @@ const Darken = styled(BrowserLink, {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const EventListContainer = styled('div', {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '5px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const TwitchEventContainer = styled('div', {
|
||||||
|
background: '$gray3',
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '5px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const supportedMessages: EventSubNotificationType[] = [
|
||||||
|
EventSubNotificationType.Followed,
|
||||||
|
];
|
||||||
|
|
||||||
|
function TwitchEvent({ data }: { data: EventSubNotification }) {
|
||||||
|
let content: JSX.Element | string;
|
||||||
|
const message = unwrapEvent(data);
|
||||||
|
switch (message.type) {
|
||||||
|
case EventSubNotificationType.Followed: {
|
||||||
|
content = `${message.event.user_name} followed you!`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
content = <small>{message.type}</small>;
|
||||||
|
}
|
||||||
|
return <TwitchEventContainer>{content}</TwitchEventContainer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TwitchEventLog({ events }: { events: EventSubNotification[] }) {
|
||||||
|
// TODO Include a note specifying that it's not ALL events!!
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SectionHeader>Latest events</SectionHeader>
|
||||||
|
<Scrollbar vertical={true} viewport={{ maxHeight: '200px' }}>
|
||||||
|
<EventListContainer>
|
||||||
|
{events
|
||||||
|
.filter((ev) => supportedMessages.includes(ev.subscription.type))
|
||||||
|
.map((ev) => (
|
||||||
|
<TwitchEvent
|
||||||
|
key={`${ev.subscription.id}-${ev.subscription.created_at}`}
|
||||||
|
data={ev}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</EventListContainer>
|
||||||
|
</Scrollbar>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TwitchStreamStatus({ info }: { info: StreamInfo }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<StreamBlock>
|
||||||
|
<LiveIndicator
|
||||||
|
css={{
|
||||||
|
backgroundImage: `url(${info.thumbnail_url
|
||||||
|
.replace('{width}', '160')
|
||||||
|
.replace('{height}', '90')})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Darken target="_blank" href={`https://twitch.tv/${info.user_login}`}>
|
||||||
|
<CircleIcon /> {t('pages.dashboard.live')}
|
||||||
|
</Darken>
|
||||||
|
</LiveIndicator>
|
||||||
|
<StreamTitle>{info.title}</StreamTitle>
|
||||||
|
<StreamInfo>
|
||||||
|
{info.game_name} -{' '}
|
||||||
|
{t('pages.dashboard.x-viewers', {
|
||||||
|
count: info.viewer_count,
|
||||||
|
})}
|
||||||
|
</StreamInfo>
|
||||||
|
</StreamBlock>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function TwitchSection() {
|
function TwitchSection() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const twitchInfo = useLiveKey<StreamInfo[]>('twitch/stream-info');
|
const twitchInfo = useLiveKey<StreamInfo[]>('twitch/stream-info');
|
||||||
// const twitchActivity = useLiveKey<StreamInfo[]>('twitch/chat-activity');
|
// const twitchActivity = useLiveKey<StreamInfo[]>('twitch/chat-activity');
|
||||||
|
const twitchEvents = useLiveKey<EventSubNotification[]>(
|
||||||
|
'twitch/eventsub-history',
|
||||||
|
);
|
||||||
|
console.log(twitchEvents);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SectionHeader>{t('pages.dashboard.twitch-status')}</SectionHeader>
|
<SectionHeader spacing="none">
|
||||||
|
{t('pages.dashboard.twitch-status')}
|
||||||
|
</SectionHeader>
|
||||||
{twitchInfo && twitchInfo.length > 0 ? (
|
{twitchInfo && twitchInfo.length > 0 ? (
|
||||||
<StreamBlock>
|
<TwitchStreamStatus info={twitchInfo[0]} />
|
||||||
<LiveIndicator
|
|
||||||
css={{
|
|
||||||
backgroundImage: `url(${twitchInfo[0].thumbnail_url
|
|
||||||
.replace('{width}', '160')
|
|
||||||
.replace('{height}', '90')})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Darken
|
|
||||||
target="_blank"
|
|
||||||
href={`https://twitch.tv/${twitchInfo[0].user_login}`}
|
|
||||||
>
|
|
||||||
<CircleIcon /> {t('pages.dashboard.live')}
|
|
||||||
</Darken>
|
|
||||||
</LiveIndicator>
|
|
||||||
<StreamTitle>{twitchInfo[0].title}</StreamTitle>
|
|
||||||
<StreamInfo>
|
|
||||||
{twitchInfo[0].game_name} -{' '}
|
|
||||||
{t('pages.dashboard.x-viewers', {
|
|
||||||
count: twitchInfo[0].viewer_count,
|
|
||||||
})}
|
|
||||||
</StreamInfo>
|
|
||||||
</StreamBlock>
|
|
||||||
) : (
|
) : (
|
||||||
<TextBlock>{t('pages.dashboard.not-live')}</TextBlock>
|
<TextBlock>{t('pages.dashboard.not-live')}</TextBlock>
|
||||||
)}
|
)}
|
||||||
|
{twitchEvents ? <TwitchEventLog events={twitchEvents} /> : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue