feet/lib/api/api.dart

157 lines
4 KiB
Dart

library fever_api;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';
part 'feed.dart';
part 'item.dart';
part 'group.dart';
part 'feeds_group.dart';
part 'cache.dart';
part 'exceptions.dart';
part 'util.dart';
/// Basic implementation of the Fever API
/// https://github.com/dasmurphy/tinytinyrss-fever-plugin/blob/master/fever-api.md
class FeverAPI {
String? apiKey;
String? apiUrl;
/// We use a shared HTTP client for all requests
final _httpClient = http.Client();
late final APICache cache;
FeverAPI({ this.apiKey, this.apiUrl }) {
cache = APICache(this);
}
static String generateApiKey(String username, String password) {
var bytes = utf8.encode('$username:$password');
return md5.convert(bytes).toString();
}
/// Checks whether the given API URL is a valid Fever API endpoint
static Future<bool> isValidAPI(String url) async {
try {
final response = jsonDecode((await http.post(Uri.parse(url))).body);
return response['api_version'] == 3;
} catch(e) {
print(e);
return false;
}
}
/// Checks whether the given API URL is a valid Fever API endpoint and the given API key is authorized to access it
static Future<bool> isAuthenticated(String url, String key) async {
try {
final response = jsonDecode((await http.post(Uri.parse(url), body: { 'api_key': key })).body);
return response['api_version'] == 3 && response['auth'] == 1;
} catch(e) {
print(e);
return false;
}
}
/// Whether we have an API URL and API key set
bool loggedIn() {
return apiKey != null && apiUrl != null;
}
APIRequestBuilder request() {
return APIRequestBuilder(this, _httpClient);
}
}
class APIRequestBuilder {
final FeverAPI api;
final http.Client _httpClient;
List<String> args = [];
APIRequestBuilder(this.api, this._httpClient);
void _addArg(String arg) {
if (!args.contains(arg)) args.add(arg);
}
Uri generateUrl() {
return Uri.parse('${api.apiUrl}?api&${args.join('&')}');
}
/// Include items in response
APIRequestBuilder withItems() {
_addArg('items');
return this;
}
/// Include feeds in response
APIRequestBuilder withFeeds() {
_addArg('feeds');
return this;
}
/// Include groups in response
APIRequestBuilder withGroups() {
_addArg('groups');
return this;
}
/// Executes the API request and returns the JSON data
Future<Map<String, dynamic>> fetch() async {
if (api.apiKey == null || api.apiUrl == null) throw Exception('API Key or API URL not set');
final response = await _httpClient.post(generateUrl(), body: { 'api_key': api.apiKey });
final data = jsonDecode((response).body);
if (data['auth'] == 0) throw UnauthenticatedException(data['auth']);
return data;
}
/// Executes the API request and populates the local cache with the response data
Future<void> execute() async {
final data = await fetch();
if (data.containsKey('groups')) {
List<Group> groups = (data['groups'] as List<dynamic>)
.map((json) => Group.fromJSON(api, json))
.toList();
for (var group in groups) {
api.cache.groups.set(group.id, group);
}
}
if (data.containsKey('feeds')) {
List<Feed> feeds = (data['feeds'] as List<dynamic>)
.map((json) => Feed.fromJSON(api, json))
.toList();
for (var feed in feeds) {
api.cache.feeds.set(feed.id, feed);
}
}
if (data.containsKey('items')) {
List<Item> items = (data['items'] as List<dynamic>)
.map((json) => Item.fromJSON(api, json))
.toList();
for (var item in items) {
api.cache.items.set(item.id, item);
}
}
if (data.containsKey('feeds_groups')) {
List<FeedsGroup> feedsGroups = (data['feeds_groups'] as List<dynamic>)
.map((json) => FeedsGroup.fromJSON(api, json))
.toList();
api.cache.feedsGroups.clear();
api.cache.feedsGroups.addAll(feedsGroups);
}
}
}