diff --git a/.gitignore b/.gitignore
index 37fd804..997e1d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,4 @@
config.json
.vs/
obj/
-bin/
*.user
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 76f74d5..40fe2a6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,15 @@ description = "Export your FFXIV character in portable, generic formats"
[lib]
crate-type = ["cdylib", "rlib"]
+
+[[bin]]
+name = "cli"
+required-features = ["cli"]
+
+[[bin]]
+name = "ui"
+required-features = ["ui"]
+
[features]
# Builds the Qt UI for Auracite
ui = []# ["cxx-qt-build", "cxx-qt", "cxx-qt-lib", "cxx-kde-frameworks"]
diff --git a/README.md b/README.md
index e2819e2..3401afa 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,8 @@ you can display in your browser.
## Usage
+### CLI
+
Provide a character name via `--name`:
```shell
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..6e14ac2
--- /dev/null
+++ b/index.html
@@ -0,0 +1,58 @@
+
+
+
+
+ Auracite
+
+
+
+
Auracite
+
This tool allows you to export your FFXIV character into portable, generic formats.
+
+
Auracite uses the Lodestone for collecting data about your character. We use a proxy and that's unfortunately necessary, due to CORS compliance and Lodestone doing annoying browser compatibility checks we need to inject cookies for.
+
+
The entire process happens locally, and does not give me any personally identifiable data about you. We only collect what is already publicly available on the Lodestone.
+
+ How does this work?
+
+
+
+
Here is the list of requests we make to the Lodestone:
+
It makes one request to search for your character by it's name.
+
It makes one request to request your character's main page. The other pages are not yet considered.
+
It makes two requests to download images. This is currently your avatar and the full body portrait images.
+
+
+ What kind of requests does Auracite make?
+
+
+
+
Auracite can only collect as much data about your character as they make publicly available on the Lodestone.
+
To work around this, I created a Dalamud plugin to collect even more information. It's available in my personal Dalamud plugin repository.
+
The plugin needs to start a local HTTP server in order to communicate with Auracite. To prevent this from running all the time, you must type /auracite begin before clicking the "Download" button. Once the process is complete, the server is shutdown automatically. It's always safe to disable the plugin when you're not using Auracite.
+
The website connects to your game client locally, and it does not use my server to proxy any data. No data leaves your device.
+
+ What is the "Connect to Dalamud Plugin" option?
+
+
+
+
+
+
+
+Source Code
+
+
diff --git a/src/bin/cli.rs b/src/bin/cli.rs
new file mode 100644
index 0000000..6f1e9a9
--- /dev/null
+++ b/src/bin/cli.rs
@@ -0,0 +1,33 @@
+use auracite::downloader::download;
+use auracite::html::write_html;
+use auracite::parser::{parse_lodestone, parse_search};
+use clap::Parser;
+use serde::Deserialize;
+use std::convert::Infallible;
+use std::fs::{read, write};
+use std::path::Path;
+use std::sync::{Arc, Mutex};
+use touche::server::Service;
+use touche::{Body, HttpBody, Request, Response, Server, StatusCode};
+use auracite::archive_character;
+
+const LODESTONE_HOST: &str = "https://na.finalfantasyxiv.com";
+
+#[derive(Parser, Debug)]
+#[command(version, about)]
+struct Args {
+ #[arg(short, long, help = "The character's name.")]
+ name: String,
+
+ #[arg(short, long, help = "Whether to import more data from the Auracite Dalamud plugin.")]
+ dalamud: bool,
+}
+
+#[tokio::main]
+async fn main() {
+ let args = Args::parse();
+
+ println!("Downloading character data for {}...", args.name);
+
+ archive_character(&args.name, args.dalamud).await;
+}
diff --git a/src/bin/ui/Main.qml b/src/bin/ui/Main.qml
new file mode 100644
index 0000000..5f802a0
--- /dev/null
+++ b/src/bin/ui/Main.qml
@@ -0,0 +1,25 @@
+import QtQuick.Layouts
+import QtQuick.Controls as QQC2
+import org.kde.kirigami as Kirigami
+import zone.xiv.auracite
+
+Kirigami.ApplicationWindow {
+ id: root
+
+ title: "Auracite"
+
+ readonly property Backend backend: Backend {}
+
+ ColumnLayout {
+ QQC2.TextField {
+ id: characterNameField
+
+ placeholderText: "Full name of the character"
+ }
+
+ QQC2.Button {
+ text: "Archive"
+ onClicked: root.backend.archiveCharacter(characterNameField.text, false)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/bin/ui/bridge.rs b/src/bin/ui/bridge.rs
new file mode 100644
index 0000000..852312d
--- /dev/null
+++ b/src/bin/ui/bridge.rs
@@ -0,0 +1,39 @@
+#[cxx_qt::bridge]
+pub mod bridge {
+ unsafe extern "C++" {
+ include!("cxx-qt-lib/qstring.h");
+ type QString = cxx_qt_lib::QString;
+ }
+
+ unsafe extern "RustQt" {
+ #[qobject]
+ #[qml_element]
+ type Backend = super::BackendRust;
+ }
+
+ unsafe extern "RustQt" {
+ #[qinvokable]
+ #[cxx_name = "archiveCharacter"]
+ fn archive_character(self: &Backend, character_name: &QString, use_dalamud: bool);
+ }
+}
+
+use std::fs::write;
+use cxx_qt_lib::QString;
+use auracite::archive_character;
+
+#[derive(Default)]
+pub struct BackendRust {
+}
+
+impl bridge::Backend {
+ pub fn archive_character(&self, character_name: &QString, use_dalamud: bool) {
+ let rt = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap();
+
+ let inner = rt.block_on(archive_character(&character_name.to_string(), use_dalamud));
+ write("/home/josh/test.zip", inner);
+ }
+}
\ No newline at end of file
diff --git a/src/bin/ui/main.rs b/src/bin/ui/main.rs
new file mode 100644
index 0000000..00cd25b
--- /dev/null
+++ b/src/bin/ui/main.rs
@@ -0,0 +1,24 @@
+use cxx_kde_frameworks::ki18n::{KLocalizedContext, KLocalizedString};
+use cxx_qt_lib::{QByteArray, QGuiApplication, QQmlApplicationEngine, QQuickStyle, QString, QUrl};
+
+#[cfg(feature = "ui")]
+pub mod bridge;
+
+fn main() {
+ QQuickStyle::set_style(&QString::from("org.kde.desktop"));
+
+ let mut app = QGuiApplication::new();
+ let mut engine = QQmlApplicationEngine::new();
+
+ KLocalizedString::set_application_domain(&QByteArray::from("mgedit"));
+
+ if let Some(mut engine) = engine.as_mut() {
+ KLocalizedContext::initialize_engine(engine.as_mut().as_qqmlengine());
+ // TODO: replace with loadModule (requires cxx-qt changes)
+ engine.load(&QUrl::from("qrc:/qt/qml/zone/xiv/auracite/src/bin/ui/Main.qml"));
+ }
+
+ if let Some(app) = app.as_mut() {
+ app.exec();
+ }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index 360d4e2..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use auracite::downloader::download;
-use auracite::html::write_html;
-use auracite::parser::{parse_lodestone, parse_search};
-use clap::Parser;
-use serde::Deserialize;
-use std::convert::Infallible;
-use std::fs::{read, write};
-use std::path::Path;
-use std::sync::{Arc, Mutex};
-use touche::server::Service;
-use touche::{Body, HttpBody, Request, Response, Server, StatusCode};
-use auracite::archive_character;
-
-const LODESTONE_HOST: &str = "https://na.finalfantasyxiv.com";
-
-#[derive(Parser, Debug)]
-#[command(version, about)]
-struct Args {
- #[arg(short, long, help = "The character's name.")]
- name: String,
-
- #[arg(short, long, help = "Whether to import more data from the Auracite Dalamud plugin.")]
- dalamud: bool,
-}
-
-#[derive(Default, Deserialize, Clone)]
-struct Package {
- playtime: String,
- height: i32,
- bust_size: i32,
- gil: u32,
- is_battle_mentor: bool,
- is_trade_mentor: bool,
- is_novice: bool,
- is_returner: bool,
- player_commendations: i32,
-}
-
-#[derive(Clone)]
-struct PackageService<'a> {
- wants_stop: Arc>, // TODO: THIS IS TERRIBLE STOP STOP STOP
- package: &'a Arc>,
-}
-
-impl Service for PackageService<'_> {
- type Body = &'static str;
- type Error = Infallible;
-
- fn call(&self, req: Request) -> Result, Self::Error> {
- *self.package.lock().unwrap() = serde_json::from_str(&String::from_utf8(req.into_body().into_bytes().unwrap()).unwrap()).unwrap();
-
- *self.wants_stop.lock().unwrap() = true;
-
- Ok(Response::builder()
- .status(StatusCode::OK)
- .body("")
- .unwrap())
- }
-
- // TODO: NO NO NO NO
- fn wants_stop(&self) -> bool {
- *self.wants_stop.lock().unwrap()
- }
-}
-
-fn main() {
- let args = Args::parse();
-
- println!("Downloading character data for {}...", args.name);
-
- archive_character(&args.name, args.dalamud);
-}