diff --git a/.gitignore b/.gitignore
index 3c8a157..2cf44b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,9 @@
# Conventional directory for build output.
build/
+
+# IntelliJ Files
+.idea
+
+# Executables
+*.exe
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 73f69e0..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
deleted file mode 100644
index f71feb6..0000000
--- a/.idea/libraries/Dart_Packages.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml
deleted file mode 100644
index 064833e..0000000
--- a/.idea/libraries/Dart_SDK.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 639900d..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 19450bb..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index 797acea..0000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/bin/exec_error.dart b/bin/exec_error.dart
new file mode 100644
index 0000000..9c08b3b
--- /dev/null
+++ b/bin/exec_error.dart
@@ -0,0 +1,11 @@
+class ExecError {
+ final ErrorType type;
+ final String message;
+
+ ExecError({required this.type, this.message = ''});
+}
+
+enum ErrorType {
+ authentication,
+ missingEndpoint,
+}
diff --git a/bin/friendica_archiver.dart b/bin/friendica_archiver.dart
index d721de6..85b227c 100644
--- a/bin/friendica_archiver.dart
+++ b/bin/friendica_archiver.dart
@@ -1,3 +1,85 @@
-void main(List arguments) {
- print('Hello world!');
+import 'dart:io';
+
+import 'package:args/args.dart';
+
+import 'friendica_client.dart';
+import 'json_printer.dart';
+
+const defaultRequestDelayMilliseconds = 5000;
+const defaultMaxPosts = 1;
+const defaultReadComments = false;
+const defaultReadImages = false;
+
+void main(List arguments) async {
+ final argParser = ArgParser()
+ ..addOption('archive-folder',
+ abbr: 'a',
+ help:
+ 'Specifies the local folder all data files pulled from the server will be stored',
+ mandatory: true)
+ ..addOption('username',
+ abbr: 'u', help: 'Username on your Friendica instance', mandatory: true)
+ ..addOption('server-name',
+ abbr: 's',
+ help:
+ 'The server name for your instance. (e.g. if the URL in your browser is "https://friendica.com/" then this would be "friendica.com',
+ mandatory: true)
+ ..addOption('delay',
+ abbr: 'd',
+ help:
+ 'Delay in milliseconds between requests to try not to stress the server (thousands of API calls can be made)',
+ defaultsTo: '$defaultRequestDelayMilliseconds')
+ ..addOption('max-post-requests',
+ abbr: 'm',
+ help: 'The maximum number of times to query for posts',
+ defaultsTo: '$defaultMaxPosts')
+ ..addFlag('read-comments',
+ abbr: 'c',
+ help:
+ 'Whether to read comments on posts (defaults to $defaultReadComments)',
+ defaultsTo: defaultReadComments)
+ ..addFlag('download-images',
+ abbr: 'i',
+ help:
+ 'Whether to download images from posts when those images are stored on the server (not links to other sites) (defaults to $defaultReadImages)',
+ defaultsTo: defaultReadComments);
+
+ late ArgResults settings;
+ try {
+ settings = argParser.parse(arguments);
+ } on ArgParserException catch (e) {
+ print("Error with arguments: ${e.message}");
+ print(argParser.usage);
+ return;
+ }
+
+ stdout.write('Enter Password: ');
+ _setEcho(false);
+ final password = stdin.readLineSync() ?? '';
+ _setEcho(true);
+ print('');
+
+ final username = settings['username'];
+ final client = FriendicaClient(
+ username: username,
+ password: password,
+ serverName: settings['server-name']);
+ final timelineResult = await client.getTimeline(username, 1, 3);
+ timelineResult.match(
+ onSuccess: (posts) => File('/tmp/test.json')
+ .writeAsStringSync(PrettyJsonEncoder().convert(posts)),
+ onError: (error) => print('Error getting posts: $error'));
+ print("Done processing API requests");
+ return;
+}
+
+// Seems in IntelliJ and release build mode setting echo fails
+void _setEcho(bool value) {
+ try {
+ stdin.echoMode = value;
+ // ignore: empty_catches
+ } catch (e) {
+ print('');
+ print('Error toggling echo to $value, so will stay current value...');
+ }
}
diff --git a/bin/friendica_client.dart b/bin/friendica_client.dart
new file mode 100644
index 0000000..fc229b2
--- /dev/null
+++ b/bin/friendica_client.dart
@@ -0,0 +1,44 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:result_monad/result_monad.dart';
+
+class FriendicaClient {
+ final String username;
+ final String password;
+ final String serverName;
+ final _client = HttpClient();
+ late final String _authHeader;
+
+ FriendicaClient(
+ {required this.username,
+ required this.password,
+ required this.serverName}) {
+ final authenticationString = "$username:$password";
+ final encodedAuthString = base64Encode(utf8.encode(authenticationString));
+ _authHeader = "Basic $encodedAuthString";
+ }
+
+ FutureResult, String> getTimeline(
+ String userId, int page, int count) async {
+ final request = Uri.parse(
+ 'https://$serverName/api/statuses/user_timelineuser_id=$userId&count=$count&page=$page');
+ return await _getApiRequest(request);
+ }
+
+ FutureResult, String> getPostComments(int postId) async {
+ return Result.error("Not Implemented");
+ }
+
+ FutureResult, String> _getApiRequest(Uri url) async {
+ // TODO Error mode against: bad server URL, bad auth, bad path, empty response
+ final request = await _client.getUrl(url);
+ request.headers.add('authorization', _authHeader);
+ request.headers.contentType =
+ ContentType('application', 'json', charset: 'utf-8');
+ final response = await request.close();
+ final body = await response.transform(utf8.decoder).join('');
+ final bodyJson = jsonDecode(body) as List;
+ return Result.ok(bodyJson);
+ }
+}
diff --git a/bin/json_printer.dart b/bin/json_printer.dart
new file mode 100644
index 0000000..c5a4661
--- /dev/null
+++ b/bin/json_printer.dart
@@ -0,0 +1,11 @@
+import 'dart:convert';
+
+class PrettyJsonEncoder {
+ late JsonEncoder encoder;
+
+ PrettyJsonEncoder() {
+ encoder = JsonEncoder.withIndent('\t');
+ }
+
+ String convert(Object json) => encoder.convert(json);
+}
diff --git a/pubspec.lock b/pubspec.lock
index 50a12ba..0404e4b 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,13 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ args:
+ dependency: "direct main"
+ description:
+ name: args
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.3.0"
lints:
dependency: "direct dev"
description:
@@ -8,5 +15,12 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
+ result_monad:
+ dependency: "direct main"
+ description:
+ name: result_monad
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.2"
sdks:
dart: ">=2.15.1 <3.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index f8b0d0d..cc70bf4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -6,9 +6,9 @@ version: 1.0.0
environment:
sdk: '>=2.15.1 <3.0.0'
-
dependencies:
args: ^2.3.0
+ result_monad: ^1.0.2
dev_dependencies:
lints: ^1.0.0