Add support for FFZ dev, bot, and supporter badges

This commit is contained in:
Tommy Chow 2021-12-30 23:00:03 -05:00
parent d9bca9cc59
commit c86b0f8c8f
6 changed files with 159 additions and 9 deletions

View file

@ -1,5 +1,6 @@
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' as http;
@ -11,7 +12,7 @@ class FFZ {
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body) as List;
final emotes = decoded.map((emote) => EmoteFFZ.fromJson(emote)).toList();
final emotes = decoded.map((emote) => EmoteFFZ.fromJson(emote));
return emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzGlobal)).toList();
} else {
@ -27,7 +28,7 @@ class FFZ {
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body) as List;
final emotes = decoded.map((emote) => EmoteFFZ.fromJson(emote)).toList();
final emotes = decoded.map((emote) => EmoteFFZ.fromJson(emote));
return emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzChannel)).toList();
} else {
@ -35,4 +36,50 @@ class FFZ {
return [];
}
}
static Future<Map<String, List<BadgeInfoFFZ>>?> getBadges() async {
final url = Uri.parse('https://api.frankerfacez.com/v1/badges');
final response = await http.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final badges = decoded['badges'] as List;
final users = decoded['users'];
final badgeObjects = badges.map((badge) => BadgeInfoFFZ.fromJson(badge)).toList();
final result = <String, List<BadgeInfoFFZ>>{};
final developers = users['1'] as List;
final bots = users['2'] as List;
final supporters = users['3'] as List;
for (var username in developers) {
final entry = result[username];
if (entry != null) {
entry.add(badgeObjects[0]);
} else {
result[username] = [badgeObjects[0]];
}
}
for (var username in bots) {
final entry = result[username];
if (entry != null) {
entry.add(badgeObjects[1]);
} else {
result[username] = [badgeObjects[1]];
}
}
for (var username in supporters) {
final entry = result[username];
if (entry != null) {
entry.add(badgeObjects[2]);
} else {
result[username] = [badgeObjects[2]];
}
}
return result;
}
debugPrint('Failed to get FFZ badges');
}
}

View file

@ -29,3 +29,46 @@ class BadgeInfoTwitch {
factory BadgeInfoTwitch.fromJson(Map<String, dynamic> json) => _$BadgeInfoTwitchFromJson(json);
}
@JsonSerializable(createToJson: false, fieldRename: FieldRename.snake)
class BadgeInfoFFZ {
final int id;
final String name;
final String title;
final int slot;
final String? replaces;
final String color;
final String image;
final BadgeUrlsFFZ urls;
const BadgeInfoFFZ(
this.id,
this.name,
this.title,
this.slot,
this.replaces,
this.color,
this.image,
this.urls,
);
factory BadgeInfoFFZ.fromJson(Map<String, dynamic> json) => _$BadgeInfoFFZFromJson(json);
}
@JsonSerializable(createToJson: false)
class BadgeUrlsFFZ {
@JsonKey(name: '1')
final String url1x;
@JsonKey(name: '2')
final String url2x;
@JsonKey(name: '4')
final String url4x;
const BadgeUrlsFFZ(
this.url1x,
this.url2x,
this.url4x,
);
factory BadgeUrlsFFZ.fromJson(Map<String, dynamic> json) => _$BadgeUrlsFFZFromJson(json);
}

View file

@ -16,3 +16,20 @@ BadgeInfoTwitch _$BadgeInfoTwitchFromJson(Map<String, dynamic> json) =>
json['click_action'] as String,
json['click_url'] as String,
);
BadgeInfoFFZ _$BadgeInfoFFZFromJson(Map<String, dynamic> json) => BadgeInfoFFZ(
json['id'] as int,
json['name'] as String,
json['title'] as String,
json['slot'] as int,
json['replaces'] as String?,
json['color'] as String,
json['image'] as String,
BadgeUrlsFFZ.fromJson(json['urls'] as Map<String, dynamic>),
);
BadgeUrlsFFZ _$BadgeUrlsFFZFromJson(Map<String, dynamic> json) => BadgeUrlsFFZ(
json['1'] as String,
json['2'] as String,
json['4'] as String,
);

