Migrate from BTTV's FFZ API to FFZ's official API
This commit is contained in:
parent
5d321b7c00
commit
e768a05c37
4 changed files with 139 additions and 102 deletions
|
|
@ -3,16 +3,23 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:frosty/models/badges.dart';
|
||||
import 'package:frosty/models/emotes.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class FFZ {
|
||||
/// Returns a map of global FFZ emotes to their URL.
|
||||
static Future<List<Emote>> getEmotesGlobal() async {
|
||||
final url = Uri.parse('https://api.betterttv.net/3/cached/frankerfacez/emotes/global');
|
||||
final url = Uri.parse('https://api.frankerfacez.com/v1/set/global');
|
||||
final response = await http.get(url);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body) as List;
|
||||
final emotes = decoded.map((emote) => EmoteFFZ.fromJson(emote));
|
||||
final decoded = jsonDecode(response.body);
|
||||
final defaultSets = decoded['default_sets'] as List;
|
||||
|
||||
final emotes = <EmoteFFZ>[];
|
||||
for (final setId in defaultSets) {
|
||||
final emoticons = decoded['sets'][setId.toString()]['emoticons'] as List;
|
||||
emotes.addAll(emoticons.map((emote) => EmoteFFZ.fromJson(emote)));
|
||||
}
|
||||
|
||||
return emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzGlobal)).toList();
|
||||
} else {
|
||||
|
|
@ -21,60 +28,44 @@ class FFZ {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a map of a channel's FFZ emotes to their URL.
|
||||
static Future<List<Emote>> getEmotesChannel({required String id}) async {
|
||||
final url = Uri.parse('https://api.betterttv.net/3/cached/frankerfacez/users/twitch/$id');
|
||||
final response = await http.get(url);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body) as List;
|
||||
final emotes = decoded.map((emote) => EmoteFFZ.fromJson(emote));
|
||||
|
||||
return emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzChannel)).toList();
|
||||
} else {
|
||||
debugPrint('Failed to get FFZ emotes for id: $id. Error code: ${response.statusCode}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, List<BadgeInfoFFZ>>?> getBadges() async {
|
||||
final url = Uri.parse('https://api.frankerfacez.com/v1/badges');
|
||||
/// Returns a channel's FFZ room info including custom badges and emote used.
|
||||
static Future<Tuple2<RoomFFZ, List<Emote>>?> getRoomInfo({required String name}) async {
|
||||
final url = Uri.parse('https://api.frankerfacez.com/v1/room/$name');
|
||||
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 roomInfo = RoomFFZ.fromJson(decoded['room']);
|
||||
final emoticons = decoded['sets'][roomInfo.set.toString()]['emoticons'] as List;
|
||||
|
||||
final emotes = emoticons.map((emote) => EmoteFFZ.fromJson(emote));
|
||||
|
||||
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}');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, List<BadgeInfoFFZ>>?> getBadges() async {
|
||||
final url = Uri.parse('https://api.frankerfacez.com/v1/badges/ids');
|
||||
final response = await http.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<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]];
|
||||
for (final badge in badgeObjects.reversed) {
|
||||
for (final userId in decoded['users'][badge.id.toString()]) {
|
||||
final entry = result[userId.toString()];
|
||||
if (entry == null) {
|
||||
result[userId.toString()] = [badge];
|
||||
} else {
|
||||
entry.add(badge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,28 +106,32 @@ class EmoteBTTVChannel {
|
|||
}
|
||||
|
||||
// * FFZ Emotes *
|
||||
@JsonSerializable(createToJson: false)
|
||||
class UserFFZ {
|
||||
final int id;
|
||||
final String name;
|
||||
final String displayName;
|
||||
@JsonSerializable(createToJson: false, fieldRename: FieldRename.snake)
|
||||
class RoomFFZ {
|
||||
final int set;
|
||||
final String? moderatorBadge;
|
||||
final ImagesFFZ? vipBadge;
|
||||
final ImagesFFZ? modUrls;
|
||||
// final Map<String, List<String>> userBadges;
|
||||
// final Map<String, List<int>> userBadgesIds;
|
||||
|
||||
const UserFFZ(
|
||||
this.id,
|
||||
this.name,
|
||||
this.displayName,
|
||||
const RoomFFZ(
|
||||
this.set,
|
||||
this.moderatorBadge,
|
||||
this.vipBadge,
|
||||
this.modUrls,
|
||||
);
|
||||
|
||||
factory UserFFZ.fromJson(Map<String, dynamic> json) => _$UserFFZFromJson(json);
|
||||
factory RoomFFZ.fromJson(Map<String, dynamic> json) => _$RoomFFZFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class ImagesFFZ {
|
||||
@JsonKey(name: '1x')
|
||||
@JsonKey(name: '1')
|
||||
final String url1x;
|
||||
@JsonKey(name: '2x')
|
||||
@JsonKey(name: '2')
|
||||
final String? url2x;
|
||||
@JsonKey(name: '4x')
|
||||
@JsonKey(name: '4')
|
||||
final String? url4x;
|
||||
|
||||
const ImagesFFZ(
|
||||
|
|
@ -142,22 +146,40 @@ class ImagesFFZ {
|
|||
@JsonSerializable(createToJson: false)
|
||||
class EmoteFFZ {
|
||||
final int id;
|
||||
final UserFFZ user;
|
||||
final String code;
|
||||
final ImagesFFZ images;
|
||||
final String imageType;
|
||||
final String name;
|
||||
final int height;
|
||||
final int width;
|
||||
final OwnerFFZ owner;
|
||||
final ImagesFFZ urls;
|
||||
|
||||
const EmoteFFZ(
|
||||
this.id,
|
||||
this.user,
|
||||
this.code,
|
||||
this.images,
|
||||
this.imageType,
|
||||
this.name,
|
||||
this.height,
|
||||
this.width,
|
||||
this.owner,
|
||||
this.urls,
|
||||
);
|
||||
|
||||
factory EmoteFFZ.fromJson(Map<String, dynamic> json) => _$EmoteFFZFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false, fieldRename: FieldRename.snake)
|
||||
class OwnerFFZ {
|
||||
@JsonKey(name: '_id')
|
||||
final int id;
|
||||
final String name;
|
||||
final String displayName;
|
||||
|
||||
const OwnerFFZ(
|
||||
this.id,
|
||||
this.name,
|
||||
this.displayName,
|
||||
);
|
||||
|
||||
factory OwnerFFZ.fromJson(Map<String, dynamic> json) => _$OwnerFFZFromJson(json);
|
||||
}
|
||||
|
||||
// * 7TV Emotes *
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
|
|
@ -272,9 +294,9 @@ class Emote {
|
|||
|
||||
factory Emote.fromFFZ(EmoteFFZ emote, EmoteType type) => Emote(
|
||||
id: emote.id.toString(),
|
||||
name: emote.code,
|
||||
name: emote.name,
|
||||
zeroWidth: false,
|
||||
url: emote.images.url4x ?? emote.images.url2x ?? emote.images.url1x,
|
||||
url: 'https:' + (emote.urls.url4x ?? emote.urls.url2x ?? emote.urls.url1x),
|
||||
type: type,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -54,24 +54,36 @@ EmoteBTTVChannel _$EmoteBTTVChannelFromJson(Map<String, dynamic> json) =>
|
|||
.toList(),
|
||||
);
|
||||
|
||||
UserFFZ _$UserFFZFromJson(Map<String, dynamic> json) => UserFFZ(
|
||||
json['id'] as int,
|
||||
json['name'] as String,
|
||||
json['displayName'] as String,
|
||||
RoomFFZ _$RoomFFZFromJson(Map<String, dynamic> json) => RoomFFZ(
|
||||
json['set'] as int,
|
||||
json['moderator_badge'] as String?,
|
||||
json['vip_badge'] == null
|
||||
? null
|
||||
: ImagesFFZ.fromJson(json['vip_badge'] as Map<String, dynamic>),
|
||||
json['mod_urls'] == null
|
||||
? null
|
||||
: ImagesFFZ.fromJson(json['mod_urls'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
ImagesFFZ _$ImagesFFZFromJson(Map<String, dynamic> json) => ImagesFFZ(
|
||||
json['1x'] as String,
|
||||
json['2x'] as String?,
|
||||
json['4x'] as String?,
|
||||
json['1'] as String,
|
||||
json['2'] as String?,
|
||||
json['4'] as String?,
|
||||
);
|
||||
|
||||
EmoteFFZ _$EmoteFFZFromJson(Map<String, dynamic> json) => EmoteFFZ(
|
||||
json['id'] as int,
|
||||
UserFFZ.fromJson(json['user'] as Map<String, dynamic>),
|
||||
json['code'] as String,
|
||||
ImagesFFZ.fromJson(json['images'] as Map<String, dynamic>),
|
||||
json['imageType'] as String,
|
||||
json['name'] as String,
|
||||
json['height'] as int,
|
||||
json['width'] as int,
|
||||
OwnerFFZ.fromJson(json['owner'] as Map<String, dynamic>),
|
||||
ImagesFFZ.fromJson(json['urls'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
OwnerFFZ _$OwnerFFZFromJson(Map<String, dynamic> json) => OwnerFFZ(
|
||||
json['_id'] as int,
|
||||
json['name'] as String,
|
||||
json['display_name'] as String,
|
||||
);
|
||||
|
||||
Role7TV _$Role7TVFromJson(Map<String, dynamic> json) => Role7TV(
|
||||
|
|
|
|||
|
|
@ -182,34 +182,46 @@ void main() {
|
|||
test("emote should parse correctly", () {
|
||||
const sampleJson = """
|
||||
{
|
||||
"id": 317897,
|
||||
"user": {
|
||||
"id": 84534,
|
||||
"name": "vulpeshd",
|
||||
"displayName": "VulpesHD"
|
||||
"id": 27081,
|
||||
"name": "ZreknarF",
|
||||
"height": 30,
|
||||
"width": 40,
|
||||
"public": false,
|
||||
"hidden": false,
|
||||
"modifier": false,
|
||||
"offset": null,
|
||||
"margins": null,
|
||||
"css": null,
|
||||
"owner": {
|
||||
"_id": 1,
|
||||
"name": "sirstendec",
|
||||
"display_name": "SirStendec"
|
||||
},
|
||||
"code": "peepoPog",
|
||||
"images": {
|
||||
"1x": "https://cdn.betterttv.net/frankerfacez_emote/317897/1",
|
||||
"2x": "https://cdn.betterttv.net/frankerfacez_emote/317897/2",
|
||||
"4x": null
|
||||
"urls": {
|
||||
"1": "//cdn.frankerfacez.com/emote/27081/1",
|
||||
"2": "//cdn.frankerfacez.com/emote/27081/2",
|
||||
"4": "//cdn.frankerfacez.com/emote/27081/4"
|
||||
},
|
||||
"imageType": "png"
|
||||
"status": 1,
|
||||
"usage_count": 1,
|
||||
"created_at": "2015-06-01T05:52:57.770Z",
|
||||
"last_updated": "2015-06-01T05:53:03.744Z"
|
||||
}
|
||||
""";
|
||||
|
||||
final json = jsonDecode(sampleJson);
|
||||
final emote = EmoteFFZ.fromJson(json);
|
||||
|
||||
expect(emote.id, 317897);
|
||||
expect(emote.user.id, 84534);
|
||||
expect(emote.user.name, "vulpeshd");
|
||||
expect(emote.user.displayName, "VulpesHD");
|
||||
expect(emote.code, "peepoPog");
|
||||
expect(emote.images.url1x, "https://cdn.betterttv.net/frankerfacez_emote/317897/1");
|
||||
expect(emote.images.url2x, "https://cdn.betterttv.net/frankerfacez_emote/317897/2");
|
||||
expect(emote.images.url4x, null);
|
||||
expect(emote.imageType, "png");
|
||||
expect(emote.id, 27081);
|
||||
expect(emote.height, 30);
|
||||
expect(emote.width, 40);
|
||||
expect(emote.name, 'ZreknarF');
|
||||
expect(emote.owner.id, 1);
|
||||
expect(emote.owner.name, 'sirstendec');
|
||||
expect(emote.owner.displayName, 'SirStendec');
|
||||
expect(emote.urls.url1x, '//cdn.frankerfacez.com/emote/27081/1');
|
||||
expect(emote.urls.url2x, '//cdn.frankerfacez.com/emote/27081/2');
|
||||
expect(emote.urls.url4x, '//cdn.frankerfacez.com/emote/27081/4');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue