ChatBot (wymagany)
Aby framework działał poprawnie, musisz dostarczyć trzy kluczowe providery - możesz przekazać je jako klasę bądź instancję klasy, która implementuje wymagany interfejs.
export type Provider<T> = {
token: string;
useClass?: new (...args: any[]) => T;
useValue?: T;
}
type ClassOrValue<T> = Omit<Provider<T>, 'token'>;
ChatBotModule.forRoot({
listenChannels: ClassOrValue<IListenChannelsProvider>,
channelOptions: ClassOrValue<IChannelOptionsProvider<any>>,
tokenRepository: ClassOrValue<TokenRepositoryProvider>
}),
1. ListenChannelsProvider – Definiowanie kanałów do nasłuchiwania
Musisz określić, które kanały Twitcha bot ma monitorować. W tym celu tworzysz własny provider implementujący IListenChannelsProvider
Wymagane metody:
getChannelIds()
– zwraca listę identyfikatorów kanałów (string[]
lubPromise<string[]>
)getRefreshInterval()
– określa, jak często lista kanałów ma być odświeżana (w milisekundach)
/* Wymagany interfejs */
interface IListenChannelsProvider {
getChannelIds(): Promise<string[]> | string[];
getRefreshInterval(): number;
}
💡 Przykład: Jeśli chcesz, aby bot nasłuchiwał dwóch kanałów i odświeżał listę co 60 sekund, Twój provider może wyglądać tak:
class MyListenChannelsProvider implements IListenChannelsProvider {
getChannelIds(): string[] {
return ['123456', '7891011'];
}
getRefreshInterval(): number {
return 60000; // 60 sekund
}
}
Metoda getChannelIds()
może korzystać również z zewnętrznych źródeł takich jak: lokalny plik, endpoint, baza danych - jedynym wymogiem jest zwrócenie identyfikatorów użytkowników na platformie twitch.
2. ChannelOptionsProvider – Konfiguracja kanałów
Provider ma domyślnie ustawione pole prefix
dla komend bota, ale możesz dodać dodatkowe opcje, np. zapisywanie czasu ostatniej wiadomości. Tworzysz własny provider implementujący IChannelOptionsProvider<Extend>
, gdzie Extend
zawiera dodatkowe ustawienia dla kanału.
Wymagane metody:
getOptions(channelId: string)
– pobiera konfigurację dla danego kanału.setOptions(channelId: string, options: TChannelOptions<Extend>)
– pozwala zmienić konfigurację kanału.
/* Typy danych */
type ChannelBaseOptions = {
prefix: string;
}
type TChannelOptions<Extend extends Record<string, any>> = ChannelBaseOptions & Extend;
type TorPromiseT<T> = T | Promise<T>;
/* Wymagany interfejs */
interface IChannelOptionsProvider<Extend extends Record<string, any>> {
getOptions(channelId: string): TorPromiseT<TChannelOptions<Extend>>;
setOptions(channelId: string, options: TChannelOptions<Extend>): TorPromiseT<void>;
}
💡 Przykład: Jeśli chcesz dodać opcję przechowywania czasu ostatniej wiadomości (lastMessageTimestamp
):
interface MyChannelOptions {
lastMessageTimestamp: number;
}
class MyChannelOptionsProvider implements IChannelOptionsProvider<MyChannelOptions> {
private readonly baseOptions: ChannelOptionsExtend = {
prefix: '!',
lastMessageTimestamp: 0,
};
private readonly changedOptions = new Map<string, ChannelOptionsExtend>();
async getOptions(channelId: string): Promise<ChannelOptionsExtend> {
if (this.changedOptions.has(channelId)) {
return this.changedOptions.get(channelId) as ChannelOptionsExtend;
}
return this.baseOptions;
}
async setOptions(channelId: string, options: ChannelOptionsExtend): Promise<void> {
this.changedOptions.set(channelId, options);
}
}
Identycznie jak w poprzednim przypadku - getOptions
oraz setOptions
mogą wykorzystywać zewnętrzne źródło danych (np.: odczyt/zapis do/z bazy danych). W powyższym przykładzie konfiguracje przechowuje się w zmiennej changedOptions
. Pamiętaj jednak, że getOptions
powinno zawsze zwrócić wszystkie wymagane opcje (tutaj: gdy opcje nie zostały nadpisane, zwraca bazowe ustawienie).
Pole prefix
będzie później wykorzystywane do wywoływania komend z czatu (każdy czat może mieć inny prefix. Prefix można zmienić w czasie działania programu - patrz: !prefix)
3. TokenRepositoryProvider – Obsługa tokenów autoryzacyjnych
Bot wymaga tokenów dostępu do API Twitcha. Musisz dostarczyć własny provider implementujący ITokenRepositoryProvider
, który zarządza przechowywaniem i aktualizacją tokenów.
Wymagane metody:
Pobieranie i zapisywanie tokenów aplikacji:
getAppToken()
: Pozyskanie tokenu dostępu dla aplikacjisaveAppToken(token)
: Zapisanie tokenu dostępu dla aplikacji
UWAGA!: Gdy
getAppToken()
zwrócinull
framework sam wyśle żądanie o ten token oraz zapisze go przy użyciusaveAppToken(token)
. Nie musisz wysyłać żadnych żądań manualnie o token.Pobieranie, zapisywanie i usuwanie tokenów użytkownika:
getUserAccessToken(userId)
: Pozyskiwanie tokenu dostępu dla konkretnego użytkownikasaveUserAccessToken(userId, token)
: Zapisywanie tokenu dostępu dla konkretnego użytkownikaremoveUserAccessToken(userId)
: Usuwanie tokenu dostępu dla konkretnego użytkownika
UWAGA!: Podobnie jak w powyższym przypadku dla tokenów dostępu dla aplikacji - powyższe metody służą do obsługi "pamięci" - jeżeli
getUserAccessToken(userId)
zwrócinull
- wykorzystana zostanie poniższa metodagetUserRefreshToken(userId)
w celu pozyskania tokena dostępu dla użytkownika, a następniesaveUserAccessToken(userId, token)
w celu zapisania tokena (token dostępu ważny jest przez około 15 minut od wygenerowania, po tym czasie zostanie automatycznie usunięty - metodaremoveUserAccessToken(userId)
). Jednak jeżeligetUserRefreshToken(userId)
zwrócinull
- pozyskanie tokenu dostępu będzie niemożliwe - żądana akcja (np.: żądanie API, inne działanie wymagające tokenu dostępu tego użytkownika) zakończy się niepowodzeniem.Pobieranie i usuwanie refresh tokenów:
getUserRefreshToken(userId)
: Pozyskiwanie tokenu odświeżającego token dostępowy dla konkretnego użytkownika.removeUserRefreshToken(userId)
: Usuwanie tokenu odświeżającego token dostępowy dla konkretnego użytkownika.
UWAGA!: Metoda
removeUserRefreshToken(userId)
wywoływana jest w momencie, gdy żądanie o token dostępu dla tego użytkownika zakończy się niepowodzeniem (możliwe powody: token jest nieprawidłowy, użytkownik cofnął autoryzacje aplikacji, inne). UWAGA 2!: MetodagetUserRefreshToken(userId)
dla userId, które odpowiada userId zdefiniowanemu w dekoratorze @TwitchBot powinien zawsze zwrócić token odświeżający. W innym przypadku większość funkcji bota będzie bezużyteczna.
/* Typy danych */
type TokenSaveTimestamp = {
savedAt: number;
}
export type AppToken = {
access_token: string;
expires_in: number;
} & TokenSaveTimestamp;
export type UserToken = {
access_token: string;
refresh_token: string;
expires_in: number;
scope: string[];
} & TokenSaveTimestamp;
/* Wymagany interfejs */
export interface ITokenRepositoryProvider {
// App / Client
getAppToken(): Promise<AppToken | null>;
saveAppToken(token: AppToken): Promise<void>;
// User - Access
getUserAccessToken(userId: string): Promise<UserToken | null>;
saveUserAccessToken(userId: string, token: UserToken): Promise<void>;
removeUserAccessToken(userId: string): Promise<void>;
// User - Refresh
getUserRefreshToken(userId: string): Promise<string | null>;
removeUserRefreshToken(userId: string): Promise<void>;
};
💡 Przykład: W projekcie została utworzona predefiniowana klasa InMemoryTokenRepository
, która implementuje wymagany interfejs i może być użyta, w przypadku gdy będziemy korzystać z interakcji, do których wymagany będzie token dostępowy aplikacji lub token dostępowy użytkownika (zdefiniowanego jako bot - userId).
import { AppToken, ITokenRepositoryProvider, UserToken } from "@tfxjs/tfxjs";
export default class InMemoryTokenRepository implements ITokenRepositoryProvider {
private logger: Logger;
private appAccessToken: AppToken | null = null;
private userAccessTokens: Map<string, UserToken> = new Map();
private userRefreshTokens: Map<string, string> = new Map();
constructor(userId: string, userRefreshToken: string) {
if(!userId || !userRefreshToken) {
const message = 'User ID and refresh token must be provided';
this.logger.error(message);
throw new Error(message);
}
this.userRefreshTokens.set(userId, userRefreshToken);
}
getAppToken(): Promise<AppToken | null> {
return Promise.resolve(this.appAccessToken);
}
saveAppToken(token: AppToken): Promise<void> {
this.appAccessToken = token;
return Promise.resolve();
}
getUserAccessToken(userId: string): Promise<UserToken | null> {
return Promise.resolve(this.userAccessTokens.get(userId) || null);
}
saveUserAccessToken(userId: string, token: UserToken): Promise<void> {
this.userAccessTokens.set(userId, token);
return Promise.resolve();
}
removeUserAccessToken(userId: string): Promise<void> {
this.userAccessTokens.delete(userId);
return Promise.resolve();
}
getUserRefreshToken(userId: string): Promise<string | null> {
return Promise.resolve(this.userRefreshTokens.get(userId) || null);
}
removeUserRefreshToken(userId: string): Promise<void> {
this.userRefreshTokens.delete(userId);
return Promise.resolve();
}
}
Aby użyć powyższej klasy w Twoim projekcie musisz utworzyć instancje tej klasy wraz z wymaganymi w konstruktorze argumentami (userRefreshToken
powinien być wygenerowany przy użyciu userId
). Następnie musisz przekazać instancję w opcjach modułu z użyciem pola useValue
ChatBotModule.forRoot({
/* 2 pozostałe providery */
tokenRepository: { useValue: new InMemoryTokenRepository(userId, userRefreshToken) },
})
Dla bardziej zaawansowanych przypadków (np.: korzystanie z tokenów odświeżających i dostępowych większej liczby użytkowników) należy zdefiniować tą klasę samemu. Krytycznie ważne metody dla takiego zastosowania to:
getUserRefreshToken(userId)
removeUserRefreshToken(userId)
Powinny one korzystać z zewnętrznego źródła, gdzie przechowywane są tokeny odświeżające (zapisane po pomyślnej autoryzacji użytkownika dla tej aplikacji).
class AdvancedTokenRepositoryProvider implements ITokenRepositoryProvider {
private appToken: AppToken | null = null;
private userTokens: Map<string, UserToken> = new Map();
async getAppToken(): Promise<AppToken | null> {
return this.appToken;
}
async saveAppToken(token: AppToken): Promise<void> {
this.appToken = token;
}
async getUserAccessToken(userId: string): Promise<UserToken | null> {
return this.userTokens.get(userId) || null;
}
async saveUserAccessToken(userId: string, token: UserToken): Promise<void> {
this.userTokens.set(userId, token);
}
async removeUserAccessToken(userId: string): Promise<void> {
this.userTokens.delete(userId);
}
async getUserRefreshToken(userId: string): Promise<string | null> {
// Pobieranie z: Zewnętrzne źródło tokenów odświeżających
}
async removeUserRefreshToken(userId: string): Promise<void> {
// Usuwanie z: Zewnętrzne źródło tokenów odświeżających
}
}
Identycznie jak w poprzednich przypadkach - wszystkie metody mogą wykorzystywać zewnętrzne źródła danych.
Last updated