import "dart:convert"; import "dart:typed_data"; import "package:http/http.dart" as http; class QuoteEngagement { final int? likes; final int? retweets; final int? replies; final int? views; QuoteEngagement({ this.likes, this.retweets, this.replies, this.views, }); Map toJson() { return { if (likes != null) "likes": likes, if (retweets != null) "retweets": retweets, if (replies != null) "replies": replies, if (views != null) "views": views, }; } factory QuoteEngagement.fromJson(Map json) { return QuoteEngagement( likes: json["likes"], retweets: json["retweets"], replies: json["replies"], views: json["views"], ); } } class QuoteSessionRequest { final String? displayName; final String? username; final dynamic avatarUrl; final String? text; final dynamic imageUrl; final int? timestamp; final bool? verified; final QuoteEngagement? engagement; final bool clearAvatarUrl; final bool clearImageUrl; final bool clearEngagement; QuoteSessionRequest({ this.displayName, this.username, this.avatarUrl, this.text, this.imageUrl, this.timestamp, this.verified, this.engagement, this.clearAvatarUrl = false, this.clearImageUrl = false, this.clearEngagement = false, }); // convert img bytes to base64 data uri String? _encodeImage(dynamic image) { if (image == null) return null; if (image is String) return image; if (image is Uint8List) { final base64String = base64Encode(image); return "data:image/png;base64,$base64String"; } return null; } Map toJson() { final map = {}; if (displayName != null) map["displayName"] = displayName; if (username != null) map["username"] = username; if (clearAvatarUrl) { map["avatarUrl"] = null; } else { final encodedAvatar = _encodeImage(avatarUrl); if (encodedAvatar != null) map["avatarUrl"] = encodedAvatar; } if (text != null) map["text"] = text; if (clearImageUrl) { map["imageUrl"] = null; } else { final encodedImage = _encodeImage(imageUrl); if (encodedImage != null) map["imageUrl"] = encodedImage; } if (timestamp != null) map["timestamp"] = timestamp; if (verified != null) map["verified"] = verified; if (clearEngagement) { map["engagement"] = null; } else if (engagement != null) { map["engagement"] = engagement!.toJson(); } return map; } } class QuoteSession { final String id; final String? displayName; final String? username; final String? avatarUrl; final String? text; final String? imageUrl; final int? timestamp; final bool? verified; final QuoteEngagement? engagement; final int createdAt; final int updatedAt; QuoteSession({ required this.id, this.displayName, this.username, this.avatarUrl, this.text, this.imageUrl, this.timestamp, this.verified, this.engagement, required this.createdAt, required this.updatedAt, }); factory QuoteSession.fromJson(Map json) { return QuoteSession( id: json["id"], displayName: json["displayName"], username: json["username"], avatarUrl: json["avatarUrl"], text: json["text"], imageUrl: json["imageUrl"], timestamp: json["timestamp"], verified: json["verified"], engagement: json["engagement"] != null ? QuoteEngagement.fromJson(json["engagement"]) : null, createdAt: json["createdAt"], updatedAt: json["updatedAt"], ); } } class QuoteSnapshot { final String token; final String url; final String sessionId; final int createdAt; final int expiresAt; QuoteSnapshot({ required this.token, required this.url, required this.sessionId, required this.createdAt, required this.expiresAt, }); factory QuoteSnapshot.fromJson(Map json) { return QuoteSnapshot( token: json["token"], url: json["url"], sessionId: json["sessionId"], createdAt: json["createdAt"], expiresAt: json["expiresAt"], ); } } class QuoteGeneratorApiV2 { // static const String _baseUrl = "https://quotes.imbenji.net"; static const String _baseUrl = "http://localhost:3000"; // create new session static Future createSession(QuoteSessionRequest request) async { final url = Uri.parse("$_baseUrl/v2/quote"); final response = await http.post( url, headers: { "Content-Type": "application/json", "User-Agent": "QuoteGen-Flutter/1.0", }, body: jsonEncode(request.toJson()), ); if (response.statusCode == 201) { final data = jsonDecode(response.body); return QuoteSession.fromJson(data); } else { throw Exception("Failed to create session: ${response.statusCode}"); } } // get session state static Future getSession(String sessionId) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId"); final response = await http.get( url, headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); return QuoteSession.fromJson(data); } else { throw Exception("Failed to get session: ${response.statusCode}"); } } // update session fields (only send whats changed) static Future updateSession( String sessionId, QuoteSessionRequest updates, ) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId"); final response = await http.patch( url, headers: { "Content-Type": "application/json", "User-Agent": "QuoteGen-Flutter/1.0", }, body: jsonEncode(updates.toJson()), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); return QuoteSession.fromJson(data); } else { throw Exception("Failed to update session: ${response.statusCode}"); } } // render the session as image static Future generateImage(String sessionId) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId/image"); final response = await http.get( url, headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, ); if (response.statusCode == 200) { return response.bodyBytes; } else { throw Exception("Failed to generate image: ${response.statusCode}"); } } // delete session static Future deleteSession(String sessionId) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId"); final response = await http.delete( url, headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, ); if (response.statusCode != 204) { throw Exception("Failed to delete session: ${response.statusCode}"); } } // create persistant snapshot link static Future createSnapshotLink(String sessionId) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId/snapshot-link"); final response = await http.post( url, headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, ); if (response.statusCode == 201) { final data = jsonDecode(response.body); return QuoteSnapshot.fromJson(data); } else { throw Exception("Failed to create snapshot link: ${response.statusCode}"); } } // get snapshot image by token static Future getSnapshot(String token) async { final url = Uri.parse("$_baseUrl/v2/snapshot/$token"); final response = await http.get( url, headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, ); if (response.statusCode == 200) { return response.bodyBytes; } else { throw Exception("Failed to get snapshot: ${response.statusCode}"); } } // helper to get current timestamp in seconds static int getCurrentTimestamp() { return DateTime.now().millisecondsSinceEpoch ~/ 1000; } }