From 912824bfe6e763d445e709ed74e05699d371ec72 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Tue, 24 Feb 2026 17:46:26 +1000 Subject: [PATCH] Migrate away from deprecated mbin api for different post types to single endpoint. --- lib/src/api/threads.dart | 273 ++++++++---------------- lib/src/screens/feed/create_screen.dart | 16 +- 2 files changed, 96 insertions(+), 193 deletions(-) diff --git a/lib/src/api/threads.dart b/lib/src/api/threads.dart index feca46d9..0082e911 100644 --- a/lib/src/api/threads.dart +++ b/lib/src/api/threads.dart @@ -8,6 +8,7 @@ import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/utils/models.dart'; import 'package:interstellar/src/utils/utils.dart'; +import 'package:mime/mime.dart'; const Map lemmyFeedSortMap = { FeedSort.active: 'Active', @@ -371,199 +372,91 @@ class APIThreads { } } - Future createArticle( - int communityId, { + Future create({ + required int communityId, required String title, - required bool isOc, - required String body, required String lang, - required bool isAdult, - required List tags, + String? body, + String? url, + XFile? image, + String? alt, + bool isAdult = false, + bool isOc = false, + List tags = const [], }) async { switch (client.software) { case ServerSoftware.mbin: - final path = '/magazine/$communityId/article'; - - final response = await client.post( - path, - body: { - 'title': title, - 'tags': tags, - 'isOc': isOc, - 'body': body, - 'lang': lang, - 'isAdult': isAdult, - }, - ); - - return PostModel.fromMbinEntry(response.bodyJson); - - case ServerSoftware.lemmy: - const path = '/post'; - final response = await client.post( - path, - body: { - 'name': title, - 'community_id': communityId, - 'body': body, - 'nsfw': isAdult, - 'language_id': await client.languageIdFromCode(lang), - }, - ); - - return PostModel.fromLemmy( - response.bodyJson, - langCodeIdPairs: await client.languageCodeIdPairs(), - ); - - case ServerSoftware.piefed: - const path = '/post'; - final response = await client.post( - path, - body: { - 'title': title, - 'community_id': communityId, - 'body': body, - 'nsfw': isAdult, - 'language_id': await client.languageIdFromCode(lang), - }, - ); - - return PostModel.fromPiefed( - response.bodyJson, - langCodeIdPairs: await client.languageCodeIdPairs(), - ); - } - } - - Future createLink( - int communityId, { - required String title, - required String url, - required bool isOc, - required String body, - required String lang, - required bool isAdult, - required List tags, - }) async { - switch (client.software) { - case ServerSoftware.mbin: - final path = '/magazine/$communityId/link'; - - final response = await client.post( - path, - body: { - 'title': title, - 'url': url, - 'tags': tags, - 'isOc': isOc, - 'body': body, - 'lang': lang, - 'isAdult': isAdult, - }, - ); - - return PostModel.fromMbinEntry(response.bodyJson); - - case ServerSoftware.lemmy: - const path = '/post'; - final response = await client.post( - path, - body: { - 'name': title, - 'community_id': communityId, - 'url': url, - 'body': body, - 'nsfw': isAdult, - 'language_id': await client.languageIdFromCode(lang), - }, + assert( + body?.isNotEmpty != null || url?.isNotEmpty != null || image != null, + 'Post needs either a body an url or an image.', ); + tags = tags.where((tag) => tag.isNotEmpty).toList(); - return PostModel.fromLemmy( - response.bodyJson, - langCodeIdPairs: await client.languageCodeIdPairs(), - ); - - case ServerSoftware.piefed: - const path = '/post'; - final response = await client.post( - path, - body: { - 'title': title, - 'community_id': communityId, - 'url': url, - 'body': body, - 'nsfw': isAdult, - 'language_id': await client.languageIdFromCode(lang), - }, - ); - - return PostModel.fromPiefed( - response.bodyJson, - langCodeIdPairs: await client.languageCodeIdPairs(), - ); - } - } - - Future createImage( - int communityId, { - required String title, - required XFile image, - required String alt, - required bool isOc, - required String body, - required String lang, - required bool isAdult, - required List tags, - }) async { - switch (client.software) { - case ServerSoftware.mbin: - final path = '/magazine/$communityId/image'; + final path = '/magazine/$communityId/entries'; final request = http.MultipartRequest( 'POST', Uri.https(client.domain, client.software.apiPathPrefix + path), ); - final multipartFile = http.MultipartFile.fromBytes( - 'uploadImage', - await image.readAsBytes(), - filename: image.name, - contentType: MediaType.parse(image.mimeType!), - ); - request.files.add(multipartFile); + request.fields['title'] = title; - for (var i = 0; i < tags.length; i++) { + if (url != null) { + request.fields['url'] = url; + } + for (var i = 0; i < tags.length; ++i) { request.fields['tags[$i]'] = tags[i]; } request.fields['isOc'] = isOc.toString(); - request.fields['body'] = body; + if (body != null && body.isNotEmpty) { + request.fields['body'] = body; + } request.fields['lang'] = lang; request.fields['isAdult'] = isAdult.toString(); - request.fields['alt'] = alt; + if (alt != null) { + request.fields['alt'] = alt; + } + if (image != null) { + final file = http.MultipartFile.fromBytes( + 'uploadImage', + await image.readAsBytes(), + filename: image.name, + contentType: MediaType.parse( + image.mimeType ?? lookupMimeType(image.path)!, + ), + ); + request.files.add(file); + } + final response = await client.sendRequest(request); return PostModel.fromMbinEntry(response.bodyJson); case ServerSoftware.lemmy: - const pictrsPath = '/pictrs/image'; + if (image != null) { + const uploadPath = '/pictrs/image'; - final uploadRequest = http.MultipartRequest( - 'POST', - Uri.https(client.domain, pictrsPath), - ); - final multipartFile = http.MultipartFile.fromBytes( - 'images[]', - await image.readAsBytes(), - filename: image.name, - contentType: MediaType.parse(image.mimeType!), - ); - uploadRequest.files.add(multipartFile); - final pictrsResponse = await client.sendRequest(uploadRequest); + final uploadRequest = http.MultipartRequest( + 'POST', + Uri.https(client.domain, uploadPath), + ); + final multipartFile = http.MultipartFile.fromBytes( + 'images[]', + await image.readAsBytes(), + filename: image.name, + contentType: MediaType.parse( + image.mimeType ?? lookupMimeType(image.path)!, + ), + ); + uploadRequest.files.add(multipartFile); + final pictrsResponse = await client.sendRequest(uploadRequest); - final imageName = - ((pictrsResponse.bodyJson['files']! as List).first! - as JsonMap)['file'] - as String?; + final imageName = + ((pictrsResponse.bodyJson['files']! as List).first! + as JsonMap)['file'] + as String?; + + url = 'https://${client.domain}/pictrs/image/$imageName'; + } const path = '/post'; final response = await client.post( @@ -571,10 +464,10 @@ class APIThreads { body: { 'name': title, 'community_id': communityId, - 'url': 'https://${client.domain}/pictrs/image/$imageName', + 'url': url, 'body': body, 'nsfw': isAdult, - 'alt_text': nullIfEmpty(alt), + 'alt_text': alt, 'language_id': await client.languageIdFromCode(lang), }, ); @@ -585,23 +478,32 @@ class APIThreads { ); case ServerSoftware.piefed: - const uploadPath = '/upload/image'; + if (image != null) { + const uploadPath = '/upload/image'; + + final uploadRequest = http.MultipartRequest( + 'POST', + Uri.https( + client.domain, + client.software.apiPathPrefix + uploadPath, + ), + ); + final multipartFile = http.MultipartFile.fromBytes( + 'file', + await image.readAsBytes(), + filename: image.name, + contentType: MediaType.parse( + image.mimeType ?? lookupMimeType(image.path)!, + ), + ); + uploadRequest.files.add(multipartFile); - final uploadRequest = http.MultipartRequest( - 'POST', - Uri.https(client.domain, client.software.apiPathPrefix + uploadPath), - ); - final multipartFile = http.MultipartFile.fromBytes( - 'file', - await image.readAsBytes(), - filename: image.name, - contentType: MediaType.parse(image.mimeType!), - ); - uploadRequest.files.add(multipartFile); + final uploadResponse = await client.sendRequest(uploadRequest); - final uploadResponse = await client.sendRequest(uploadRequest); + final imageUrl = uploadResponse.bodyJson['url'] as String?; - final imageUrl = uploadResponse.bodyJson['url'] as String?; + url = imageUrl; + } const path = '/post'; final response = await client.post( @@ -609,9 +511,10 @@ class APIThreads { body: { 'title': title, 'community_id': communityId, - 'url': imageUrl, + 'url': url, 'body': body, 'nsfw': isAdult, + 'alt_text': alt, 'language_id': await client.languageIdFromCode(lang), }, ); diff --git a/lib/src/screens/feed/create_screen.dart b/lib/src/screens/feed/create_screen.dart index cd52ed04..9aa35f70 100644 --- a/lib/src/screens/feed/create_screen.dart +++ b/lib/src/screens/feed/create_screen.dart @@ -404,8 +404,8 @@ class _CreateScreenState extends State { : () async { final tags = _tagsTextController.text.split(' '); - final post = await ac.api.threads.createArticle( - _community!.id, + final post = await ac.api.threads.create( + communityId: _community!.id, title: _titleTextController.text, isOc: _isOc, body: _bodyTextController.text, @@ -444,11 +444,11 @@ class _CreateScreenState extends State { : () async { final tags = _tagsTextController.text.split(' '); - final post = await ac.api.threads.createImage( - _community!.id, + final post = await ac.api.threads.create( + communityId: _community!.id, title: _titleTextController.text, - image: _imageFile!, - alt: _altText ?? '', + image: _imageFile, + alt: _altText, isOc: _isOc, body: _bodyTextController.text, lang: _lang, @@ -486,8 +486,8 @@ class _CreateScreenState extends State { : () async { final tags = _tagsTextController.text.split(' '); - final post = await ac.api.threads.createLink( - _community!.id, + final post = await ac.api.threads.create( + communityId: _community!.id, title: _titleTextController.text, url: _urlTextController.text, isOc: _isOc,