WIP Twitch Event Rules List

This commit is contained in:
Brandon Thomas committed 2022-02-22 00:37:23 -05:00
1 parent c959ca9fe6
commit 7af2c8ee46
12 files changed
+220 -2

No files matched your search

+24 -1
View File
@@ -20,6 +20,7 @@ enum Domain {
JungleHorse,
Vocodes,
FakeYou,
Storyteller,
Unknown,
}
@@ -47,10 +48,14 @@ class ApiConfig {
domain = Domain.Vocodes;
} else if (document.location.host.includes("fakeyou.com")) {
domain = Domain.FakeYou;
} else if (document.location.host.includes("storyteller.io")) {
domain = Domain.Storyteller;
}
let v2ApiHost = "api.fakeyou.com";
if (!useSsl && (domain === Domain.Localhost || domain === Domain.JungleHorse)) {
if (domain === Domain.Storyteller) {
v2ApiHost = "api.storyteller.io";
} else if (!useSsl && (domain === Domain.Localhost || domain === Domain.JungleHorse)) {
// NB: Lack of SSL means use local development.
v2ApiHost = "api.jungle.horse";
}
@@ -342,6 +347,24 @@ class ApiConfig {
return `${this.getScheme()}://${this.getNewApiHost()}/detect_locale`;
}
// =============== Storyteller-specific ===============
listTwitchEventRules() : string {
return `${this.getScheme()}://${this.getNewApiHost()}/twitch/event_rule/list`;
}
createTwitchEventRule() : string {
return `${this.getScheme()}://${this.getNewApiHost()}/twitch/event_rule/create`;
}
deleteTwitchEventRule(eventRuleToken: string) : string {
return `${this.getScheme()}://${this.getNewApiHost()}/twitch/event_rule/delete/${eventRuleToken}`;
}
editTwitchEventRule(eventRuleToken: string) : string {
return `${this.getScheme()}://${this.getNewApiHost()}/twitch/event_rule/update/${eventRuleToken}`;
}
obsEventsWebsocket(twitchUsername: string) : string {
return `ws://localhost:54321/obs/${twitchUsername}`;
//return `wss://obs.storyteller.io/obs/${twitchUsername}`;
@@ -0,0 +1,62 @@
import { ApiConfig } from "../../ApiConfig";
import { EventMatchPredicate } from "./shared/EventMatchPredicate";
import { EventResponse } from "./shared/EventResponse";
import { TwitchEventCategory } from "./shared/TwitchEventCategory";
export interface ListTwitchEventRulesSuccessResponse {
success: boolean,
twitch_event_rules: Array<TwitchEventRule>
}
export interface TwitchEventRule{
token: string,
event_category: TwitchEventCategory,
event_match_predicate: EventMatchPredicate,
event_response: EventResponse,
user_specified_rule_order: number,
rule_is_disabled: boolean,
created_at: Date,
updated_at: Date,
}
export interface ListTwitchEventRulesErrorResponse {
success: boolean,
}
type ListTwitchEventRulesResponse = ListTwitchEventRulesSuccessResponse | ListTwitchEventRulesErrorResponse;
export function ListTwitchEventRulesIsOk(response: ListTwitchEventRulesResponse): response is ListTwitchEventRulesSuccessResponse {
return response?.success === true;
}
export function ListTwitchEventRulesIsError(response: ListTwitchEventRulesResponse): response is ListTwitchEventRulesErrorResponse {
return response?.success === false;
}
export async function ListTwitchEventRules() : Promise<ListTwitchEventRulesResponse>
{
const endpoint = new ApiConfig().listTwitchEventRules();
return await fetch(endpoint, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include',
})
.then(res => res.json())
.then(res => {
if (!res) {
return { success : false }; // TODO: This loses error semantics and is deprecated
}
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -0,0 +1,34 @@
export interface NotSet {
};
export interface BitsCheermoteNameExactMatch {
cheermote_name: string,
};
export interface BitsCheermotePrefixSpendThreshold {
cheermote_name_prefix: string,
minimum_bits_spent: number,
};
export interface BitsSpendThreshold {
minimum_bits_spent: number,
}
export interface ChannelPointsRewardNameExactMatch {
reward_name: string,
}
// The predicate will have one field set exclusively.
export interface EventMatchPredicate {
// Empty
not_set?: NotSet,
// Bits
bits_cheermote_name_exact_match?: BitsCheermoteNameExactMatch,
bits_spend_threshold?: BitsSpendThreshold,
bits_cheermote_prefix_spend_threshold?: BitsCheermotePrefixSpendThreshold,
// Channel Points
channel_points_reward_name_exact_match?: ChannelPointsRewardNameExactMatch,
}
@@ -0,0 +1,22 @@
export interface NotSet {
};
export interface TtsSingleVoice {
tts_model_token: string,
};
export interface TtsRandomVoice {
tts_model_tokens: string[],
};
// The response will have one field set exclusively.
export interface EventResponse {
// Empty
not_set?: NotSet,
// Respond with TTS
tts_single_voice?: TtsSingleVoice,
tts_random_voice?: TtsRandomVoice,
}
@@ -0,0 +1,6 @@
export enum TwitchEventCategory {
Bits = "bits",
ChannelPoints = "channel_points",
ChatCommand = "chat_command",
};
+12
View File
@@ -8,6 +8,7 @@ import i18n from 'i18next';
import { ComingSoonPage } from './pages/coming-soon/ComingSoonPage';
import { LoginPage } from './pages/login/LoginPage';
import { SignupPage } from './pages/signup/SignupPage';
import { TtsConfigsIndexPage } from './pages/tts_configs/TtsConfigsIndexPage';
import { DetectLocale, DetectLocaleIsOk } from '@storyteller/components/src/api/locale/DetectLocale';
import { IndexPage } from './pages/index/IndexPage';
import { ObsLayerPage } from './pages/obs-layer/ObsLayerPage';
@@ -15,6 +16,7 @@ import { STORYTELLER_MERGED_TRANSLATIONS } from './_i18n/StorytellerTranslations
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { TopNav } from './layout/TopNav';
import { initReactI18next } from 'react-i18next';
import { TtsConfigsCreateRulePage } from './pages/tts_configs/TtsConfigsCreateRulePage';
i18n
.use(initReactI18next) // passes i18n down to react-i18next
@@ -97,6 +99,16 @@ class App extends React.Component<Props, State> {
querySessionAction={this.querySession}
/>
</Route>
<Route exact={true} path="/tts_configs/create">
<TtsConfigsCreateRulePage
sessionWrapper={this.state.sessionWrapper}
/>
</Route>
<Route exact={true} path="/tts_configs">
<TtsConfigsIndexPage
sessionWrapper={this.state.sessionWrapper}
/>
</Route>
<Route path="/obs/:username">
<ObsLayerPage />
</Route>
+6 -1
View File
@@ -45,7 +45,12 @@ function TopNav(props: Props) {
<Link to="/stream"
className="navbar-item"
onClick={() => closeHamburger()}
>Stream TTS</Link>
>Stream TTS (OLD)</Link>
<Link to="/tts_configs"
className="navbar-item"
onClick={() => closeHamburger()}
>TTS Configs (NEW)</Link>
<Link to="/coming-soon"
className="navbar-item"
@@ -0,0 +1,16 @@
import React from 'react';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
interface Props {
sessionWrapper: SessionWrapper,
}
function TtsConfigsCreateRulePage(props: Props) {
return (
<div>
<h1> TTS : Create Rule </h1>
</div>
)
}
export { TtsConfigsCreateRulePage }
@@ -0,0 +1,37 @@
import React, { useCallback, useEffect, useState } from 'react';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { ListTwitchEventRules, ListTwitchEventRulesIsError, ListTwitchEventRulesIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules';
interface Props {
sessionWrapper: SessionWrapper,
}
function TtsConfigsIndexPage(props: Props) {
const [twitchEventRules, setTwitchEventRules] = useState<TwitchEventRule[]>([]);
const listTwitchEventRules = useCallback(async () => {
const response = await ListTwitchEventRules();
if (ListTwitchEventRulesIsOk(response)) {
setTwitchEventRules(response.twitch_event_rules);
} else if (ListTwitchEventRulesIsError(response)) {
// TODO
}
}, []);
useEffect(() => {
listTwitchEventRules();
}, [listTwitchEventRules]);
if (!props.sessionWrapper.isLoggedIn()) {
return <h1>Must Log In</h1>;
}
return (
<div>
<h1> TTS Setup </h1>
</div>
)
}
export { TtsConfigsIndexPage }