Change all API requests to throw

This commit is contained in:
tommyxchow 2022-01-14 22:51:13 -05:00
parent e21e32b7c5
commit fcc85e451a
4 changed files with 138 additions and 156 deletions

View file

@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:frosty/models/badges.dart';
import 'package:frosty/models/emotes.dart';
import 'package:http/http.dart';
@ -13,24 +12,23 @@ class BTTVApi {
/// Returns a map of global BTTV emotes to their URL.
Future<List<Emote>> getEmotesGlobal() async {
final url = Uri.parse('https://api.betterttv.net/3/cached/emotes/global');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body) as List;
final emotes = decoded.map((emote) => EmoteBTTV.fromJson(emote)).toList();
return emotes.map((emote) => Emote.fromBTTV(emote, EmoteType.bttvGlobal)).toList();
} else {
debugPrint('Failed to get global BTTV emotes. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get global BTTV emotes.');
}
}
/// Returns a map of a channel's BTTV emotes to their URL.
Future<List<Emote>> getEmotesChannel({required String id}) async {
final url = Uri.parse('https://api.betterttv.net/3/cached/users/twitch/$id');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final result = EmoteBTTVChannel.fromJson(decoded);
@ -41,22 +39,22 @@ class BTTVApi {
return emoteToUrl;
} else {
debugPrint('Failed to get BTTV emotes for id: $id. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get channel BTTV emotes.');
}
}
Future<Map<String, Badge>?> getBadges() async {
Future<Map<String, Badge>> getBadges() async {
final url = Uri.parse('https://api.betterttv.net/3/cached/badges');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final badges = jsonDecode(response.body) as List;
final badgeObjects = badges.map((badge) => BadgeInfoBTTV.fromJson(badge)).toList();
return {for (final badge in badgeObjects) badge.providerId: Badge.fromBTTV(badge)};
} else {
throw Exception('Failed to get BTTV badges');
}
debugPrint('Failed to get FFZ badges');
}
}

View file

@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:frosty/models/badges.dart';
import 'package:frosty/models/emotes.dart';
import 'package:http/http.dart';
@ -14,8 +13,8 @@ class FFZApi {
/// Returns a map of global FFZ emotes to their URL.
Future<List<Emote>> getEmotesGlobal() async {
final url = Uri.parse('https://api.frankerfacez.com/v1/set/global');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final defaultSets = decoded['default_sets'] as List;
@ -28,16 +27,15 @@ class FFZApi {
return emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzGlobal)).toList();
} else {
debugPrint('Failed to get global FFZ emotes. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get global FFZ emotes.');
}
}
/// Returns a channel's FFZ room info including custom badges and emote used.
Future<Tuple2<RoomFFZ, List<Emote>>?> getRoomInfo({required String name}) async {
Future<Tuple2<RoomFFZ, List<Emote>>> getRoomInfo({required String name}) async {
final url = Uri.parse('https://api.frankerfacez.com/v1/room/$name');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final roomInfo = RoomFFZ.fromJson(decoded['room']);
@ -47,22 +45,20 @@ class FFZApi {
return Tuple2(roomInfo, emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzChannel)).toList());
} else {
debugPrint('Failed to get FFZ emotes for id: $name. Error code: ${response.statusCode}');
throw Exception('Failed to get FFZ room info.}');
}
}
Future<Map<String, List<Badge>>?> getBadges() async {
Future<Map<String, List<Badge>>> getBadges() async {
final url = Uri.parse('https://api.frankerfacez.com/v1/badges/ids');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final badges = decoded['badges'] as List;
final badgeObjects = badges.map((badge) => BadgeInfoFFZ.fromJson(badge)).toList();
final result = <String, List<Badge>>{};
for (final badge in badgeObjects.reversed) {
for (final userId in decoded['users'][badge.id.toString()]) {
final entry = result[userId.toString()];
@ -76,7 +72,8 @@ class FFZApi {
}
return result;
} else {
throw Exception('Failed to get FFZ badges.');
}
debugPrint('Failed to get FFZ badges');
}
}

View file

@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:frosty/models/badges.dart';
import 'package:frosty/models/emotes.dart';
import 'package:http/http.dart';
@ -14,39 +13,37 @@ class SevenTVAPI {
/// Returns a map of global 7TV emotes to their URL.
Future<List<Emote>> getEmotesGlobal() async {
final url = Uri.parse('https://api.7tv.app/v2/emotes/global');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body) as List;
final emotes = decoded.map((emote) => Emote7TV.fromJson(emote));
return emotes.map((emote) => Emote.from7TV(emote, EmoteType.sevenTvGlobal)).toList();
} else {
debugPrint('Failed to get global 7TV emotes. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get global 7TV emotes.');
}
}
/// Returns a map of a channel's 7TV emotes to their URL.
Future<List<Emote>> getEmotesChannel({required String user}) async {
final url = Uri.parse('https://api.7tv.app/v2/users/$user/emotes');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body) as List;
final emotes = decoded.map((emote) => Emote7TV.fromJson(emote));
return emotes.map((emote) => Emote.from7TV(emote, EmoteType.sevenTvChannel)).toList();
} else {
debugPrint('Failed to get channel 7TV emotes. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get channel 7TV emotes.');
}
}
Future<Map<String, List<Badge>>?> getBadges() async {
Future<Map<String, List<Badge>>> getBadges() async {
final url = Uri.parse('https://api.7tv.app/v2/badges?user_identifier=twitch_id');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body)['badges'] as List;
final badges = decoded.map((emote) => BadgeInfo7TV.fromJson(emote));
@ -66,7 +63,7 @@ class SevenTVAPI {
return result;
} else {
debugPrint('Failed to get channel 7TV emotes. Error code: ${response.statusCode}');
throw Exception('Failed to get 7TV badges.');
}
}
}

