mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-20 05:28:05 +02:00
Merge pull request #18 from R0m1k3/antigravity
feat: add Xtream Codes API service and data models for IPTV integration.
This commit is contained in:
2 files changed
+144
-12
No files matched your search
@@ -38,6 +38,7 @@ class Movie {
|
|||||||
final String name;
|
final String name;
|
||||||
final String? streamIcon;
|
final String? streamIcon;
|
||||||
final String? categoryId;
|
final String? categoryId;
|
||||||
|
final String categoryName;
|
||||||
final String? containerExtension;
|
final String? containerExtension;
|
||||||
final String? rating;
|
final String? rating;
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ class Movie {
|
|||||||
required this.name,
|
required this.name,
|
||||||
this.streamIcon,
|
this.streamIcon,
|
||||||
this.categoryId,
|
this.categoryId,
|
||||||
|
this.categoryName = '',
|
||||||
this.containerExtension,
|
this.containerExtension,
|
||||||
this.rating,
|
this.rating,
|
||||||
});
|
});
|
||||||
@@ -56,6 +58,7 @@ class Movie {
|
|||||||
name: json['name']?.toString() ?? 'Unknown',
|
name: json['name']?.toString() ?? 'Unknown',
|
||||||
streamIcon: json['stream_icon']?.toString(),
|
streamIcon: json['stream_icon']?.toString(),
|
||||||
categoryId: json['category_id']?.toString(),
|
categoryId: json['category_id']?.toString(),
|
||||||
|
categoryName: json['category_name']?.toString() ?? '',
|
||||||
containerExtension: json['container_extension']?.toString() ?? 'mp4',
|
containerExtension: json['container_extension']?.toString() ?? 'mp4',
|
||||||
rating: json['rating']?.toString(),
|
rating: json['rating']?.toString(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -96,6 +96,39 @@ class XtreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load categories mapping (category_id -> category_name)
|
||||||
|
Future<Map<String, String>> _getLiveCategories() async {
|
||||||
|
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _dio.get(
|
||||||
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
|
queryParameters: {
|
||||||
|
'username': _currentPlaylist!.username,
|
||||||
|
'password': _currentPlaylist!.password,
|
||||||
|
'action': 'get_live_categories',
|
||||||
|
},
|
||||||
|
options: Options(extra: _cacheOptions.toExtra()),
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<dynamic> categories = response.data as List<dynamic>;
|
||||||
|
final Map<String, String> categoryMap = {};
|
||||||
|
|
||||||
|
for (final cat in categories) {
|
||||||
|
final catData = cat as Map<String, dynamic>;
|
||||||
|
final id = catData['category_id']?.toString() ?? '';
|
||||||
|
final name = catData['category_name']?.toString() ?? 'Unknown';
|
||||||
|
if (id.isNotEmpty) {
|
||||||
|
categoryMap[id] = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryMap;
|
||||||
|
} catch (e) {
|
||||||
|
return {}; // Return empty map on error, channels will be "Uncategorized"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all live TV channels grouped by category
|
/// Get all live TV channels grouped by category
|
||||||
///
|
///
|
||||||
/// Returns a Map where key is category name and value is list of channels
|
/// Returns a Map where key is category name and value is list of channels
|
||||||
@@ -103,6 +136,9 @@ class XtreamService {
|
|||||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// First, load categories to get the mapping
|
||||||
|
final categoryMap = await _getLiveCategories();
|
||||||
|
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@@ -117,8 +153,15 @@ class XtreamService {
|
|||||||
final Map<String, List<Channel>> groupedChannels = {};
|
final Map<String, List<Channel>> groupedChannels = {};
|
||||||
|
|
||||||
for (final streamData in streams) {
|
for (final streamData in streams) {
|
||||||
final channel = Channel.fromJson(streamData as Map<String, dynamic>);
|
final data = streamData as Map<String, dynamic>;
|
||||||
final categoryName = channel.categoryName.isEmpty ? 'Uncategorized' : channel.categoryName;
|
// Get category name from our mapping using category_id
|
||||||
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
|
final categoryName = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
|
|
||||||
|
// Inject category_name into data before parsing
|
||||||
|
data['category_name'] = categoryName;
|
||||||
|
|
||||||
|
final channel = Channel.fromJson(data);
|
||||||
|
|
||||||
if (!groupedChannels.containsKey(categoryName)) {
|
if (!groupedChannels.containsKey(categoryName)) {
|
||||||
groupedChannels[categoryName] = [];
|
groupedChannels[categoryName] = [];
|
||||||
@@ -132,11 +175,44 @@ class XtreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load VOD categories mapping
|
||||||
|
Future<Map<String, String>> _getVodCategories() async {
|
||||||
|
if (_currentPlaylist == null) return {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _dio.get(
|
||||||
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
|
queryParameters: {
|
||||||
|
'username': _currentPlaylist!.username,
|
||||||
|
'password': _currentPlaylist!.password,
|
||||||
|
'action': 'get_vod_categories',
|
||||||
|
},
|
||||||
|
options: Options(extra: _cacheOptions.toExtra()),
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<dynamic> categories = response.data as List<dynamic>;
|
||||||
|
final Map<String, String> categoryMap = {};
|
||||||
|
|
||||||
|
for (final cat in categories) {
|
||||||
|
final catData = cat as Map<String, dynamic>;
|
||||||
|
final id = catData['category_id']?.toString() ?? '';
|
||||||
|
final name = catData['category_name']?.toString() ?? 'Unknown';
|
||||||
|
if (id.isNotEmpty) categoryMap[id] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryMap;
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all VOD items (movies) grouped by category
|
/// Get all VOD items (movies) grouped by category
|
||||||
Future<Map<String, List<VodItem>>> getVodItems() async {
|
Future<Map<String, List<VodItem>>> getVodItems() async {
|
||||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final categoryMap = await _getVodCategories();
|
||||||
|
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@@ -151,8 +227,12 @@ class XtreamService {
|
|||||||
final Map<String, List<VodItem>> groupedVods = {};
|
final Map<String, List<VodItem>> groupedVods = {};
|
||||||
|
|
||||||
for (final vodData in vods) {
|
for (final vodData in vods) {
|
||||||
final vod = VodItem.fromJson(vodData as Map<String, dynamic>);
|
final data = vodData as Map<String, dynamic>;
|
||||||
final categoryName = vod.categoryName.isEmpty ? 'Uncategorized' : vod.categoryName;
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
|
final categoryName = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
|
data['category_name'] = categoryName;
|
||||||
|
|
||||||
|
final vod = VodItem.fromJson(data);
|
||||||
|
|
||||||
if (!groupedVods.containsKey(categoryName)) {
|
if (!groupedVods.containsKey(categoryName)) {
|
||||||
groupedVods[categoryName] = [];
|
groupedVods[categoryName] = [];
|
||||||
@@ -166,11 +246,44 @@ class XtreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load Series categories mapping
|
||||||
|
Future<Map<String, String>> _getSeriesCategories() async {
|
||||||
|
if (_currentPlaylist == null) return {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _dio.get(
|
||||||
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
|
queryParameters: {
|
||||||
|
'username': _currentPlaylist!.username,
|
||||||
|
'password': _currentPlaylist!.password,
|
||||||
|
'action': 'get_series_categories',
|
||||||
|
},
|
||||||
|
options: Options(extra: _cacheOptions.toExtra()),
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<dynamic> categories = response.data as List<dynamic>;
|
||||||
|
final Map<String, String> categoryMap = {};
|
||||||
|
|
||||||
|
for (final cat in categories) {
|
||||||
|
final catData = cat as Map<String, dynamic>;
|
||||||
|
final id = catData['category_id']?.toString() ?? '';
|
||||||
|
final name = catData['category_name']?.toString() ?? 'Unknown';
|
||||||
|
if (id.isNotEmpty) categoryMap[id] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryMap;
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all series grouped by category
|
/// Get all series grouped by category
|
||||||
Future<Map<String, List<Series>>> getSeries() async {
|
Future<Map<String, List<Series>>> getSeries() async {
|
||||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final categoryMap = await _getSeriesCategories();
|
||||||
|
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@@ -185,8 +298,12 @@ class XtreamService {
|
|||||||
final Map<String, List<Series>> groupedSeries = {};
|
final Map<String, List<Series>> groupedSeries = {};
|
||||||
|
|
||||||
for (final seriesData in seriesList) {
|
for (final seriesData in seriesList) {
|
||||||
final series = Series.fromJson(seriesData as Map<String, dynamic>);
|
final data = seriesData as Map<String, dynamic>;
|
||||||
final categoryName = series.categoryName.isEmpty ? 'Uncategorized' : series.categoryName;
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
|
final categoryName = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
|
data['category_name'] = categoryName;
|
||||||
|
|
||||||
|
final series = Series.fromJson(data);
|
||||||
|
|
||||||
if (!groupedSeries.containsKey(categoryName)) {
|
if (!groupedSeries.containsKey(categoryName)) {
|
||||||
groupedSeries[categoryName] = [];
|
groupedSeries[categoryName] = [];
|
||||||
@@ -205,6 +322,9 @@ class XtreamService {
|
|||||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Load categories for mapping
|
||||||
|
final categoryMap = await _getVodCategories();
|
||||||
|
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@@ -223,9 +343,12 @@ class XtreamService {
|
|||||||
|
|
||||||
final paginatedMovies = allMovies.sublist(offset, endIndex);
|
final paginatedMovies = allMovies.sublist(offset, endIndex);
|
||||||
|
|
||||||
return paginatedMovies
|
return paginatedMovies.map((movieData) {
|
||||||
.map((movieData) => xm.Movie.fromJson(movieData as Map<String, dynamic>))
|
final data = movieData as Map<String, dynamic>;
|
||||||
.toList();
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
|
data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
|
return xm.Movie.fromJson(data);
|
||||||
|
}).toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Failed to fetch movies: $e');
|
throw Exception('Failed to fetch movies: $e');
|
||||||
}
|
}
|
||||||
@@ -236,6 +359,9 @@ class XtreamService {
|
|||||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Load categories for mapping
|
||||||
|
final categoryMap = await _getSeriesCategories();
|
||||||
|
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@@ -254,9 +380,12 @@ class XtreamService {
|
|||||||
|
|
||||||
final paginatedSeries = allSeries.sublist(offset, endIndex);
|
final paginatedSeries = allSeries.sublist(offset, endIndex);
|
||||||
|
|
||||||
return paginatedSeries
|
return paginatedSeries.map((seriesData) {
|
||||||
.map((seriesData) => xm.Series.fromJson(seriesData as Map<String, dynamic>))
|
final data = seriesData as Map<String, dynamic>;
|
||||||
.toList();
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
|
data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
|
return xm.Series.fromJson(data);
|
||||||
|
}).toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Failed to fetch series: $e');
|
throw Exception('Failed to fetch series: $e');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user