Fix emoji sizing, add common badge model

This commit is contained in:
Tommy Chow 2022-01-12 09:29:31 -05:00
parent 89443247fe
commit 09edf1cb1d
9 changed files with 124 additions and 61 deletions

View file

@ -41,7 +41,7 @@ class BTTV {
}
}
static Future<Map<String, BadgeInfoBTTV>?> getBadges() async {
static Future<Map<String, Badge>?> getBadges() async {
final url = Uri.parse('https://api.betterttv.net/3/cached/badges');
final response = await http.get(url);
@ -50,7 +50,7 @@ class BTTV {
final badgeObjects = badges.map((badge) => BadgeInfoBTTV.fromJson(badge)).toList();
return {for (final badge in badgeObjects) badge.providerId: badge};
return {for (final badge in badgeObjects) badge.providerId: Badge.fromBTTV(badge)};
}
debugPrint('Failed to get FFZ badges');
}

View file

@ -46,7 +46,7 @@ class FFZ {
}
}
static Future<Map<String, List<BadgeInfoFFZ>>?> getBadges() async {
static Future<Map<String, List<Badge>>?> getBadges() async {
final url = Uri.parse('https://api.frankerfacez.com/v1/badges/ids');
final response = await http.get(url);
@ -56,15 +56,16 @@ class FFZ {
final badges = decoded['badges'] as List;
final badgeObjects = badges.map((badge) => BadgeInfoFFZ.fromJson(badge)).toList();
final result = <String, List<BadgeInfoFFZ>>{};
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()];
final normalBadge = Badge.fromFFZ(badge);
if (entry == null) {
result[userId.toString()] = [badge];
result[userId.toString()] = [normalBadge];
} else {
entry.add(badge);
entry.add(normalBadge);
}
}
}

View file

@ -38,7 +38,7 @@ class SevenTV {
}
}
static Future<Map<String, List<BadgeInfo7TV>>?> getBadges() async {
static Future<Map<String, List<Badge>>?> getBadges() async {
final url = Uri.parse('https://api.7tv.app/v2/badges?user_identifier=twitch_id');
final response = await http.get(url);
@ -46,14 +46,15 @@ class SevenTV {
final decoded = jsonDecode(response.body)['badges'] as List;
final badges = decoded.map((emote) => BadgeInfo7TV.fromJson(emote));
final result = <String, List<BadgeInfo7TV>>{};
final result = <String, List<Badge>>{};
for (final badge in badges) {
for (final userId in badge.users) {
final entry = result[userId];
final normalBadge = Badge.from7TV(badge);
if (entry == null) {
result[userId] = [badge];
result[userId] = [normalBadge];
} else {
entry.add(badge);
entry.add(normalBadge);
}
}
}

View file

@ -69,17 +69,17 @@ class Twitch {
}
/// Returns a map of global Twitch badges to their URL.
static Future<Map<String, BadgeInfoTwitch>> getBadgesGlobal() async {
static Future<Map<String, Badge>> getBadgesGlobal() async {
final url = Uri.parse('https://badges.twitch.tv/v1/badges/global/display');
final response = await http.get(url);
if (response.statusCode == 200) {
final result = <String, BadgeInfoTwitch>{};
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'] = 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 {
@ -89,17 +89,17 @@ class Twitch {
}
/// Returns a map of a channel's Twitch badges to their URL.
static Future<Map<String, BadgeInfoTwitch>> getBadgesChannel({required String id}) async {
static 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 http.get(url);
if (response.statusCode == 200) {
final result = <String, BadgeInfoTwitch>{};
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'] = 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 {

View file

@ -123,3 +123,49 @@ class BadgeDetailsBTTV {
factory BadgeDetailsBTTV.fromJson(Map<String, dynamic> json) => _$BadgeDetailsBTTVFromJson(json);
}
class Badge {
final String name;
final String url;
final BadgeType type;
final String? color;
const Badge({
required this.name,
required this.url,
this.color,
required this.type,
});
factory Badge.fromTwitch(BadgeInfoTwitch badge) => Badge(
name: badge.title,
url: badge.imageUrl4x,
type: BadgeType.twitch,
);
factory Badge.fromBTTV(BadgeInfoBTTV badge) => Badge(
name: badge.badge.description,
url: badge.badge.svg,
type: BadgeType.bttv,
);
factory Badge.fromFFZ(BadgeInfoFFZ badge) => Badge(
name: badge.title,
url: badge.urls.url4x,
color: badge.color,
type: BadgeType.ffz,
);
factory Badge.from7TV(BadgeInfo7TV badge) => Badge(
name: badge.tooltip,
url: badge.urls[2][1],
type: BadgeType.sevenTv,
);
}
enum BadgeType {
twitch,
bttv,
ffz,
sevenTv,
}

View file

@ -258,7 +258,6 @@ class Emote7TV {
/// The common emote class.
class Emote {
final String id;
final String name;
final int? width;
final int? height;
@ -267,7 +266,6 @@ class Emote {
final EmoteType type;
const Emote({
required this.id,
required this.name,
this.width,
this.height,
@ -277,7 +275,6 @@ class Emote {
});
factory Emote.fromTwitch(EmoteTwitch emote, EmoteType type) => Emote(
id: emote.id,
name: emote.name,
zeroWidth: false,
url: 'https://static-cdn.jtvnw.net/emoticons/v2/${emote.id}/default/dark/3.0',
@ -285,7 +282,6 @@ class Emote {
);
factory Emote.fromBTTV(EmoteBTTV emote, EmoteType type) => Emote(
id: emote.id,
name: emote.code,
zeroWidth: zeroWidthEmotes.contains(emote.code),
url: 'https://cdn.betterttv.net/emote/${emote.id}/3x',
@ -293,7 +289,6 @@ class Emote {
);
factory Emote.fromFFZ(EmoteFFZ emote, EmoteType type) => Emote(
id: emote.id.toString(),
name: emote.name,
zeroWidth: false,
url: 'https:' + (emote.urls.url4x ?? emote.urls.url2x ?? emote.urls.url1x),
@ -301,7 +296,6 @@ class Emote {
);
factory Emote.from7TV(Emote7TV emote, EmoteType type) => Emote(
id: emote.id,
name: emote.name,
width: emote.width.first,
height: emote.height.first,

View file

@ -129,16 +129,16 @@ class IRCMessage {
for (final badge in twitchBadges) {
final badgeInfo = twitchBadgeToObject[badge];
if (badgeInfo != null) {
var badgeUrl = badgeInfo.imageUrl4x;
var badgeUrl = badgeInfo.url;
// Add custom FFZ mod badge if it exists.
if (badgeInfo.title == 'Moderator' && (ffzUserBadges != null || ffzRoomInfo?.modUrls != null)) {
if (badgeInfo.name == 'Moderator' && (ffzUserBadges != null || ffzRoomInfo?.modUrls != null)) {
// Check if mod is bot.
final botBadge = ffzUserBadges?.firstWhereOrNull((element) => element.id == 2);
final botBadge = ffzUserBadges?.firstWhereOrNull((element) => element.name == 'Bot');
// Check if user has bot badge or room has custom FFZ mod badges
if (botBadge != null) {
badgeUrl = 'https:' + botBadge.urls.url4x;
badgeUrl = 'https:' + botBadge.url;
skipBot = true;
} else if (ffzRoomInfo?.modUrls != null) {
badgeUrl = 'https:' + (ffzRoomInfo!.modUrls?.url4x ?? ffzRoomInfo.modUrls?.url2x ?? ffzRoomInfo.modUrls!.url1x);
@ -157,14 +157,14 @@ class IRCMessage {
}
// Add custom FFZ vip badge if it exists
if (badgeInfo.title == 'VIP' && ffzRoomInfo?.vipBadge != null) {
if (badgeInfo.name == 'VIP' && ffzRoomInfo?.vipBadge != null) {
badgeUrl = 'https:' + (ffzRoomInfo!.vipBadge?.url4x ?? ffzRoomInfo.vipBadge?.url2x ?? ffzRoomInfo.vipBadge!.url1x);
}
span.add(
_createEmoteSpan(
emoteUrl: badgeUrl,
tooltip: badgeInfo.title,
tooltip: badgeInfo.name,
height: badgeHeight,
),
);
@ -176,15 +176,15 @@ class IRCMessage {
// Add FFZ badges to span
if (ffzUserBadges != null) {
for (final badge in ffzUserBadges) {
if (badge.id == 2) {
if (badge.name == 'Bot') {
if (!skipBot) {
span.insert(
0,
_createEmoteSpan(
emoteUrl: 'https:' + badge.urls.url4x,
tooltip: badge.title,
emoteUrl: 'https:' + badge.url,
tooltip: badge.name,
height: badgeHeight,
backgroundColor: HexColor.fromHex(badge.color),
backgroundColor: HexColor.fromHex(badge.color!),
),
);
span.add(const TextSpan(text: ' '));
@ -192,10 +192,10 @@ class IRCMessage {
} else {
span.add(
_createEmoteSpan(
emoteUrl: 'https:' + badge.urls.url4x,
tooltip: badge.title,
emoteUrl: 'https:' + badge.url,
tooltip: badge.name,
height: badgeHeight,
backgroundColor: HexColor.fromHex(badge.color),
backgroundColor: HexColor.fromHex(badge.color!),
),
);
span.add(const TextSpan(text: ' '));
@ -208,8 +208,8 @@ class IRCMessage {
if (userBTTVBadge != null) {
span.add(
_createEmoteSpan(
emoteUrl: userBTTVBadge.badge.svg,
tooltip: userBTTVBadge.badge.description,
emoteUrl: userBTTVBadge.url,
tooltip: userBTTVBadge.name,
height: badgeHeight,
isSvg: true,
),
@ -223,8 +223,8 @@ class IRCMessage {
for (final badge in user7TVBadges) {
span.add(
_createEmoteSpan(
emoteUrl: badge.urls[2][1],
tooltip: badge.tooltip,
emoteUrl: badge.url,
tooltip: badge.name,
height: badgeHeight,
),
);
@ -271,8 +271,28 @@ class IRCMessage {
// Add the message and any emotes to the span.
final chatMessage = message;
if (chatMessage != null) {
final words = chatMessage.split(' ');
words.removeWhere((element) => element == '');
final words = <String>[];
final emojiBuffer = StringBuffer();
final wordBuffer = StringBuffer();
for (final character in chatMessage.characters) {
if (regexEmoji.hasMatch(character)) {
if (wordBuffer.isNotEmpty) {
words.addAll(wordBuffer.toString().split(' '));
wordBuffer.clear();
}
emojiBuffer.write(character);
} else {
if (emojiBuffer.isNotEmpty) {
words.add(emojiBuffer.toString());
emojiBuffer.clear();
}
wordBuffer.write(character);
}
}
if (wordBuffer.isNotEmpty) words.addAll(wordBuffer.toString().split(' ').where((element) => element != ''));
if (emojiBuffer.isNotEmpty) words.add(emojiBuffer.toString());
if (useZeroWidth) {
final localSpan = <InlineSpan>[];
@ -492,7 +512,9 @@ class IRCMessage {
// Also remove any "INVALID/UNDEFINED" Unicode characters.
// Rendering this character on iOS shows a question mark inside a square.
// This character is used by some clients to bypass restrictions on repeating message.
var message = splitMessage.length > 3 ? splitMessage.sublist(3).map((word) => word.replaceAll('\u{E0000}', '').trim()).join(' ').substring(1) : null;
var message = splitMessage.length > 3
? splitMessage.sublist(3).map((word) => word.replaceAll('\u{E0000}', '').trim()).where((element) => element != '').join(' ').substring(1)
: null;
// Check if IRC actions like "/me" were called.
var action = false;
@ -538,7 +560,6 @@ class IRCMessage {
// Store the emote word and its associated URL for later use.
localEmotes[emoteWord] = Emote(
id: emoteId,
name: emoteWord,
zeroWidth: false,
url: 'https://static-cdn.jtvnw.net/emoticons/v2/$emoteId/default/dark/3.0',

View file

@ -25,7 +25,7 @@ abstract class _ChatAssetsStoreBase with Store {
List<Emote> get sevenTvEmotes => _emoteToObject.values.where((emote) => is7TV(emote)).toList();
/// The map of badges ids to their object representation.
final twitchBadgesToObject = ObservableMap<String, BadgeInfoTwitch>();
final twitchBadgesToObject = ObservableMap<String, Badge>();
/// The map of emote words to their image or GIF URL. May be used by anyone in the chat.
@readonly
@ -35,17 +35,17 @@ abstract class _ChatAssetsStoreBase with Store {
@readonly
var _userEmoteToObject = <String, Emote>{};
/// The map of usernames to their FFZ badges.
/// The map of user IDs to their FFZ badges.
@readonly
var _userToFFZBadges = <String, List<BadgeInfoFFZ>>{};
var _userToFFZBadges = <String, List<Badge>>{};
/// The map of usernames to their 7TV badges.
/// The map of user IDs to their 7TV badges.
@readonly
var _userTo7TVBadges = <String, List<BadgeInfo7TV>>{};
var _userTo7TVBadges = <String, List<Badge>>{};
/// The map of usernames to their BTTV badge.
/// The map of user IDs to their BTTV badge.
@readonly
var _userToBTTVBadges = <String, BadgeInfoBTTV>{};
var _userToBTTVBadges = <String, Badge>{};
/// The current index of the emote menu stack.
@observable

View file

@ -70,16 +70,16 @@ mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store {
final _$_userToFFZBadgesAtom =
Atom(name: '_ChatAssetsStoreBase._userToFFZBadges');
Map<String, List<BadgeInfoFFZ>> get userToFFZBadges {
Map<String, List<Badge>> get userToFFZBadges {
_$_userToFFZBadgesAtom.reportRead();
return super._userToFFZBadges;
}
@override
Map<String, List<BadgeInfoFFZ>> get _userToFFZBadges => userToFFZBadges;
Map<String, List<Badge>> get _userToFFZBadges => userToFFZBadges;
@override
set _userToFFZBadges(Map<String, List<BadgeInfoFFZ>> value) {
set _userToFFZBadges(Map<String, List<Badge>> value) {
_$_userToFFZBadgesAtom.reportWrite(value, super._userToFFZBadges, () {
super._userToFFZBadges = value;
});
@ -88,16 +88,16 @@ mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store {
final _$_userTo7TVBadgesAtom =
Atom(name: '_ChatAssetsStoreBase._userTo7TVBadges');
Map<String, List<BadgeInfo7TV>> get userTo7TVBadges {
Map<String, List<Badge>> get userTo7TVBadges {
_$_userTo7TVBadgesAtom.reportRead();
return super._userTo7TVBadges;
}
@override
Map<String, List<BadgeInfo7TV>> get _userTo7TVBadges => userTo7TVBadges;
Map<String, List<Badge>> get _userTo7TVBadges => userTo7TVBadges;
@override
set _userTo7TVBadges(Map<String, List<BadgeInfo7TV>> value) {
set _userTo7TVBadges(Map<String, List<Badge>> value) {
_$_userTo7TVBadgesAtom.reportWrite(value, super._userTo7TVBadges, () {
super._userTo7TVBadges = value;
});
@ -106,16 +106,16 @@ mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store {
final _$_userToBTTVBadgesAtom =
Atom(name: '_ChatAssetsStoreBase._userToBTTVBadges');
Map<String, BadgeInfoBTTV> get userToBTTVBadges {
Map<String, Badge> get userToBTTVBadges {
_$_userToBTTVBadgesAtom.reportRead();
return super._userToBTTVBadges;
}
@override
Map<String, BadgeInfoBTTV> get _userToBTTVBadges => userToBTTVBadges;
Map<String, Badge> get _userToBTTVBadges => userToBTTVBadges;
@override
set _userToBTTVBadges(Map<String, BadgeInfoBTTV> value) {
set _userToBTTVBadges(Map<String, Badge> value) {
_$_userToBTTVBadgesAtom.reportWrite(value, super._userToBTTVBadges, () {
super._userToBTTVBadges = value;
});