View file

@ -26,35 +26,40 @@ class TwitchApi {
return emotes.map((emote) => Emote.fromTwitch(emote, EmoteType.twitchGlobal)).toList();
} else {
debugPrint('Failed to get global Twitch emotes. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get global Twitch emotes.');
}
}
/// Returns a map of a channel's Twitch emotes to their URL.
Future<List<Emote>> getEmotesChannel({required String id, required Map<String, String>? headers}) async {
Future<List<Emote>> getEmotesChannel({
required String id,
required Map<String, String>? headers,
}) async {
final url = Uri.parse('https://api.twitch.tv/helix/chat/emotes?broadcaster_id=$id');
final response = await _client.get(url, headers: headers);
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body)['data'] as List;
final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList();
return emotes.map((emote) => Emote.fromTwitch(emote, EmoteType.twitchChannel)).toList();
} else {
debugPrint('Failed to get Twitch emotes for id: $id. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get channel Twitch emotes.');
}
}
/// Returns a map of a channel's Twitch emotes to their URL.
Future<List<Emote>> getEmotesSets({required String setId, required Map<String, String>? headers, sub = false}) async {
Future<List<Emote>> getEmotesSets({
required String setId,
required Map<String, String>? headers,
}) async {
final url = Uri.parse('https://api.twitch.tv/helix/chat/emotes/set?emote_set_id=$setId');
final response = await _client.get(url, headers: headers);
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body)['data'] as List;
final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList();
return emotes.map((emote) {
switch (emote.emoteType) {
case 'globals':
@ -67,68 +72,60 @@ class TwitchApi {
}
}).toList();
} else {
debugPrint('Failed to get Twitch emotes for set id: $setId. Error code: ${response.statusCode}');
return [];
throw Exception('Failed to get Twitch emotes set.');
}
}
/// Returns a map of global Twitch badges to their URL.
Future<Map<String, Badge>> getBadgesGlobal() async {
final url = Uri.parse('https://badges.twitch.tv/v1/badges/global/display');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final result = <String, Badge>{};
final decoded = jsonDecode(response.body)['badge_sets'] as Map;
decoded.forEach((id, versions) =>
(versions['versions'] as Map).forEach((version, badgeInfo) => result['$id/$version'] = Badge.fromTwitch(BadgeInfoTwitch.fromJson(badgeInfo))));
decoded.forEach((id, versions) => (versions['versions'] as Map)
.forEach((version, badgeInfo) => result['$id/$version'] = Badge.fromTwitch(BadgeInfoTwitch.fromJson(badgeInfo))));
return result;
} else {
debugPrint('Failed to get global Twitch badges. Error code: ${response.statusCode}');
return {};
throw Exception('Failed to get global Twitch badges.');
}
}
/// Returns a map of a channel's Twitch badges to their URL.
Future<Map<String, Badge>> getBadgesChannel({required String id}) async {
final url = Uri.parse('https://badges.twitch.tv/v1/badges/channels/$id/display');
final response = await _client.get(url);
final response = await _client.get(url);
if (response.statusCode == 200) {
final result = <String, Badge>{};
final decoded = jsonDecode(response.body)['badge_sets'] as Map;
decoded.forEach((id, versions) =>
(versions['versions'] as Map).forEach((version, badgeInfo) => result['$id/$version'] = Badge.fromTwitch(BadgeInfoTwitch.fromJson(badgeInfo))));
decoded.forEach((id, versions) => (versions['versions'] as Map)
.forEach((version, badgeInfo) => result['$id/$version'] = Badge.fromTwitch(BadgeInfoTwitch.fromJson(badgeInfo))));
return result;
} else {
debugPrint('Failed to get Twitch badges for id: $id. Error code: ${response.statusCode}');
return {};
throw Exception('Failed to get Twitch channel badges.');
}
}
/// Returns the user's info given their token (headers)
Future<UserTwitch?> getUserInfo({required Map<String, String>? headers}) async {
final response = await _client.get(Uri.parse('https://api.twitch.tv/helix/users'), headers: headers);
Future<UserTwitch> getUserInfo({required Map<String, String>? headers}) async {
final url = Uri.parse('https://api.twitch.tv/helix/users');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final userData = jsonDecode(response.body)['data'] as List;
return UserTwitch.fromJson(userData.first);
} else {
debugPrint('Failed to get user info');
throw Exception('Failed to get user info');
}
}
/// Returns a token for an anonymous user.
Future<String?> getDefaultToken() async {
debugPrint('Getting default token...');
Future<String> getDefaultToken() async {
final url = Uri(
scheme: 'https',
host: 'id.twitch.tv',
@ -141,226 +138,222 @@ class TwitchApi {
);
final response = await _client.post(url);
if (response.statusCode == 200) {
return jsonDecode(response.body)['access_token'];
} else {
debugPrint('Failed to get default token.');
throw Exception('Failed to get default token.');
}
}
/// Returns the validity of the given token
Future<bool> validateToken({required String token}) async {
debugPrint('Validating token...');
final response = await _client.get(
Uri.parse('https://id.twitch.tv/oauth2/validate'),
headers: {'Authorization': 'Bearer $token'},
);
final url = Uri.parse('https://id.twitch.tv/oauth2/validate');
final response = await _client.get(url, headers: {'Authorization': 'Bearer $token'});
if (response.statusCode == 200) {
debugPrint('Token validated!');
return true;
} else {
debugPrint('Token invalidated :(');
return false;
}
debugPrint('Token invalidated :(');
return false;
}
/// Returns a map containing top 20 streams and a cursor for further requests.
Future<StreamsTwitch?> getTopStreams({required Map<String, String>? headers, String? cursor}) async {
final Uri uri;
if (cursor == null) {
uri = Uri.parse('https://api.twitch.tv/helix/streams');
} else {
uri = Uri.parse('https://api.twitch.tv/helix/streams?after=$cursor');
}
final response = await _client.get(uri, headers: headers);
Future<StreamsTwitch> getTopStreams({
required Map<String, String>? headers,
String? cursor,
}) async {
final url = Uri.parse(cursor == null ? 'https://api.twitch.tv/helix/streams' : 'https://api.twitch.tv/helix/streams?after=$cursor');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return StreamsTwitch.fromJson(decoded);
} else {
debugPrint('Failed to update top streams');
throw Exception('Failed to get top streams');
}
}
/// Returns a map with the given user's top 20 followed streams and a cursor for further requests.
Future<StreamsTwitch?> getFollowedStreams({
Future<StreamsTwitch> getFollowedStreams({
required String id,
required Map<String, String>? headers,
String? cursor,
}) async {
final Uri uri;
if (cursor == null) {
uri = Uri.parse('https://api.twitch.tv/helix/streams/followed?user_id=$id');
} else {
uri = Uri.parse('https://api.twitch.tv/helix/streams/followed?user_id=$id&after=$cursor');
}
final response = await _client.get(uri, headers: headers);
final url = Uri.parse(cursor == null
? 'https://api.twitch.tv/helix/streams/followed?user_id=$id'
: 'https://api.twitch.tv/helix/streams/followed?user_id=$id&after=$cursor');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return StreamsTwitch.fromJson(decoded);
} else {
debugPrint('Failed to update followed streams');
throw Exception('Failed to get followed streams.');
}
}
/// Returns the list of streams under the given game/category ID.
Future<StreamsTwitch?> getStreamsUnderGame({
Future<StreamsTwitch> getStreamsUnderCategory({
required String gameId,
required Map<String, String>? headers,
String? cursor,
}) async {
final uri = cursor == null
? Uri.parse('https://api.twitch.tv/helix/streams?game_id=$gameId')
: Uri.parse('https://api.twitch.tv/helix/streams?game_id=$gameId&after=$cursor');
final response = await _client.get(uri, headers: headers);
final url = Uri.parse(
cursor == null ? 'https://api.twitch.tv/helix/streams?game_id=$gameId' : 'https://api.twitch.tv/helix/streams?game_id=$gameId&after=$cursor');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return StreamsTwitch.fromJson(decoded);
} else {
debugPrint('Failed to update game streams');
throw Exception('Failed to get streams under category.');
}
}
/// Returns the stream info given the user login.
Future<StreamTwitch?> getStream({required String userLogin, required Map<String, String>? headers}) async {
Future<StreamTwitch> getStream({
required String userLogin,
required Map<String, String>? headers,
}) async {
final uri = Uri.parse('https://api.twitch.tv/helix/streams?user_login=$userLogin');
final response = await _client.get(uri, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final data = decoded['data'] as List;
if (data.isEmpty) return null;
return StreamTwitch.fromJson(data.first);
if (data.isNotEmpty) {
return StreamTwitch.fromJson(data.first);
} else {
throw Exception('User is offline or does not exist.');
}
} else {
debugPrint('Failed to update top streams');
throw Exception('Failed to get stream info.');
}
}
/// Returns a user's info given their login name.
Future<UserTwitch?> getUser({String? userLogin, String? id, required Map<String, String>? headers}) async {
final uri = id != null ? Uri.parse('https://api.twitch.tv/helix/users?id=$id') : Uri.parse('https://api.twitch.tv/helix/users?login=$userLogin');
Future<UserTwitch> getUser({
String? userLogin,
String? id,
required Map<String, String>? headers,
}) async {
final url = Uri.parse(id != null ? 'https://api.twitch.tv/helix/users?id=$id' : 'https://api.twitch.tv/helix/users?login=$userLogin');
final response = await _client.get(uri, headers: headers);
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final userData = jsonDecode(response.body)['data'] as List;
if (userData.isNotEmpty) {
return UserTwitch.fromJson(userData.first);
} else {
debugPrint('User does not exist');
throw Exception('User does not exist.');
}
} else {
debugPrint('User does not exist');
throw Exception('Failed to get user.');
}
}
/// Returns a channels's info associated with the given ID.
Future<Channel?> getChannel({required String userId, required Map<String, String>? headers}) async {
final response = await _client.get(Uri.parse('https://api.twitch.tv/helix/channels?broadcaster_id=$userId'), headers: headers);
Future<Channel> getChannel({
required String userId,
required Map<String, String>? headers,
}) async {
final url = Uri.parse('https://api.twitch.tv/helix/channels?broadcaster_id=$userId');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final channelData = jsonDecode(response.body)['data'] as List;
if (channelData.isNotEmpty) {
return Channel.fromJson(channelData.first);
} else {
debugPrint('Channel does not exist');
throw Exception('Channel does not exist');
}
} else {
debugPrint('Channel does not exist');
throw Exception('Failed to get channel.');
}
}
/// Returns a list of channel query objects closest matching the given query string.
Future<List<ChannelQuery>> searchChannels({required String query, required Map<String, String>? headers}) async {
final response = await _client.get(Uri.parse('https://api.twitch.tv/helix/search/channels?first=8&query=$query'), headers: headers);
Future<List<ChannelQuery>> searchChannels({
required String query,
required Map<String, String>? headers,
}) async {
final url = Uri.parse('https://api.twitch.tv/helix/search/channels?first=8&query=$query');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final channelData = jsonDecode(response.body)['data'] as List;
return channelData.map((e) => ChannelQuery.fromJson(e)).toList();
} else {
return [];
throw Exception('Failed to get channels.');
}
}
/// Returns a map containing top 20 categories/games and a cursor for further requests.
Future<CategoriesTwitch?> getTopGames({required Map<String, String>? headers, String? cursor}) async {
final Uri uri;
if (cursor == null) {
uri = Uri.parse('https://api.twitch.tv/helix/games/top');
} else {
uri = Uri.parse('https://api.twitch.tv/helix/games/top?after=$cursor');
}
final response = await _client.get(uri, headers: headers);
Future<CategoriesTwitch> getTopCategories({
required Map<String, String>? headers,
String? cursor,
}) async {
final url = Uri.parse(cursor == null ? 'https://api.twitch.tv/helix/games/top' : 'https://api.twitch.tv/helix/games/top?after=$cursor');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return CategoriesTwitch.fromJson(decoded);
} else {
debugPrint('Failed to update top games');
throw Exception('Failed to get top categories.');
}
}
/// Returns a map containing top 20 categories/games and a cursor for further requests.
Future<CategoriesTwitch?> searchCategories({required Map<String, String>? headers, required String query, String? cursor}) async {
final uri = cursor == null
? Uri.parse('https://api.twitch.tv/helix/search/categories?first=8&query=$query')
: Uri.parse('https://api.twitch.tv/helix/search/categories?first=8&query=$query&after=$cursor');
final response = await _client.get(uri, headers: headers);
Future<CategoriesTwitch> searchCategories({required Map<String, String>? headers, required String query, String? cursor}) async {
final url = Uri.parse(cursor == null
? 'https://api.twitch.tv/helix/search/categories?first=8&query=$query'
: 'https://api.twitch.tv/helix/search/categories?first=8&query=$query&after=$cursor');
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return CategoriesTwitch.fromJson(decoded);
} else {
debugPrint('Failed to update top games');
throw Exception('Failed to get categories.');
}
}
/// Returns the sub count for a user.
Future<int?> getSubscriberCount({required String userId, required Map<String, String>? headers}) async {
Future<int> getSubscriberCount({required String userId, required Map<String, String>? headers}) async {
final uri = Uri.parse('https://api.twitch.tv/helix/subscriptions?broadcaster_id=$userId');
final response = await _client.get(uri, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return decoded['total'] as int;
} else {
debugPrint('Failed to update top games');
throw Exception('Failed to get sub count.');
}
}
Future<ChatUsers?> getChatters({required String userLogin}) async {
Future<ChatUsers> getChatters({required String userLogin}) async {
final uri = Uri.parse('https://tmi.twitch.tv/group/user/$userLogin/chatters');
final response = await _client.get(uri);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
return ChatUsers.fromJson(decoded);
} else {
debugPrint('Failed to get chatters');
throw Exception('Failed to get chatters.');
}
}
@ -370,14 +363,13 @@ class TwitchApi {
required Map<String, String>? headers,
String? cursor,
}) async {
final uri = cursor == null
? Uri.parse('https://api.twitch.tv/helix/users/blocks?first=100&broadcaster_id=$id')
: Uri.parse('https://api.twitch.tv/helix/users/blocks?first=100&broadcaster_id=$id&after=$cursor');
final url = Uri.parse(cursor == null
? 'https://api.twitch.tv/helix/users/blocks?first=100&broadcaster_id=$id'
: 'https://api.twitch.tv/helix/users/blocks?first=100&broadcaster_id=$id&after=$cursor');
final response = await _client.get(uri, headers: headers);
final response = await _client.get(url, headers: headers);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final cursor = decoded['pagination']['cursor'];
final blockedList = decoded['data'] as List;
@ -390,20 +382,18 @@ class TwitchApi {
return result;
} else {
debugPrint('User does not have anyone blocked');
return [];
throw Exception('User does not have anyone blocked');
}
} else {
debugPrint('User does not exist');
return [];
throw Exception('User does not exist');
}
}
// Blocks the user with the given ID and returns true on success or false on failure.
Future<bool> blockUser({required String userId, required Map<String, String> headers}) async {
final uri = Uri.parse('https://api.twitch.tv/helix/users/blocks?target_user_id=$userId');
final url = Uri.parse('https://api.twitch.tv/helix/users/blocks?target_user_id=$userId');
final response = await _client.put(uri, headers: headers);
final response = await _client.put(url, headers: headers);
if (response.statusCode == 204) {
return true;
} else {
@ -413,9 +403,9 @@ class TwitchApi {
// Unblocks the user with the given ID and returns true on success or false on failure.
Future<bool> unblockUser({required String userId, required Map<String, String> headers}) async {
final uri = Uri.parse('https://api.twitch.tv/helix/users/blocks?target_user_id=$userId');
final url = Uri.parse('https://api.twitch.tv/helix/users/blocks?target_user_id=$userId');
final response = await _client.delete(uri, headers: headers);
final response = await _client.delete(url, headers: headers);
if (response.statusCode == 204) {
return true;
} else {