Implement basic API requests and types

This commit is contained in:
Jan 2022-12-29 17:11:01 +01:00
parent 2379ee1a69
commit 4551ad36dc
Signed by: Lea
GPG key ID: 1BAFFE8347019C42
9 changed files with 308 additions and 0 deletions

127
lib/api/api.dart Normal file
View file

@ -0,0 +1,127 @@
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 '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();
FeverAPI({ required this.apiKey, required this.apiUrl });
static String generateApiKey(String username, String password) {
var bytes = utf8.encode('$username:$password');
return md5.convert(bytes).toString();
}
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 {
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();
List<Group> groups = [];
List<Feed> feeds = [];
List<Item> items = [];
List<FeedsGroup> feedsGroups = [];
if (data.containsKey('groups')) {
groups = (data['groups'] as List<dynamic>)
.map((json) => Group.fromJSON(api, json))
.toList();
}
if (data.containsKey('feeds')) {
feeds = (data['feeds'] as List<dynamic>)
.map((json) => Feed.fromJSON(api, json))
.toList();
}
if (data.containsKey('items')) {
items = (data['items'] as List<dynamic>)
.map((json) => Item.fromJSON(api, json))
.toList();
}
if (data.containsKey('feeds_groups')) {
feedsGroups = (data['feeds_groups'] as List<dynamic>)
.map((json) => FeedsGroup.fromJSON(api, json))
.toList();
}
print(groups);
print(feeds);
print(items);
print(feedsGroups);
}
}
void main() {
var api = FeverAPI(apiKey: "", apiUrl: "https://rss.amogus.cloud/api/fever.php");
api.request()
.withFeeds()
.withGroups()
.withItems()
.execute();
}

9
lib/api/exceptions.dart Normal file
View file

@ -0,0 +1,9 @@
part of fever_api;
class UnauthenticatedException implements Exception {
final int statusCode;
const UnauthenticatedException(this.statusCode);
@override
String toString() => 'Unauthenticated: Fever API returned status $statusCode';
}

38
lib/api/feed.dart Normal file
View file

@ -0,0 +1,38 @@
part of fever_api;
class Feed {
final FeverAPI api;
late int id;
late int faviconId;
late String title;
late String url;
late String siteUrl;
late bool isSpark;
late DateTime lastUpdatedOnTime;
Feed(this.api, {
required this.id,
required this.faviconId,
required this.title,
required this.url,
required this.siteUrl,
required this.isSpark,
required this.lastUpdatedOnTime,
});
Feed.fromJSON(this.api, Map<String, dynamic> json) {
id = toInt(json['id']);
faviconId = toInt(json['favicon_id']);
title = json['title'];
url = json['url'];
siteUrl = json['site_url'];
isSpark = json['isSpark'] == '1';
lastUpdatedOnTime = DateTime.fromMillisecondsSinceEpoch(toInt(json['last_updated_on_time']) * 1000);
}
@override
String toString() {
return 'Feed $id: $title';
}
}

25
lib/api/feeds_group.dart Normal file
View file

@ -0,0 +1,25 @@
part of fever_api;
class FeedsGroup {
final FeverAPI api;
late int groupId;
late List<int> feedIds;
FeedsGroup(this.api, {
required this.groupId,
required this.feedIds,
});
FeedsGroup.fromJSON(this.api, Map<String, dynamic> json) {
groupId = toInt(json['group_id']);
feedIds = (json['feed_ids'] as String)
.split(',').map((i) => int.parse(i))
.toList();
}
@override
String toString() {
return 'FeedsGroup $groupId -> ${feedIds.join(', ')}';
}
}

23
lib/api/group.dart Normal file
View file

@ -0,0 +1,23 @@
part of fever_api;
class Group {
final FeverAPI api;
late int id;
late String title;
Group(this.api, {
required this.id,
required this.title,
});
Group.fromJSON(this.api, Map<String, dynamic> json) {
id = toInt(json['id']);
title = json['title'];
}
@override
String toString() {
return 'Group $id: $title';
}
}

44
lib/api/item.dart Normal file
View file

@ -0,0 +1,44 @@
part of fever_api;
class Item {
final FeverAPI api;
late int id;
late int feedId;
late String title;
late String author;
late String html;
late String url;
late bool isSaved;
late bool isRead;
late DateTime createdOnTime;
Item(this.api, {
required this.id,
required this.feedId,
required this.title,
required this.author,
required this.html,
required this.url,
required this.isSaved,
required this.isRead,
required this.createdOnTime,
});
Item.fromJSON(this.api, Map<String, dynamic> json) {
id = toInt(json['id']);
feedId = toInt(json['feed_id']);
title = json['title'];
author = json['author'];
html = json['html'];
url = json['url'];
isSaved = json['is_saved'] == '1';
isRead = json['is_read'] == '1';
createdOnTime = DateTime.fromMillisecondsSinceEpoch(toInt(json['created_on_time']) * 1000);
}
@override
String toString() {
return 'Item $id: $title';
}
}

8
lib/api/util.dart Normal file
View file

@ -0,0 +1,8 @@
part of fever_api;
/// PHP developers seem to be incapable of returning consistent types,
/// so I need a toInt function that handles both ints and Strings.
int toInt(dynamic input) {
if (input is int) return input;
return int.parse(input.toString());
}

View file

@ -41,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
crypto:
dependency: "direct main"
description:
name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
url: "https://pub.dev"
source: hosted
version: "3.0.2"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -75,6 +83,22 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
http:
dependency: "direct main"
description:
name: http
sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
url: "https://pub.dev"
source: hosted
version: "0.13.5"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -176,6 +200,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.16" version: "0.4.16"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:

View file

@ -35,6 +35,8 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
http: ^0.13.5
crypto: ^3.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: