Fix emoji sizing, add common badge model
This commit is contained in:
parent
89443247fe
commit
09edf1cb1d
9 changed files with 124 additions and 61 deletions
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue