Add handler script for the IMDb API limit error

This commit is contained in:
Erfan Rahmati
2022-08-05 11:16:04 +04:30
parent eafe095acd
commit ce184b52ca
17 changed files with 270 additions and 84 deletions

View File

@ -0,0 +1,57 @@
// Movie or TV show as result of a search model class
class ExternalSites {
final String officialWebsite;
final String imDb;
final String theMovieDb;
final String rottenTomatoes;
final String metacritic;
final String netflix;
final String googlePlay;
final String filmAffinity;
final String freebase;
final String wikipedia;
const ExternalSites({
required this.officialWebsite,
required this.imDb,
required this.theMovieDb,
required this.rottenTomatoes,
required this.metacritic,
required this.netflix,
required this.googlePlay,
required this.filmAffinity,
required this.freebase,
required this.wikipedia,
});
factory ExternalSites.fromJson(Map<String, dynamic> json) {
return ExternalSites(
officialWebsite: json['officialWebsite'] ?? '',
imDb: json['imDb']?['url'] ?? '',
theMovieDb: json['theMovieDb']?['url'] ?? '',
rottenTomatoes: json['rottenTomatoes']?['url'] ?? '',
metacritic: json['metacritic']?['url'] ?? '',
netflix: json['netflix']?['url'] ?? '',
googlePlay: json['googlePlay']?['url'] ?? '',
filmAffinity: json['filmAffinity']?['url'] ?? '',
freebase: json['freebase']?['url'] ?? '',
wikipedia: json['wikipediaUrls']?[0]?['url'] ?? '',
);
}
}
List<Map<String, String>> externalSitesList = [
{'title': "HomePage", 'imgUrl': ""},
{'title': "IMDb", 'imgUrl': "assets/images/logos/IMDb.png"},
{'title': "TMDb", 'imgUrl': "assets/images/logos/TMDb.png"},
{
'title': "Rotten Tomatoes",
'imgUrl': "assets/images/logos/RottenTomatoes.png"
},
{'title': "Metacritic", 'imgUrl': "assets/images/logos/Metacritic.png"},
{'title': "Netflix", 'imgUrl': "assets/images/logos/Netflix.png"},
{'title': "Google Play", 'imgUrl': "assets/images/logos/GooglePlay.png"},
{'title': "Film Affinity", 'imgUrl': "assets/images/logos/FilmAffinity.png"},
{'title': "Freebase", 'imgUrl': "assets/images/logos/Freebase.png"},
{'title': "Wikipedia", 'imgUrl': "assets/images/logos/Wikipedia.png"},
];

View File

