frosty/lib/apis/twitch_api.dart
Tommy Chow 8f4ebde155
v5 (#463)
* migrate to dio

* ignore folders

* simplify twitch api headers handling

* clean up main

* simplify logs

* optimize profile picture fetching
fixes refetches on tab switch

* update claude md

* refactor irc.dart

* update copilot instructions with essential commands and project structure details

* implement message sending state management and UI updates

* refactor notification handling and add copy-paste functionality in chat

* refactor AlertMessage to support vertical layout and optional padding

* refactor error messages for consistency and clarity across various components

* refactor chat layout to use draggable divider and improve chat width settings

* refactor Uptime widget to use SharedTimerService for synchronized updates

* refactor search results components to handle null future states gracefully

* refactor loading indicators to use skeleton loaders for improved user experience

* refactor UI components for improved layout and responsiveness, enhance skeleton loaders, and streamline padding adjustments

* refactor: replace manual blur effects with BlurredContainer for consistency and reusability

* refactor: enhance JavaScript execution and error handling in WebView components

* refactor: replace app bar with blurred container for improved UI consistency across screens

* refactor: streamline video chat layout and enhance PiP drag gesture functionality

* revert some app bar changes

* refactor: implement Picture-in-Picture drag functionality in VideoChat

* refactor: pass currentChannelId to IRCMessage and ChatMessage for improved context

* fix large thubmnail width

* chore: update Gradle and Kotlin plugin versions for improved compatibility

* changes from v4.10.0 branch

* feat: integrate AnimatedScrollBorder for improved UI responsiveness across various screens

* refactor: update SectionHeader styling and improve theme management

* feat: add pinned section to StreamsList and enhance skeleton loader padding

* feat: add repository guidelines for project structure, coding style, testing, and security

* feat: enhance StreamCard UI with BlurredContainer and adjust border width in theme

* feat: implement unfocusInput and safeRequestFocus methods to manage text field focus behavior

* feat: enhance Picture-in-Picture drag interactions with haptic feedback and smooth animations

* refactor: improve code formatting and readability across multiple files

* feat: implement picture-in-picture mode toggle and state management for video playback

* feat: reset PiP mode state when pausing video playback

* feat: add custom app bar with profile picture and stream info for video chat

* feat: convert multiple settings screens to use StatefulWidget and add animated scroll borders

* feat: update blur configuration for adaptive alpha based on theme

* fix: adjust default font size in SectionHeader widget

* feat: enhance PiP mode with drag overlay instruction and UI improvements

* fix: improve badge rendering with consistent margin and border for current channel

* refactor: replace BackdropFilter with BlurredContainer for improved clarity and maintainability

* fix: reduce background transparency in Uptime widget for better visibility

* feat: enhance photo view dialog with full resolution toggle and blurred background

* fix: reset chat delay to 0 when auto sync is enabled and update settings title for clarity

* fix: adjust border width in theme to improve consistency

* feat: add forceDarkMode option to BlurredContainer for improved theme adaptability

* feat: implement offline followed channels feature with UI integration and settings option

* fix: increase font size and weight for offline status text for better readability

* feat: remove offline pinned channels feature and update related UI components

* feat: optimize offline channels loading behavior based on expansion state

* fix: improve clarity and consistency in repository guidelines formatting

* feat: update full resolution URL handling and button visibility logic

* feat: replace app bar with blurred version in chat-only mode and add animated scroll border

* feat: refactor video chat layout for improved chat and video display in landscape mode

* feat: remove bottom bar visibility setting and adjust chat layout accordingly

* fix: simplify condition for displaying offline channels section in following tab

* feat: enhance reply thread display with indentation and reply icon

* feat: implement showModalBottomSheetWithProperFocus for improved focus handling

* feat: add gradient direction support to BlurredContainer for enhanced visual effects

* feat: enhance NavigationBar and Theme settings for improved visual consistency

* feat: add gradient direction support to BlurredContainer in VideoChat for enhanced visual effects

* chore: update .gitignore to include .cursor file for better environment management

* fix: update PiP drag gesture conditions to include video paused state for better user experience

* refactor: replace ScrollController with SettingsPageLayout in settings screens for improved layout management

* refactor: centralize orientation handling by introducing OrientationUtils for improved readability and maintainability

* refactor: replace OrientationUtils with ContextExtensions for streamlined context-based access to theme, layout, and API dependencies

* refactor: improve video chat UI by adjusting title layout and adding stream info tooltips for better user experience

* refactor: adjust titleSpacing in theme and navigation components for improved layout consistency

* refactor: update stream info timer handling and overlay management for improved UI responsiveness

* refactor: enhance stream info timer logic and overlay visibility management for chat-only mode

* refactor: enhance input decoration theme in FrostyThemes for improved visual consistency and responsiveness

* refactor: adjust inputDecorationTheme padding in FrostyThemes for improved visual consistency

* refactor: enhance video overlay management by including video preview overlay in hideElements for improved UI consistency

* refactor: improve chat bottom bar padding and video chat layout for enhanced UI consistency

* refactor: adjust chat padding to account for device bottom insets for improved layout consistency

* refactor: update text styles in FrostyThemes for improved visual consistency across UI components

* refactor: update iOS minimum deployment target to 13.0 for improved compatibility

* refactor: adjust chat bottom padding based on emote menu visibility for improved layout consistency

* refactor: adjust padding values in StreamsList for improved layout consistency

* refactor: replace Padding with AnimatedPadding in Chat for smoother layout transitions
refactor: replace Container with AnimatedContainer in BlurredContainer for smoother animations

* refactor: remove unnecessary radius property from ProfilePicture in OfflineChannelCard for cleaner code

* refactor: enhance chat bottom bar suffix icon to display message delay with tooltip for improved user experience

* refactor: update hint text and tooltips in chat bottom bar and search for improved clarity

* refactor: simplify notification display logic and enhance chat input behavior for improved user experience

* refactor: enhance layout consistency by adjusting spacing in various components across the channel and home screens

* refactor: introduce LiveIndicator widget and adjust spacing in channel and video components for improved layout consistency

* refactor: remove monospace font family from chat bottom bar for improved text rendering

* refactor: reduce notification animation duration and change dismiss direction for improved user experience

* refactor: replace VideoBar with StreamInfoBar in video components for improved layout and consistency

* refactor: adjust font sizes and spacing in StreamInfoBar for improved layout consistency

* refactor: standardize font sizes and update gradient stops in video components for improved visual consistency

* refactor: update tooltip padding and background color in theme for improved visual consistency

* refactor: enhance tooltip behavior and standardize font sizes in StreamInfoBar and related components for improved visual consistency

* refactor: add showThumbnail option to LargeStreamCardSkeletonLoader for improved layout flexibility

* refactor: add StreamInfoBarSkeletonLoader for improved layout consistency in stream components

* refactor: add shadow effects to icons and text in StreamInfoBar and VideoOverlay for improved visual depth

* refactor: update defaultOverlayOpacity to 0.0 for improved overlay behavior

* fix: correct stream info text color on overlay

* refactor: implement a new color picker dialog with random color generation for enhanced user experience

* refactor: adjust gradient colors and stops in VideoOverlay for improved visual consistency

* refactor: refine gradient colors and stops in VideoOverlay for enhanced visual clarity

* refactor: add isCompact parameter to StreamInfoBar for responsive text sizing

* fix: update tooltip background color to use surface color for better contrast

* feat: add shared chat mode support in chat store and update StreamInfoBar for visual indication

* refactor: disable debug print for IRC messages to reduce log clutter

* feat: implement chat color selection feature with user color management in chat settings

* refactor: replace color adjustment logic with new contrast-aware utility for chat names

* refactor: simplify chat details UI by replacing list tiles with action cards and removing unused clear dialog

* refactor: enhance chat details UI by improving time formatting and adding label styling to action cards

* refactor: update chat color terminology to 'Username color' for clarity in UI

* refactor: enhance chat details UI by introducing active chat modes display and improving layout structure

* refactor: improve chat details layout by replacing ListTile with Padding and adding a Divider for better visual separation

* refactor: remove chat notifications positioning setting and adjust notification alignment in video chat

* refactor: streamline StreamInfoBar component by consolidating offline handling and improving layout for better readability

* refactor: enhance StreamInfoBar by adding tooltip for streamer name and improving text overflow handling

* refactor: adjust tooltip display duration in StreamInfoBar and update theme configuration

* refactor: adjust margin and padding in StreamCard for improved layout consistency

* chore: update dependencies in pubspec.yaml and pubspec.lock for Firebase and other packages

* template

* chore: update iOS platform version to 15.0 and Firebase dependencies to 12.2.0

* refactor: simplify StreamInfoBar layout by reducing nested widgets and improving text color handling

* format

* feat: enhance Channel model and StreamInfoBar to support offline channel details

* feat: add offline indicator and improve StreamInfoBar layout for better offline channel display

* fix: adjust icon color opacity in StreamInfoBar for improved visibility

* feat: enhance dismissal animation in FrostyPhotoViewDialog to track swipe direction

* docs: update copilot instructions for clarity and additional error handling details

* refactor: optimize latency tooltip layout and improve offline indicator styling in VideoOverlay

* feat: implement shared chat asset fetching and participant management in ChatAssetsStore

* refactor: replace Observer with Builder for improved widget rebuilding in VideoOverlay

* refactor: update StreamInfoBar border styling and adjust layout in VideoOverlay for improved visual consistency

* feat: add showTextShadows property to StreamInfoBar for customizable text shadow display

* refactor: adjust layout in VideoOverlay by adding padding to improve spacing and visual consistency

* refactor: enhance VideoOverlay gradients and layout for improved visual hierarchy and user experience

* refactor: reduce vertical padding in VideoOverlay for improved layout consistency

* refactor: remove overlay opacity settings from VideoSettings and SettingsStore for simplification

* refactor: improve text overlay transition in VideoChat for better user experience

* Revert "refactor: improve text overlay transition in VideoChat for better user experience"

This reverts commit fd16ff5ec3.

* refactor: wrap videoOverlay in ColoredBox for improved layout handling

* refactor: optimize animation duration and transition curves in VideoChat for smoother interactions

* refactor: dispose searchScrollController in HomeStoreBase to prevent memory leaks

* refactor: simplify gradient definitions in VideoOverlay for improved readability and maintainability

* refactor: remove 'Offline' text overlay from VideoOverlay for cleaner UI

* refactor: add refresh functionality for assets in ChatDetails with loading indicators

* refactor: update visibility conditions for uptime, viewer count, and category display in StreamInfoBar

* refactor: replace AlertDialog with FrostyDialog for consistent dialog styling across the application

* refactor: enhance layout handling in FrostyPageView to prevent overflow during height transitions

* refactor: replace ColorPickerDialog with AccentColorPickerDialog for improved color selection experience

* refactor: replace theme and accent color settings with dedicated components for improved modularity and readability

* refactor: replace ElevatedButton with TextButton for consistent button styling across dialogs

* refactor: integrate MobX Observer for reactive state management in settings widgets

* refactor: update theme and dialog components for improved styling and consistency

* refactor: enhance dialog styling with rounded corners and border in theme configuration

* refactor: improve dialog messages for unauthorized access handling

* refactor: update bodyLarge text style in theme configuration to include font weight

* refactor: standardize Divider usage and adjust theme styles for improved consistency

* refactor: adjust Divider properties and theme styles for enhanced visual consistency

* chore: remove GEMINI.md file as it is no longer needed for project documentation

* refactor: enhance token validation and reconnect logic in AuthBase for improved error handling

* format

* refactor: integrate landscape cutout settings into VideoChat layout for improved responsiveness

* refactor: implement app lifecycle handling in VideoChat and enhance stream info update logic in VideoStore

* refactor: update landscape cutout conditionals in VideoChat layout for improved responsiveness

* update AGENTS.md

* add scrollbar to search

* fix scrolling exception

* update CLAUDE.md

* 5 auth reconnect attemps then log out

* increase reply max lines to 2

* simplify and improve reply formatting

* add prime icon

* better draggable divider

* support draggable divider in fullscreen overlay chat

* add text shadow in fullscreen chat overlay mode

* format

* add landscape checks

* video store optimization attempts
* Dispose WebViewController and remove JavaScript channels properly
  * Track and cleanup all MutationObservers to prevent accumulation
  * Throttle latency updates to once per 5 seconds (reduces CPU load)
  * Add timeout protection and null checks for robustness
  * Prevent duplicate event listeners on page reloads

* double tap divider to reset

* Revert "video store optimization attempts"

This reverts commit b1df7b0de4.

* optimize video store attempt 2

* Revert "double tap divider to reset"

This reverts commit aeb26a4335.

* optimize latency listener to open the menu and check every 20 secconds

* more optimizations

* hard refresh

* abbreviate latency

* fix lint

* improve hide default overlay

* fix chat no longer reconnecting with chat delay

* refine chat alerts

* fix historical message missing badges and emotes on initial connection

* parallelize

* revert

* add reconnect button inline action

* refactor: replace Scrollbar with FrostyScrollbar across chat and settings screens for consistent styling

* tweak chat details menu

* add scrollbar to release notes, renames for consistency

* fix duplicate message case

* fix chat delay alerts in auto sync mode

* add regex for numeric strings and enhance display name formatting logic

* exterminate borders

* refactor: simplify active chat modes display in chat details screen

* feat: enhance chat input button with delay notification and disable state

* fix: wrap chat in SafeArea to prevent UI overflow issues

* refactor: remove divider line from video chat and update draggable divider visibility logic

* persist overlay in pip mode

* fix: update chat delay handling and notifications in chat bottom bar

* fix: update chat overlay visibility logic to respect fullscreen settings

* tap to show deleted message

* fix: prevent accidental chat scrolling when accessing system UI in landscape mode

* show latency in portrait

* fix: enhance chat delay notifications with dynamic delay time

* docs: update CLAUDE.md for improved environment variable instructions and architecture overview

* fix: improve chat delay countdown management and message handling

* add preliminary release notes

* remove agents.md

* update lockfile

* add initial multi chat feature

* add option to persist multi chat tabs

* add remove confirmation dialog

* improve add chat design

* lazy-load tabs

* create global assets store to dedupe global asset fetching

* improve tab design/styles

* update notes

* use frosty dialog

* update claude md

* tweak

* add ability to reorder tabs

* resolve conflict

* merge cleanup

* format

* update release notes

* add chat delay sync completion tracking to prevent countdown re-appearance

* add tests

* fix

* update release notes

* bump
2026-01-01 22:28:17 -05:00

468 lines
15 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:frosty/apis/base_api_client.dart';
import 'package:frosty/constants.dart';
import 'package:frosty/models/badges.dart';
import 'package:frosty/models/category.dart';
import 'package:frosty/models/channel.dart';
import 'package:frosty/models/emotes.dart';
import 'package:frosty/models/followed_channel.dart';
import 'package:frosty/models/shared_chat_session.dart';
import 'package:frosty/models/stream.dart';
import 'package:frosty/models/user.dart';
/// The Twitch service for making API calls.
class TwitchApi extends BaseApiClient {
static const String _helixBaseUrl = 'https://api.twitch.tv/helix';
static const String _oauthBaseUrl = 'https://id.twitch.tv/oauth2';
static const String _recentMessagesUrl =
'https://recent-messages.robotty.de/api/v2';
TwitchApi(Dio dio) : super(dio, _helixBaseUrl);
/// Returns a list of all Twitch global emotes.
Future<List<Emote>> getEmotesGlobal() async {
final data = await get<JsonMap>('/chat/emotes/global');
final decoded = data['data'] as JsonList;
final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList();
return emotes
.map((emote) => Emote.fromTwitch(emote, EmoteType.twitchGlobal))
.toList();
}
/// Returns a list of a channel's Twitch emotes given their [id].
Future<List<Emote>> getEmotesChannel({required String id}) async {
final data = await get<JsonMap>(
'/chat/emotes',
queryParameters: {'broadcaster_id': id},
);
final decoded = data['data'] as JsonList;
final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList();
return emotes.map((emote) {
switch (emote.emoteType) {
case 'bitstier':
return Emote.fromTwitch(emote, EmoteType.twitchBits);
case 'follower':
return Emote.fromTwitch(emote, EmoteType.twitchFollower);
case 'subscriptions':
return Emote.fromTwitch(emote, EmoteType.twitchChannel);
default:
return Emote.fromTwitch(emote, EmoteType.twitchChannel);
}
}).toList();
}
/// Returns a list of Twitch emotes under the provided [setId].
Future<List<Emote>> getEmotesSets({required String setId}) async {
final data = await get<JsonMap>(
'/chat/emotes/set',
queryParameters: {'emote_set_id': setId},
);
final decoded = data['data'] as JsonList;
final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList();
return emotes.map((emote) {
switch (emote.emoteType) {
case 'globals':
case 'smilies':
return Emote.fromTwitch(emote, EmoteType.twitchGlobal);
case 'subscriptions':
return Emote.fromTwitch(emote, EmoteType.twitchSub);
default:
return Emote.fromTwitch(emote, EmoteType.twitchUnlocked);
}
}).toList();
}
/// Returns a map of global Twitch badges to their [Emote] object.
Future<Map<String, ChatBadge>> getBadgesGlobal() async {
final data = await get<JsonMap>('/chat/badges/global');
final result = <String, ChatBadge>{};
final decoded = data['data'] as JsonList;
for (final badge in decoded) {
final id = badge['set_id'] as String;
final versions = badge['versions'] as JsonList;
for (final version in versions) {
final badgeInfo = BadgeInfoTwitch.fromJson(version);
result['$id/${badgeInfo.id}'] = ChatBadge.fromTwitch(badgeInfo);
}
}
return result;
}
/// Returns a map of a channel's Twitch badges to their [Emote] object.
Future<Map<String, ChatBadge>> getBadgesChannel({required String id}) async {
final data = await get<JsonMap>(
'/chat/badges',
queryParameters: {'broadcaster_id': id},
);
final result = <String, ChatBadge>{};
final decoded = data['data'] as JsonList;
for (final badge in decoded) {
final id = badge['set_id'] as String;
final versions = badge['versions'] as JsonList;
for (final version in versions) {
final badgeInfo = BadgeInfoTwitch.fromJson(version);
result['$id/${badgeInfo.id}'] = ChatBadge.fromTwitch(badgeInfo);
}
}
return result;
}
/// Returns the user's info given their token.
Future<UserTwitch> getUserInfo() async {
final data = await get<JsonMap>('/users');
final userData = data['data'] as JsonList;
return UserTwitch.fromJson(userData.first);
}
/// Returns a token for an anonymous user.
Future<String> getDefaultToken() async {
// Use custom base URL for OAuth
final data = await post<JsonMap>(
'$_oauthBaseUrl/token',
queryParameters: {
'client_id': clientId,
'client_secret': secret,
'grant_type': 'client_credentials',
},
);
return data['access_token'] as String;
}
/// Returns a bool indicating the validity of the given token.
Future<bool> validateToken({required String token}) async {
try {
await get<JsonMap>(
'$_oauthBaseUrl/validate',
headers: {'Authorization': 'Bearer $token'},
);
return true;
} on UnauthorizedException {
// 401 -> token is invalid/expired (propagated from interceptor for validate requests)
return false;
} on ApiException catch (e) {
// Network/timeout/server errors -> treat as indeterminate, not invalid
debugPrint('Token validation indeterminate: $e');
rethrow;
}
}
/// Returns a [StreamsTwitch] object that contains the top 20 streams and a cursor for further requests.
Future<StreamsTwitch> getTopStreams({String? cursor}) async {
final data = await get<JsonMap>(
'/streams',
queryParameters: cursor != null ? {'after': cursor} : null,
);
return StreamsTwitch.fromJson(data);
}
/// Returns a [StreamsTwitch] object that contains the given user ID's top 20 followed streams and a cursor for further requests.
Future<StreamsTwitch> getFollowedStreams({
required String id,
String? cursor,
}) async {
final queryParams = {'user_id': id};
if (cursor != null) queryParams['after'] = cursor;
final data = await get<JsonMap>(
'/streams/followed',
queryParameters: queryParams,
);
return StreamsTwitch.fromJson(data);
}
/// Returns a [FollowedChannels] object containing all followed channels (including offline ones) for the given user ID.
Future<FollowedChannels> getFollowedChannels({
required String userId,
String? cursor,
}) async {
final queryParams = {'user_id': userId, 'first': '20'};
if (cursor != null) queryParams['after'] = cursor;
final data = await get<JsonMap>(
'/channels/followed',
queryParameters: queryParams,
);
return FollowedChannels.fromJson(data);
}
/// Returns a [StreamsTwitch] object that contains the list of streams under the given game/category ID.
Future<StreamsTwitch> getStreamsUnderCategory({
required String gameId,
String? cursor,
}) async {
final queryParams = {'game_id': gameId};
if (cursor != null) queryParams['after'] = cursor;
final data = await get<JsonMap>('/streams', queryParameters: queryParams);
return StreamsTwitch.fromJson(data);
}
/// Returns a [StreamTwitch] object containing the stream info associated with the given [userLogin].
Future<StreamTwitch> getStream({required String userLogin}) async {
final data = await get<JsonMap>(
'/streams',
queryParameters: {'user_login': userLogin},
);
final streamData = data['data'] as JsonList;
if (streamData.isNotEmpty) {
return StreamTwitch.fromJson(streamData.first);
} else {
throw ApiException('$userLogin is offline', 404);
}
}
Future<StreamsTwitch> getStreamsByIds({required List<String> userIds}) async {
// Create query string manually for multiple user_id parameters
final userIdParams = userIds.map((id) => 'user_id=$id').join('&');
final url = '/streams?$userIdParams&first=100';
final data = await get<JsonMap>(url);
return StreamsTwitch.fromJson(data);
}
/// Returns a [UserTwitch] object containing the user info associated with the given [userLogin].
Future<UserTwitch> getUser({String? userLogin, String? id}) async {
final queryParams = <String, String>{};
if (id != null) {
queryParams['id'] = id;
} else if (userLogin != null) {
queryParams['login'] = userLogin;
}
final data = await get<JsonMap>('/users', queryParameters: queryParams);
final userData = data['data'] as JsonList;
if (userData.isNotEmpty) {
return UserTwitch.fromJson(userData.first);
} else {
throw NotFoundException('User does not exist');
}
}
/// Returns a [Channel] object containing a channels's info associated with the given [userId].
Future<Channel> getChannel({required String userId}) async {
final data = await get<JsonMap>(
'/channels',
queryParameters: {'broadcaster_id': userId},
);
final channelData = data['data'] as JsonList;
if (channelData.isNotEmpty) {
return Channel.fromJson(channelData.first);
} else {
throw ApiException('Channel does not exist', 404);
}
}
/// Returns a list of [ChannelQuery] objects closest matching the given [query].
Future<List<ChannelQuery>> searchChannels({required String query}) async {
final data = await get<JsonMap>(
'/search/channels',
queryParameters: {'first': '8', 'query': query},
);
final channelData = data['data'] as JsonList;
return channelData.map((e) => ChannelQuery.fromJson(e)).toList();
}
/// Returns a [CategoriesTwitch] object containing the next top 20 categories/games and a cursor for further requests.
Future<CategoriesTwitch> getTopCategories({String? cursor}) async {
final data = await get<JsonMap>(
'/games/top',
queryParameters: cursor != null ? {'after': cursor} : null,
);
return CategoriesTwitch.fromJson(data);
}
/// Returns a [CategoriesTwitch] object containing the category info corresponding to the provided [gameId].
Future<CategoriesTwitch> getCategory({required String gameId}) async {
final data = await get<JsonMap>('/games', queryParameters: {'id': gameId});
return CategoriesTwitch.fromJson(data);
}
/// Returns a [CategoriesTwitch] containing up to 20 categories/games closest matching the [query] and a cursor for further requests.
Future<CategoriesTwitch> searchCategories({
required String query,
String? cursor,
}) async {
final queryParams = {'first': '8', 'query': query};
if (cursor != null) queryParams['after'] = cursor;
final data = await get<JsonMap>(
'/search/categories',
queryParameters: queryParams,
);
return CategoriesTwitch.fromJson(data);
}
/// Returns the sub count associated with the given [userId].
Future<int> getSubscriberCount({required String userId}) async {
final data = await get<JsonMap>(
'/subscriptions',
queryParameters: {'broadcaster_id': userId},
);
return data['total'] as int;
}
/// Returns a user's list of blocked users given their id.
Future<List<UserBlockedTwitch>> getUserBlockedList({
required String id,
String? cursor,
}) async {
final queryParams = {'first': '100', 'broadcaster_id': id};
if (cursor != null) queryParams['after'] = cursor;
final data = await get<JsonMap>(
'/users/blocks',
queryParameters: queryParams,
);
final paginationCursor = data['pagination']['cursor'];
final blockedList = data['data'] as JsonList;
if (blockedList.isNotEmpty) {
final result = blockedList
.map((e) => UserBlockedTwitch.fromJson(e))
.toList();
if (paginationCursor != null) {
// Wait a bit (150 milliseconds) before recursively calling.
// This will prevent going over the rate limit to due a massive blocked users list.
//
// With the Twitch API, we can make up to 800 requests per minute.
// Waiting 150 milliseconds between requests will cap the rate here at 400 requests per minute.
await Future.delayed(const Duration(milliseconds: 150));
result.addAll(
await getUserBlockedList(id: id, cursor: paginationCursor),
);
}
return result;
} else {
debugPrint('User does not have anyone blocked');
return [];
}
}
// Blocks the user with the given ID and returns true on success or false on failure.
Future<bool> blockUser({required String userId}) async {
try {
await put<dynamic>(
'/users/blocks',
queryParameters: {'target_user_id': userId},
);
return true; // If no exception, operation succeeded
} on ApiException {
return false;
}
}
// Unblocks the user with the given ID and returns true on success or false on failure.
Future<bool> unblockUser({required String userId}) async {
try {
await delete<dynamic>(
'/users/blocks',
queryParameters: {'target_user_id': userId},
);
return true; // If no exception, operation succeeded
} on ApiException {
return false;
}
}
Future<SharedChatSession?> getSharedChatSession({
required String broadcasterId,
}) async {
final data = await get<JsonMap>(
'/shared_chat/session',
queryParameters: {'broadcaster_id': broadcasterId},
);
final sessionData = data['data'] as JsonList;
if (sessionData.isEmpty) {
return null;
}
return SharedChatSession.fromJson(sessionData.first);
}
/// Gets the color used for the user's name in chat.
/// [userId] - The ID of the user whose chat color to get
/// Returns the color as a hex string or empty string if no color is set.
Future<String> getUserChatColor({required String userId}) async {
try {
final data = await get<JsonMap>(
'/chat/color',
queryParameters: {'user_id': userId},
);
final users = data['data'] as JsonList;
if (users.isNotEmpty) {
final user = users.first as JsonMap;
return user['color'] as String? ?? '';
}
return '';
} on ApiException {
return '';
}
}
/// Updates the color used for the user's name in chat.
/// [userId] - The ID of the user whose chat color to update
/// [color] - The color to use. Can be a named color (blue, blue_violet, etc.) or hex code for Turbo/Prime users
/// Returns true on success or false on failure.
Future<bool> updateUserChatColor({
required String userId,
required String color,
}) async {
try {
await put<dynamic>(
'/chat/color',
queryParameters: {'user_id': userId, 'color': color},
);
return true; // If no exception, operation succeeded
} on ApiException catch (e) {
// Log the specific error for debugging
debugPrint('Failed to update chat color: $e');
return false;
}
}
// Gets recent messages from a third-party service.
Future<JsonList> getRecentMessages({required String userLogin}) async {
// Use custom base URL for third-party service
final data = await get<JsonMap>(
'$_recentMessagesUrl/recent-messages/$userLogin',
);
return data['messages'] as JsonList;
}
}