View file

@ -71,7 +71,8 @@ class IRCMessage {
List<InlineSpan> generateSpan({
required TextStyle? style,
required Map<String, Emote> emoteToObject,
required Map<String, BadgeInfoTwitch> badgeToObject,
required Map<String, BadgeInfoTwitch> twitchBadges,
required Map<String, List<BadgeInfoFFZ>> ffzBadges,
bool hideMessage = false,
bool zeroWidthEnabled = false,
Timestamp timestamp = Timestamp.none,
@ -103,10 +104,43 @@ class IRCMessage {
}
// Add any badges to the span.
// FFZ badges
var ignoreMod = false;
final ffzUserBadges = ffzBadges[user];
if (ffzUserBadges != null) {
for (final ffzBadge in ffzUserBadges) {
if (ffzBadge.replaces != null) ignoreMod = true;
span.add(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Tooltip(
message: ffzBadge.title,
preferBelow: false,
child: ColoredBox(
color: ffzBadge.name == 'bot' ? HexColor.fromHex('#00AD03') : HexColor.fromHex(ffzBadge.color),
child: CachedNetworkImage(
imageUrl: 'https:' + ffzBadge.urls.url4x,
placeholder: (context, url) => const SizedBox(),
fadeInDuration: const Duration(seconds: 0),
height: 20,
),
),
),
),
);
span.add(const TextSpan(text: ' '));
}
}
// Twitch badges
final badges = tags['badges'];
if (badges != null) {
for (final badge in badges.split(',')) {
final badgeInfo = badgeToObject[badge];
if (badge == 'moderator/1' && ignoreMod) continue;
final badgeInfo = twitchBadges[badge];
if (badgeInfo != null) {
span.add(
WidgetSpan(

View file

@ -28,7 +28,10 @@ abstract class _ChatAssetsStoreBase with Store {
var _userEmoteToObject = ObservableMap<String, Emote>();
/// The map of badges ids to their object representation.
final badgesToObject = <String, BadgeInfoTwitch>{};
final twitchBadgesToObject = <String, BadgeInfoTwitch>{};
/// The map of usernames to their FFZ badges.
final userToFFZBadges = <String, List<BadgeInfoFFZ>>{};
/// The current index of the emote menu stack.
@observable
@ -81,9 +84,12 @@ abstract class _ChatAssetsStoreBase with Store {
for (final map in badges) {
if (map != null) {
badgesToObject.addAll(map);
twitchBadgesToObject.addAll(map);
}
}
final ffzBadges = await FFZ.getBadges();
if (ffzBadges != null) userToFFZBadges.addAll(ffzBadges);
}
}

View file

@ -33,7 +33,8 @@ class ChatMessage extends StatelessWidget {
children: ircMessage.generateSpan(
style: DefaultTextStyle.of(context).style,
emoteToObject: assetsStore.emoteToObject,
badgeToObject: assetsStore.badgesToObject,
twitchBadges: assetsStore.twitchBadgesToObject,
ffzBadges: assetsStore.userToFFZBadges,
zeroWidthEnabled: zeroWidth,
timestamp: timestamp,
),
@ -56,7 +57,8 @@ class ChatMessage extends StatelessWidget {
children: ircMessage.generateSpan(
style: DefaultTextStyle.of(context).style,
emoteToObject: assetsStore.emoteToObject,
badgeToObject: assetsStore.badgesToObject,
twitchBadges: assetsStore.twitchBadgesToObject,
ffzBadges: assetsStore.userToFFZBadges,
hideMessage: hideMessageIfBanned,
zeroWidthEnabled: zeroWidth,
timestamp: timestamp,
@ -105,7 +107,8 @@ class ChatMessage extends StatelessWidget {
children: ircMessage.generateSpan(
style: DefaultTextStyle.of(context).style,
emoteToObject: assetsStore.emoteToObject,
badgeToObject: assetsStore.badgesToObject,
twitchBadges: assetsStore.twitchBadgesToObject,
ffzBadges: assetsStore.userToFFZBadges,
zeroWidthEnabled: zeroWidth,
timestamp: timestamp,
),