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:
parent
aa5318c84a
commit
06b8afad24
9 changed files with 190 additions and 73 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,5 +3,4 @@
|
||||||
config.json
|
config.json
|
||||||
.vs/
|
.vs/
|
||||||
obj/
|
obj/
|
||||||
bin/
|
|
||||||
*.user
|
*.user
|
|
@ -6,6 +6,15 @@ description = "Export your FFXIV character in portable, generic formats"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "cli"
|
||||||
|
required-features = ["cli"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ui"
|
||||||
|
required-features = ["ui"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Builds the Qt UI for Auracite
|
# Builds the Qt UI for Auracite
|
||||||
ui = []# ["cxx-qt-build", "cxx-qt", "cxx-qt-lib", "cxx-kde-frameworks"]
|
ui = []# ["cxx-qt-build", "cxx-qt", "cxx-qt-lib", "cxx-kde-frameworks"]
|
||||||
|
|
|
@ -8,6 +8,8 @@ you can display in your browser.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
Provide a character name via `--name`:
|
Provide a character name via `--name`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
58
index.html
Normal file
58
index.html
Normal 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
33
src/bin/cli.rs
Normal 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
25
src/bin/ui/Main.qml
Normal 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
39
src/bin/ui/bridge.rs
Normal 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
24
src/bin/ui/main.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
72
src/main.rs
72
src/main.rs
|
@ -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);
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue