1
Fork 0
mirror of https://github.com/redstrate/Auracite.git synced 2025-04-22 12:47:45 +00:00

Add Dalamud plugin to collect additional data in-game

There's some data we want to save that's not available on the Lodestone,
for example playtime information. Now you can install the Dalamud plugin
(not currently distributed) to save playtime information.

In the future, we can expand this to all sorts of interesting things!
This commit is contained in:
Joshua Goins 2024-10-05 12:10:31 -04:00
parent b9bf515412
commit 39108207f6
15 changed files with 468 additions and 13 deletions

4
.gitignore vendored
View file

@ -1,3 +1,7 @@
/target
.idea/
config.json
.vs/
obj/
bin/
*.user

158
Cargo.lock generated
View file

@ -100,6 +100,7 @@ dependencies = [
"scraper",
"serde",
"serde_json",
"touche",
]
[[package]]
@ -123,6 +124,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
@ -135,6 +142,15 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -230,6 +246,25 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "cssparser"
version = "0.31.2"
@ -264,6 +299,16 @@ dependencies = [
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "downloader"
version = "0.2.8"
@ -452,6 +497,16 @@ dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getopts"
version = "0.2.21"
@ -478,6 +533,30 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "headers"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
dependencies = [
"base64 0.21.7",
"bytes",
"headers-core",
"http 0.2.12",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
dependencies = [
"http 0.2.12",
]
[[package]]
name = "heck"
version = "0.5.0"
@ -504,6 +583,17 @@ dependencies = [
"syn",
]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.1.0"
@ -522,7 +612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
"http 1.1.0",
]
[[package]]
@ -533,7 +623,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
dependencies = [
"bytes",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"pin-project-lite",
]
@ -544,6 +634,12 @@ version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.4.1"
@ -553,7 +649,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"httparse",
"itoa",
@ -588,7 +684,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"hyper",
"pin-project-lite",
@ -748,6 +844,16 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.36.4"
@ -1051,11 +1157,11 @@ version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
dependencies = [
"base64",
"base64 0.22.1",
"bytes",
"futures-core",
"futures-util",
"http",
"http 1.1.0",
"http-body",
"http-body-util",
"hyper",
@ -1109,7 +1215,7 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
dependencies = [
"base64",
"base64 0.22.1",
"rustls-pki-types",
]
@ -1251,6 +1357,17 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
@ -1390,6 +1507,15 @@ dependencies = [
"syn",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
@ -1430,6 +1556,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "touche"
version = "0.0.10"
source = "git+https://github.com/redstrate/touche#8979a3367dcf79d605d818157ea187a52608fed9"
dependencies = [
"headers",
"http 0.2.12",
"httparse",
"thiserror",
"threadpool",
]
[[package]]
name = "tower-service"
version = "0.3.3"
@ -1461,6 +1599,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-bidi"
version = "0.3.15"

View file

@ -24,3 +24,8 @@ downloader = "0.2"
# Used to generate the HTML page to easily preview your exported data
minijinja = "2.0"
# Used to communicate with the Dalamud plugin
# Needs my fork for allowing server shutdown
# TODO: upstream this or poke upstream to add this
touche = { git = "https://github.com/redstrate/touche" }

29
dalamud/Auracite.sln Normal file
View file

@ -0,0 +1,29 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auracite", "Auracite\Auracite.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
{4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Debug|x64.ActiveCfg = Debug|x64
{4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Debug|x64.Build.0 = Debug|x64
{4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Release|x64.ActiveCfg = Release|x64
{4FEC9558-EB25-419F-B86E-51B8CFDA32B7}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="Dalamud.Plugin.Bootstrap.targets"/>
<PropertyGroup>
<Version>1.0.0.0</Version>
<RootNamespace>NeoVARC</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,8 @@
{
"Author": "redstrate",
"Name": "Auracite",
"Punchline": "Export your FFXIV character in portable, generic formats",
"Description": "Export your FFXIV character in portable, generic formats.",
"Tags": [],
"RepoUrl": "https://github.com/redstrate/Auracite"
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Linux'))">$(HOME)/.xlcore/dalamud/Hooks/dev/</DalamudLibPath>
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('Linux'))">$(HOME)/.local/share/astra/dalamud/local/</DalamudLibPath>
<DalamudLibPath Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/</DalamudLibPath>
<DalamudLibPath Condition="$(DALAMUD_HOME) != ''">$(DALAMUD_HOME)/</DalamudLibPath>
</PropertyGroup>
<Import Project="$(DalamudLibPath)/targets/Dalamud.Plugin.targets"/>
</Project>

13
dalamud/Auracite/IStep.cs Normal file
View file

@ -0,0 +1,13 @@
using System;
namespace Auracite;
public interface IStep
{
public event CompletedDelegate Completed;
string StepName();
string StepDescription();
delegate void CompletedDelegate();
}

View file

@ -0,0 +1,41 @@
using System;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
namespace Auracite;
public class PlaytimeStep : IStep, IDisposable
{
public PlaytimeStep()
{
Plugin.ChatGui.ChatMessage += OnChatMessage;
}
public void Dispose()
{
Plugin.ChatGui.ChatMessage -= OnChatMessage;
}
public event IStep.CompletedDelegate? Completed;
public string StepName()
{
return "Playtime";
}
public string StepDescription()
{
return "Type /playtime into the chat window.";
}
private void OnChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message,
ref bool ishandled)
{
var msgString = message.ToString();
if (msgString.Contains("Total Play Time:") && type == XivChatType.SystemMessage)
{
Plugin.package.playtime = msgString.Split(": ")[1]; // TODO: lol
Completed?.Invoke();
}
}
}

View file

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Newtonsoft.Json;
namespace Auracite;
public sealed class Plugin : IDalamudPlugin
{
public static IStep? CurrentStep;
private readonly WindowSystem WindowSystem = new("Auracite");
private readonly List<Type> _steps =
[typeof(PlaytimeStep)];
private int _stepIndex;
private readonly StepWindow StepWindow;
public class Package
{
public string playtime;
}
public static Package? package;
public Plugin()
{
CommandManager.AddHandler("/auracite", new CommandInfo(OnAuraciteCommand)
{
HelpMessage = "Start the server."
});
StepWindow = new StepWindow();
WindowSystem.AddWindow(StepWindow);
PluginInterface.UiBuilder.Draw += WindowSystem.Draw;
}
[PluginService] internal static IClientState ClientState { get; private set; } = null!;
[PluginService] internal static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService] internal static IChatGui ChatGui { get; private set; } = null!;
[PluginService] internal static ICommandManager CommandManager { get; private set; } = null!;
public void Dispose()
{
WindowSystem.RemoveAllWindows();
}
private void OnAuraciteCommand(string command, string arguments)
{
if (arguments == "begin" && CurrentStep == null)
{
_stepIndex = -1;
package = new Package();
NextStep();
StepWindow.IsOpen = true;
}
}
private void NextStep()
{
_stepIndex++;
if (_stepIndex >= _steps.Count)
{
CurrentStep = null;
StepWindow.IsOpen = false;
SendPackage();
return;
}
CurrentStep = (IStep)Activator.CreateInstance(_steps[_stepIndex])!;
CurrentStep.Completed += NextStep;
}
private void SendPackage()
{
var client = new HttpClient();
client.PostAsync("http://127.0.0.1:8000/package", new StringContent(JsonConvert.SerializeObject(package)));
package = null;
}
}

View file

@ -0,0 +1,28 @@
using System;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Auracite;
public class StepWindow()
: Window("Step Window"), IDisposable
{
public void Dispose()
{
}
public override void Draw()
{
if (Plugin.CurrentStep != null)
{
ImGui.Text(Plugin.CurrentStep.StepName());
ImGui.Text(Plugin.CurrentStep.StepDescription());
ImGui.TextDisabled("Step requires manual user action.");
}
else
{
ImGui.Text("Auracite is not running.");
}
}
}

View file

@ -0,0 +1,13 @@
{
"version": 1,
"dependencies": {
"net8.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.13, )",
"resolved": "2.1.13",
"contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ=="
}
}
}
}

7
dalamud/global.json Normal file
View file

@ -0,0 +1,7 @@
{
"sdk": {
"version": "6.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}

View file

@ -17,6 +17,7 @@ pub struct CharacterData {
pub nameday: String,
pub guardian: String,
pub currencies: Currencies,
pub playtime: String,
#[serde(skip)]
pub face_url: String,

View file

@ -7,8 +7,13 @@ use crate::downloader::download;
use crate::html::write_html;
use crate::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};
const LODESTONE_HOST: &str = "https://na.finalfantasyxiv.com";
@ -17,6 +22,41 @@ const LODESTONE_HOST: &str = "https://na.finalfantasyxiv.com";
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,
}
#[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() {
@ -40,7 +80,7 @@ fn main() {
download(&format!("{LODESTONE_HOST}{}", href), char_page_path)
.expect("Failed to download the character page from the Lodestone.");
let char_data = parse_lodestone(&String::from_utf8(read(char_page_path).unwrap()).unwrap());
let mut char_data = parse_lodestone(&String::from_utf8(read(char_page_path).unwrap()).unwrap());
let character_folder = Path::new(&args.name);
if !character_folder.exists() {
@ -59,6 +99,18 @@ fn main() {
.expect("Failed to download the character face image.");
}
if args.dalamud {
println!("Now waiting for the Dalamud plugin. Type /auracite begin in chat.");
let package = Arc::new(Mutex::new(Package::default()));
Server::bind("0.0.0.0:8000").serve_single_thread(PackageService { wants_stop: Arc::new(Mutex::new(false)), package: &package }).unwrap();
let package = &*package.lock().unwrap();
char_data.playtime = package.playtime.parse().unwrap();
}
let serialized = serde_json::to_string(&char_data).unwrap();
write(character_folder.join("character.json"), serialized)
.expect("Failed to write the character JSON file.");