1
Fork 0
mirror of https://github.com/redstrate/Auracite.git synced 2025-04-24 05:37:44 +00:00

Add Qt UI and add a separate CLI target

The Qt UI is basic for now, to be expanded upon later - but it does
work!
This commit is contained in:
Joshua Goins 2024-10-31 18:18:06 -04:00
parent aa5318c84a
commit 06b8afad24
9 changed files with 190 additions and 73 deletions

1
.gitignore vendored
View file

@ -3,5 +3,4 @@
config.json
.vs/
obj/
bin/
*.user

View file

@ -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"]

View file

@ -8,6 +8,8 @@ you can display in your browser.
## Usage
### CLI
Provide a character name via `--name`:
```shell

58
index.html Normal file
View file

@ -0,0 +1,58 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Auracite</title>
</head>
<body>
<script type="module">
import init, {archive_character_base64} from "./pkg/auracite.js";
function archive() {
init().then(() => {
archive_character_base64(document.getElementById("name").value, false).then((uri) => {
// Download character archive
window.location.replace(uri);
});
});
}
document.querySelector('#downloadButton').addEventListener('click', archive);
</script>
<h1>Auracite</h1>
<p>This tool allows you to export your FFXIV character into portable, generic formats.</p>
<details>
<p>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.</p>
<p>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.</p>
<summary>
How does this work?
</summary>
</details>
<details>
<p>Here is the list of requests we make to the Lodestone:</p>
<p>It makes <strong>one request</strong> to search for your character by it's name.</p>
<p>It makes <strong>one request</strong> to request your character's main page. The other pages are not yet considered.</p>
<p>It makes <strong>two requests</strong> to download images. This is currently your avatar and the full body portrait images.</p>
<summary>
What kind of requests does Auracite make?
</summary>
</details>
<details>
<p>Auracite can only collect as much data about your character as they make publicly available on the Lodestone.</p>
<p>To work around this, I created a Dalamud plugin to collect even more information. It's available in my personal Dalamud plugin repository.</p>
<p>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 <code>/auracite begin</code> 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.</p>
<p>The website connects to your game client locally, and it does not use my server to proxy any data. No data leaves your device.</p>
<summary>
What is the "Connect to Dalamud Plugin" option?
</summary>
</details>
<label for="name">Character Name:</label>
<input type="text" id="name" name="name" required minlength="4" maxlength="20" size="20" />
<input type="checkbox" id="scales" name="scales" />
<label for="scales">Connect to Dalamud Plugin</label>
<button class="favorite styled" type="button" id="downloadButton">Download</button>
<a href="https://github.com/redstrate/Auracite">Source Code</a>
</body>
</html>

33
src/bin/cli.rs Normal file
View file

@ -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;
}

25
src/bin/ui/Main.qml Normal file
View file

@ -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)
}
}
}

39
src/bin/ui/bridge.rs Normal file
View file

@ -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);
}
}

24
src/bin/ui/main.rs Normal file
View file

@ -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();
}
}

View file

@ -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<Mutex<bool>>, // TODO: THIS IS TERRIBLE STOP STOP STOP
package: &'a Arc<Mutex<Package>>,
}
impl Service for PackageService<'_> {
type Body = &'static str;
type Error = Infallible;
fn call(&self, req: Request<Body>) -> Result<Response<Self::Body>, 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);
}