@ -3,30 +3,26 @@ import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:movielab/constants/types.dart';
import 'package:movielab/models/external_sites_model.dart';
import 'package:movielab/modules/cache/cacheholder.dart';
import 'package:movielab/pages/main/home/home_data_controller.dart';
import 'package:movielab/pages/main/search/search_bar/search_bar_controller.dart';
import '../models/actor_models/full_actor_model.dart';
import '../models/episode_model.dart';
import '../models/search_result_model.dart';
import '../models/show_models/full_show_model.dart';
import '../models/show_models/show_preview_model.dart';
import '../../models/actor_models/full_actor_model.dart';
import '../../models/episode_model.dart';
import '../../models/search_result_model.dart';
import '../../models/show_models/full_show_model.dart';
import '../../models/show_models/show_preview_model.dart';
class APIRequester {
static const String imdbBaseUrl = 'https://imdb-api.com/en/API';
// API keys to access the IMDB API:
static const String apiKey = "k_6lgd4s89";
// static const String apiKey = "k_y9zcdoq3";
// static const String apiKey = "";
static const List<String> apiKey = ["k_zld7q6q3", "k_y9zcdoq3", "k_6lgd4s89"];
static int activeApiKey = 0;
// Get recently trending movies from the IMDB API
Future<RequestResult> getTrendingMovies() async {
final response = await http
.get(Uri.parse('$imdbBaseUrl/MostPopularMovies/$apiKey'))
.timeout(
const Duration(seconds: 10),
);
final response = await getUrl(
url: '$imdbBaseUrl/MostPopularMovies/${apiKey[activeApiKey]}');
if (response.statusCode == 200) {
if (jsonDecode(response.body)["errorMessage"] != "") {
return RequestResult.FAILURE_SERVER_PROBLEM;
@ -46,9 +42,8 @@ class APIRequester {
// Get recently trending TV shows from the IMDB API
Future<RequestResult> getTrendingTVShows() async {
final response = await http
.get(Uri.parse('$imdbBaseUrl/MostPopularTVs/$apiKey'))
.timeout(const Duration(seconds: 10));
final response = await getUrl(
url: '$imdbBaseUrl/MostPopularTVs/${apiKey[activeApiKey]}');
if (response.statusCode == 200) {
if (jsonDecode(response.body)["errorMessage"] != "") {
@ -74,24 +69,20 @@ class APIRequester {
http.Response response;
switch (listName) {
case ImdbList.TOP_250_MOVIES:
response = await http
.get(Uri.parse('$imdbBaseUrl/Top250Movies/$apiKey'))
.timeout(const Duration(seconds: 10));
response = await getUrl(
url: '$imdbBaseUrl/Top250Movies/${apiKey[activeApiKey]}');
break;
case ImdbList.TOP_250_TVS:
response = await http
.get(Uri.parse('$imdbBaseUrl/Top250TVs/$apiKey'))
.timeout(const Duration(seconds: 10));
response =
await getUrl(url: '$imdbBaseUrl/Top250TVs/${apiKey[activeApiKey]}');
break;
case ImdbList.BoxOffice:
response = await http
.get(Uri.parse('$imdbBaseUrl/BoxOffice/$apiKey'))
.timeout(const Duration(seconds: 10));
response =
await getUrl(url: '$imdbBaseUrl/BoxOffice/${apiKey[activeApiKey]}');
break;
case ImdbList.AllTimeBoxOffice:
response = await http
.get(Uri.parse('$imdbBaseUrl/BoxOfficeAllTime/$apiKey'))
.timeout(const Duration(seconds: 10));
response = await getUrl(
url: '$imdbBaseUrl/BoxOfficeAllTime/${apiKey[activeApiKey]}');
break;
}
if (response.statusCode == 200) {
@ -123,9 +114,8 @@ class APIRequester {
// Get a company's movies from the IMDB API
Future<Map?> getCompany({required String id}) async {
final response = await http
.get(Uri.parse('$imdbBaseUrl/Company/$apiKey/$id'))
.timeout(const Duration(seconds: 10));
final response =
await getUrl(url: '$imdbBaseUrl/Company/${apiKey[activeApiKey]}/$id');
if (response.statusCode == 200) {
var json = jsonDecode(response.body)["items"];
List<ShowPreview> companyMovies = [];
@ -144,8 +134,8 @@ class APIRequester {
// Get results of a search query from the IMDB API
Future<bool> search({expression}) async {
expression ??= Get.find<SearchBarController>().fieldText;
final response =
await http.get(Uri.parse('$imdbBaseUrl/SearchAll/$apiKey/$expression'));
final response = await getUrl(
url: '$imdbBaseUrl/SearchAll/${apiKey[activeApiKey]}/$expression');
if (response.statusCode == 200) {
var json = jsonDecode(response.body)["results"];
@ -162,8 +152,10 @@ class APIRequester {
// Get full details of a show from the IMDB API
Future<FullShow?> getShow({required String id}) async {
final response = await http.get(Uri.parse(
'$imdbBaseUrl/Title/$apiKey/$id/Posters,Images,Trailer,Ratings,'));
final response = await getUrl(
url:
'$imdbBaseUrl/Title/${apiKey[activeApiKey]}/$id/Posters,Images,Trailer,Ratings,');
if (response.statusCode == 200) {
var showJson = jsonDecode(response.body);
FullShow show = FullShow.fromJson(showJson);
@ -177,8 +169,10 @@ class APIRequester {
Future<FullShow?> getShowEpisodes(
{required dynamic show, required int season}) async {
final cacheHolder = CacheHolder();
final response = await http.get(
Uri.parse('$imdbBaseUrl/SeasonEpisodes/$apiKey/${show.id}/$season'));
final response = await getUrl(
url:
'$imdbBaseUrl/SeasonEpisodes/${apiKey[activeApiKey]}/${show.id}/$season');
if (response.statusCode == 200) {
var json = jsonDecode(response.body)["episodes"];
List<Episode> seasonEpisodes = [];
@ -198,7 +192,9 @@ class APIRequester {
// Get full details of a show from the IMDB API
Future<FullActor?> getActor({required String id}) async {
final response = await http.get(Uri.parse('$imdbBaseUrl/Name/$apiKey/$id'));
final response =
await getUrl(url: '$imdbBaseUrl/Name/${apiKey[activeApiKey]}/$id');
if (response.statusCode == 200) {
var actorJson = jsonDecode(response.body);
FullActor actor = FullActor.fromJson(actorJson);
@ -207,4 +203,41 @@ class APIRequester {
return null;
}
}
// Get external sites of a show from the IMDB API
Future<ExternalSites?> getExternalSites({required String id}) async {
final response = await getUrl(
url: '$imdbBaseUrl/ExternalSites/${apiKey[activeApiKey]}/$id');
if (response.statusCode == 200) {
var json = jsonDecode(response.body);
late ExternalSites externalSites;
externalSites = ExternalSites.fromJson(json);
return externalSites;
} else {
return null;
}
}
Future getUrl({required String url}) async {
var response = await http.get(Uri.parse(url)).timeout(
const Duration(seconds: 10),
);
if (jsonDecode(response.body)['errorMessage'] != "" &&
jsonDecode(response.body)['errorMessage'] != null) {
// Here we handle the IMDb API limit error
// If the API key is invalid, change it to the next one
if (kDebugMode) {
activeApiKey++;
print("Server error: ${jsonDecode(response.body)['errorMessage']}");
print("activeApiKey changed to: $activeApiKey");
}
await getUrl(
url: url.replaceAll(
apiKey[activeApiKey - 1], apiKey[activeApiKey]))
.then((value) {
response = value;
});
}
return response;
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/modules/cache/cacheholder.dart';
import '../models/show_models/full_show_model.dart';

View File

@ -1,5 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/modules/cache/cacheholder.dart';
import '../../../models/actor_models/full_actor_model.dart';

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:movielab/constants/types.dart';
import 'package:movielab/pages/main/home/sections/box_office/pages/all_time.dart';
import 'package:movielab/pages/main/home/sections/box_office/pages/box_office.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import '../../../../../modules/navigate.dart';
import 'box.dart';

View File

@ -1,5 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/modules/cache/cacheholder.dart';
Future<Map?> getCompanyInfo({required String id}) async {

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:movielab/constants/types.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/pages/main/home/sections/imdb_lists/pages/top_tvs.dart';
import 'package:movielab/pages/main/home/sections/imdb_lists/pages/top_movies.dart';
import '../../../../../modules/navigate.dart';

View File

@ -42,4 +42,12 @@ class MainController extends GetxController {
ScrollController searchScrollController = ScrollController();
ScrollController collectionScrollController = ScrollController();
ScrollController profileScrollController = ScrollController();
// Active API key controller
int activeApiKey = 0;
changeActiveApiKey(int index) {
activeApiKey = index;
update();
}
}

View File

@ -1,6 +1,6 @@
import 'package:get/get.dart';
import 'package:movielab/constants/types.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/pages/main/search/search_bar/search_bar_controller.dart';
doSearch() async {

View File

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:movielab/constants/colors.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/pages/main/search/search.dart';
import 'package:movielab/pages/main/search/search_bar/search_bar_controller.dart';
import 'package:speech_to_text/speech_to_text.dart' as stt;

View File

@ -5,7 +5,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:movielab/constants/colors.dart';
import 'package:movielab/models/show_models/full_show_model.dart';
import 'package:share_plus/share_plus.dart';
import 'sections/external_sites/external_sites.dart';
import 'sections/lists_info/lists_info.dart';
class ShowPageBottonBar extends StatefulWidget {
@ -92,22 +91,7 @@ class _ShowPageBottonBarState extends State<ShowPageBottonBar>
),
),
onTap: () {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
)),
clipBehavior: Clip.antiAliasWithSaveLayer,
backgroundColor: kSecondaryColor,
transitionAnimationController: AnimationController(
duration: const Duration(milliseconds: 300), vsync: this),
builder: (context) {
return ShowPageExternalSites(
show: widget.show,
);
},
);
Scaffold.of(context).openEndDrawer();
},
),
const SizedBox(

View File

@ -1,18 +1,115 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:movielab/constants/colors.dart';
import 'package:movielab/models/external_sites_model.dart';
import 'package:movielab/models/show_models/full_show_model.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/widgets/section_title.dart';
import 'package:url_launcher/url_launcher.dart';
class ShowPageExternalSites extends StatelessWidget {
final FullShow show;
const ShowPageExternalSites({Key? key, required this.show}) : super(key: key);
ShowPageExternalSites({Key? key, required this.show}) : super(key: key);
late ExternalSites externalSites;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 20),
height: 300,
child: Column(
children: const [],
final APIRequester apiRequester = APIRequester();
return FutureBuilder<ExternalSites?>(
future: apiRequester.getExternalSites(id: show.id),
builder: (context, snapshot) {
if (snapshot.data != null) {
return SafeArea(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
SectionTitle(
title: "Open Width",
padding: EdgeInsets.zero,
),
SectionTitle(
title: "Done",
color: kPrimaryColor,
fontSize: 15,
padding: EdgeInsets.zero,
),
],
),
Column(
children: [
const SizedBox(
height: 50,
),
Expanded(
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: 10,
itemBuilder: (context, index) {
return Column(
children: [
index == 0
? SectionTitle(
title: "Discover",
fontSize: 15,
color: Colors.white.withOpacity(0.75),
padding: const EdgeInsets.symmetric(
vertical: 5),
)
: const SizedBox.shrink(),
InkWell(
onTap: () => _launchUrl(Uri.parse(
snapshot.data?.freebase ?? "")),
child: SizedBox(
height: 50,
child: Row(
children: [
SizedBox(
width: 50,
child: Image.asset(
"assets/images/logos/IMDb.png")),
const SizedBox(
width: 10,
),
Text(
externalSitesList[index]["title"] ??
"",
style: const TextStyle(
color: Colors.white),
),
],
),
),
),
],
);
},
),
)
],
),
],
),
),
);
}
return const SizedBox(
height: 300,
child: Center(
child: SpinKitThreeBounce(
color: Colors.white,
size: 40,
),
));
});
}
}
Future<void> _launchUrl(url) async {
if (!await launchUrl(url)) {
throw 'Could not launch $url';
}
}

View File

@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:movielab/constants/colors.dart';
import 'package:movielab/models/hive/convertor.dart';
import 'package:movielab/models/show_models/full_show_model.dart';
import 'package:movielab/modules/capitalizer.dart';
import '../../../../../../../constants/colors.dart';
import '../../../../../../../models/show_models/full_show_model.dart';
import '../../../../../../../modules/preferences_shareholder.dart';
import '../../../../../../../widgets/buttons/activeable_button.dart';
import '../../../../../../../widgets/toast.dart';
import '../../watchtime.dart';
import 'package:movielab/modules/preferences_shareholder.dart';
import 'package:movielab/pages/show/show_page/sections/bottom_bar/watchtime.dart';
import 'package:movielab/widgets/buttons/activeable_button.dart';
import 'package:movielab/widgets/toast.dart';
class ShowPageListsInfo extends StatefulWidget {
final FullShow show;

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:movielab/constants/colors.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
import 'package:movielab/pages/show/show_page/sections/episode_guide/season_page.dart';
class EpisodeGuidePage extends StatefulWidget {

View File

@ -2,15 +2,16 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:movielab/constants/colors.dart';
import 'package:movielab/constants/types.dart';
import 'package:movielab/modules/get_show_info.dart';
import 'package:movielab/modules/preferences_shareholder.dart';
import 'package:movielab/modules/system_ui_overlay_style.dart';
import 'package:movielab/pages/show/show_page/sections/media.dart';
import 'package:movielab/pages/show/show_page/sections/other_ratings/other_ratings.dart';
import 'package:movielab/widgets/error.dart';
import '../../../constants/colors.dart';
import '../../../modules/preferences_shareholder.dart';
import '../../../modules/get_show_info.dart';
import 'sections/bottom_bar/bottom_bar.dart';
import 'sections/bottom_bar/sections/external_sites/external_sites.dart';
import 'sections/index.dart';
import 'sections/bottom_bar/watchtime.dart';
@ -131,6 +132,12 @@ class _ShowPageState extends State<ShowPage> with TickerProviderStateMixin {
floatingActionButtonLocation: _isBottomAppBarVisible
? FloatingActionButtonLocation.endDocked
: FloatingActionButtonLocation.endFloat,
endDrawerEnableOpenDragGesture: false,
endDrawer: Drawer(
backgroundColor: kSecondaryColor,
width: MediaQuery.of(context).size.width * 0.66,
child: ShowPageExternalSites(show: show),
),
body: SingleChildScrollView(
controller: _scrollController,
physics: const BouncingScrollPhysics(),

View File

@ -1,7 +1,7 @@
import 'package:get/get.dart';
import 'package:movielab/constants/types.dart';
import 'package:movielab/pages/main/home/home_data_controller.dart';
import '../../modules/api_requester.dart';
import '../../modules/api/api_requester.dart';
Future<RequestResult> getInitialData() async {
final apiRequester = APIRequester();

View File

@ -2,7 +2,7 @@ import 'package:get/get.dart';
import 'package:movielab/constants/types.dart';
import 'package:movielab/pages/main/home/home_data_controller.dart';
import 'package:test/test.dart';
import 'package:movielab/modules/api_requester.dart';
import 'package:movielab/modules/api/api_requester.dart';
void main() {
test('getTrendingMovies', () async {