mirror of
https://github.com/redstrate/Auracite.git
synced 2025-04-23 21:27:45 +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
|
||||
.vs/
|
||||
obj/
|
||||
bin/
|
||||
*.user
|
|
@ -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"]
|
||||
|
|
|
@ -8,6 +8,8 @@ you can display in your browser.
|
|||
|
||||
## Usage
|
||||
|
||||
### CLI
|
||||
|
||||
Provide a character name via `--name`:
|
||||
|
||||
```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