This commit is contained in:
Anna 2022-09-05 04:02:34 -04:00
parent 7b512e78d5
commit 305b2bed4a
18 changed files with 247 additions and 26 deletions

View File

@ -1,4 +1,6 @@
using Dalamud.Game.Command;
using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets;
namespace OrangeGuidanceTomestone;
@ -19,16 +21,47 @@ internal class Commands : IDisposable {
private void OnCommand(string command, string arguments) {
switch (arguments) {
case "ban":
case "ban": {
var name = this.Plugin.DataManager.GetExcelSheet<TerritoryType>()?.GetRow(this.Plugin.ClientState.TerritoryType)
?.PlaceName
.Value
?.Name
?.ToDalamudString()
.TextValue;
if (this.Plugin.Config.BannedTerritories.Contains(this.Plugin.ClientState.TerritoryType)) {
this.Plugin.ChatGui.Print($"{name} is already on the ban list.");
return;
}
this.Plugin.Config.BannedTerritories.Add(this.Plugin.ClientState.TerritoryType);
this.Plugin.SaveConfig();
this.Plugin.ChatGui.Print($"Added {name} to the ban list.");
this.Plugin.Messages.RemoveVfx();
this.Plugin.Messages.Clear();
break;
case "unban":
}
case "unban": {
var name = this.Plugin.DataManager.GetExcelSheet<TerritoryType>()?.GetRow(this.Plugin.ClientState.TerritoryType)
?.PlaceName
.Value
?.Name
?.ToDalamudString()
.TextValue;
if (!this.Plugin.Config.BannedTerritories.Contains(this.Plugin.ClientState.TerritoryType)) {
this.Plugin.ChatGui.Print($"{name} is not on the ban list.");
return;
}
this.Plugin.Config.BannedTerritories.Remove(this.Plugin.ClientState.TerritoryType);
this.Plugin.SaveConfig();
this.Plugin.ChatGui.Print($"Removed {name} from the ban list.");
this.Plugin.Messages.SpawnVfx();
break;
}
case "refresh":
this.Plugin.Messages.SpawnVfx();
break;

View File

@ -8,4 +8,7 @@ public class Configuration : IPluginConfiguration {
public string ApiKey { get; set; } = string.Empty;
public HashSet<uint> BannedTerritories { get; set; } = new();
public bool DisableTrials = true;
public bool DisableDeepDungeon = true;
public bool RemoveGlow = true;
}

View File

@ -1,6 +1,7 @@
using System.Numerics;
using Dalamud.Game;
using Dalamud.Logging;
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json;
using OrangeGuidanceTomestone.Helpers;
@ -15,9 +16,22 @@ internal class Messages : IDisposable {
private Dictionary<Guid, Message> Current { get; } = new();
private Queue<Message> SpawnQueue { get; } = new();
private HashSet<uint> Trials { get; } = new();
private HashSet<uint> DeepDungeons { get; } = new();
internal Messages(Plugin plugin) {
this.Plugin = plugin;
foreach (var cfc in this.Plugin.DataManager.GetExcelSheet<ContentFinderCondition>()!) {
if (cfc.ContentType.Row == 4) {
this.Trials.Add(cfc.TerritoryType.Row);
}
if (cfc.ContentType.Row == 21) {
this.DeepDungeons.Add(cfc.TerritoryType.Row);
}
}
if (this.Plugin.Config.ApiKey != string.Empty) {
this.SpawnVfx();
}
@ -34,7 +48,7 @@ internal class Messages : IDisposable {
this.Plugin.ClientState.Login -= this.SpawnVfx;
this.Plugin.Framework.Update -= this.HandleSpawnQueue;
this.RemoveVfx(null, null);
this.RemoveVfx();
}
private unsafe void HandleSpawnQueue(Framework framework) {
@ -42,10 +56,10 @@ internal class Messages : IDisposable {
return;
}
PluginLog.Log($"spawning vfx for {message.Id}");
PluginLog.Debug($"spawning vfx for {message.Id}");
var rotation = Quaternion.CreateFromYawPitchRoll(message.Yaw, 0, 0);
if (this.Plugin.Vfx.SpawnStatic(message.Id, VfxPath, message.Position, rotation) == null) {
PluginLog.Log("trying again");
PluginLog.Debug("trying again");
this.SpawnQueue.Enqueue(message);
}
}
@ -64,6 +78,14 @@ internal class Messages : IDisposable {
return;
}
if (this.Plugin.Config.DisableTrials && this.Trials.Contains(territory)) {
return;
}
if (this.Plugin.Config.DisableDeepDungeon && this.DeepDungeons.Contains(territory)) {
return;
}
this.RemoveVfx(null, null);
Task.Run(async () => {
@ -95,6 +117,12 @@ internal class Messages : IDisposable {
this.Plugin.Vfx.RemoveAll();
}
internal void Clear() {
this.CurrentMutex.Wait();
this.Current.Clear();
this.CurrentMutex.Release();
}
internal IEnumerable<Message> Nearby() {
if (this.Plugin.ClientState.LocalPlayer is not { } player) {
return Array.Empty<Message>();

View File

@ -58,15 +58,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.8"/>
<PackageReference Include="Fody" Version="6.6.3" PrivateAssets="all"/>
<PackageReference Include="Resourcer.Fody" Version="1.8.0" PrivateAssets="all"/>
<PackageReference Include="YamlDotNet" Version="12.0.0"/>
<PackageReference Include="DalamudPackager" Version="2.1.8" />
<PackageReference Include="Fody" Version="6.6.3" PrivateAssets="all" />
<PackageReference Include="Resourcer.Fody" Version="1.8.0" PrivateAssets="all" />
<PackageReference Include="YamlDotNet" Version="12.0.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="../server/packs/*.yaml" LinkBase="packs"/>
<EmbeddedResource Remove="../server/packs/*_old*.yaml"/>
<EmbeddedResource Include="../server/packs/*.yaml" LinkBase="packs" />
<EmbeddedResource Remove="../server/packs/*_old*.yaml" />
</ItemGroup>
</Project>

View File

@ -3,6 +3,7 @@ using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.IoC;
using Dalamud.Plugin;
@ -14,6 +15,9 @@ public class Plugin : IDalamudPlugin {
[PluginService]
internal DalamudPluginInterface Interface { get; init; }
[PluginService]
internal ChatGui ChatGui { get; init; }
[PluginService]
internal ClientState ClientState { get; init; }

View File

@ -15,6 +15,7 @@ internal class MainWindow {
this.Tabs = new List<ITab> {
new Write(this.Plugin),
new MessageList(this.Plugin),
new Settings(this.Plugin),
};
}

View File

@ -0,0 +1,31 @@
using ImGuiNET;
namespace OrangeGuidanceTomestone.Ui.MainWindowTabs;
internal class Settings : ITab {
public string Name => "Settings";
private Plugin Plugin { get; }
internal Settings(Plugin plugin) {
this.Plugin = plugin;
}
public void Draw() {
var anyChanged = false;
var vfx = false;
anyChanged |= vfx |= ImGui.Checkbox("Disable in trials", ref this.Plugin.Config.DisableTrials);
anyChanged |= vfx |= ImGui.Checkbox("Disable in Deep Dungeons", ref this.Plugin.Config.DisableDeepDungeon);
anyChanged |= vfx |= ImGui.Checkbox("Remove glow effect from signs", ref this.Plugin.Config.RemoveGlow);
if (anyChanged) {
this.Plugin.SaveConfig();
}
if (vfx) {
this.Plugin.Messages.RemoveVfx();
this.Plugin.Messages.Clear();
}
}
}

1
server/Cargo.lock generated
View File

@ -1035,6 +1035,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"bytes",
"chrono",
"rand",
"serde",

View File

@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
anyhow = "1"
base64 = "0.13"
bytes = "1"
chrono = "0.4"
rand = "0.8"
serde = { version = "1", features = ["derive"] }

View File

@ -0,0 +1,4 @@
create table extra_tokens (
id text not null primary key,
extra integer not null
);

View File

@ -288,6 +288,7 @@ words:
- core
- fingers
- horns
- feet
- name: Affinities
words:

View File

@ -48,4 +48,6 @@ pub struct RetrievedMessageTerritory {
pub positive_votes: i32,
pub negative_votes: i32,
pub user_vote: i64,
#[serde(skip)]
pub created: NaiveDateTime,
}

View File

@ -16,6 +16,7 @@ mod get_location;
mod vote;
mod get_mine;
mod get_message;
mod claim;
pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
register::register(Arc::clone(&state))
@ -26,6 +27,7 @@ pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.or(get_message::get_message(Arc::clone(&state)))
.or(get_location::get_location(Arc::clone(&state)))
.or(get_mine::get_mine(Arc::clone(&state)))
.or(claim::claim(Arc::clone(&state)))
.recover(handle_rejection)
.boxed()
}
@ -62,6 +64,7 @@ pub enum WebError {
InvalidIndex,
TooManyMessages,
NoSuchMessage,
InvalidExtraCode,
}
impl Reject for WebError {}
@ -72,21 +75,41 @@ pub struct AnyhowRejection(anyhow::Error);
impl Reject for AnyhowRejection {}
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
let status = if let Some(AnyhowRejection(e)) = err.find::<AnyhowRejection>() {
let (status, name, desc) = if let Some(AnyhowRejection(e)) = err.find::<AnyhowRejection>() {
eprintln!("{:#?}", e);
StatusCode::INTERNAL_SERVER_ERROR
(
StatusCode::INTERNAL_SERVER_ERROR,
"internal_error",
"an internal logic error occured",
)
} else if let Some(e) = err.find::<WebError>() {
match e {
WebError::InvalidAuthToken => StatusCode::BAD_REQUEST,
WebError::InvalidPackId => StatusCode::NOT_FOUND,
WebError::InvalidIndex => StatusCode::NOT_FOUND,
WebError::TooManyMessages => StatusCode::BAD_REQUEST,
WebError::NoSuchMessage => StatusCode::NOT_FOUND,
WebError::InvalidAuthToken => (StatusCode::BAD_REQUEST, "invalid_auth_token", "the auth token was not valid"),
WebError::InvalidPackId => (StatusCode::NOT_FOUND, "invalid_pack_id", "the server does not have a pack registered with that id"),
WebError::InvalidIndex => (StatusCode::NOT_FOUND, "invalid_index", "one of the provided indices was out of range"),
WebError::TooManyMessages => (StatusCode::BAD_REQUEST, "too_many_messages", "you have run out of messages - delete one and try again"),
WebError::NoSuchMessage => (StatusCode::NOT_FOUND, "no_such_message", "no message with that id was found"),
WebError::InvalidExtraCode => (StatusCode::BAD_REQUEST, "invalid_extra_code", "that extra code was not found"),
}
} else {
eprintln!("{:#?}", err);
StatusCode::INTERNAL_SERVER_ERROR
(
StatusCode::INTERNAL_SERVER_ERROR,
"internal_error",
"an unhandled error was encountered",
)
};
Ok(warp::reply::with_status(warp::reply(), status))
#[derive(serde::Serialize)]
struct ErrorMessage {
code: &'static str,
message: &'static str,
}
let message = ErrorMessage {
code: name,
message: desc,
};
Ok(warp::reply::with_status(warp::reply::json(&message), status))
}

59
server/src/web/claim.rs Normal file
View File

@ -0,0 +1,59 @@
use std::sync::Arc;
use anyhow::Context;
use bytes::Bytes;
use uuid::Uuid;
use warp::{Filter, Rejection, Reply};
use warp::filters::BoxedFilter;
use crate::message::RetrievedMessageTerritory;
use crate::State;
use crate::web::{AnyhowRejection, WebError};
pub fn claim(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
warp::post()
.and(warp::path("claim"))
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and(warp::body::content_length_limit(256))
.and(warp::body::bytes())
.and_then(move |(id, _), code: Bytes| logic(Arc::clone(&state), id, code))
.boxed()
}
async fn logic(state: Arc<State>, id: i64, bytes: Bytes) -> Result<impl Reply, Rejection> {
let bytes: Vec<u8> = bytes.into_iter().collect();
let code = String::from_utf8(bytes)
.context("invalid utf8 for extra code")
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
let code = sqlx::query!(
// language=sqlite
r#"delete from extra_tokens where id = ? returning extra as "extra!: i64""#,
code,
)
.fetch_optional(&state.db)
.await
.context("could not get code from database")
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
if let Some(code) = code {
sqlx::query!(
// language=sqlite
"update users set extra = ? where id = ?",
code.extra,
id,
)
.execute(&state.db)
.await
.context("could not update user")
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
return Ok(code.extra.to_string());
}
Err(warp::reject::custom(WebError::InvalidExtraCode))
}

View File

@ -34,7 +34,8 @@ async fn logic(state: Arc<State>, id: i64, message_id: Uuid) -> Result<impl Repl
m.message,
coalesce(sum(v.vote between 0 and 1), 0) as positive_votes,
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
v2.vote as user_vote
v2.vote as user_vote,
m.created
from messages m
left join votes v on m.id = v.message
left join votes v2 on m.id = v2.message and v2.user = ?

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::Context;
@ -13,12 +14,18 @@ pub fn get_mine(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("messages"))
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and_then(move |(id, _)| logic(Arc::clone(&state), id))
.and(warp::query())
.and_then(move |(id, extra), query: HashMap<String, String>| logic(Arc::clone(&state), id, extra, query))
.boxed()
}
async fn logic(state: Arc<State>, id: i64) -> Result<impl Reply, Rejection> {
let messages = sqlx::query_as!(
async fn logic(state: Arc<State>, id: i64, extra: i64, mut query: HashMap<String, String>) -> Result<impl Reply, Rejection> {
let version = query.remove("v")
.unwrap_or_else(|| "1".to_string())
.parse::<u8>()
.unwrap_or(1);
let mut messages = sqlx::query_as!(
RetrievedMessageTerritory,
// language=sqlite
r#"
@ -31,7 +38,8 @@ async fn logic(state: Arc<State>, id: i64) -> Result<impl Reply, Rejection> {
m.message,
coalesce(sum(v.vote between 0 and 1), 0) as positive_votes,
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
v2.vote as user_vote
v2.vote as user_vote,
m.created
from messages m
left join votes v on m.id = v.message
left join votes v2 on m.id = v2.message and v2.user = ?
@ -45,5 +53,24 @@ async fn logic(state: Arc<State>, id: i64) -> Result<impl Reply, Rejection> {
.context("could not get messages from database")
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
Ok(warp::reply::json(&messages))
messages.sort_by_key(|msg| msg.created);
messages.reverse();
if version == 1 {
return Ok(warp::reply::json(&messages));
}
#[derive(serde::Serialize)]
struct Mine {
messages: Vec<RetrievedMessageTerritory>,
extra: i64,
}
let mine = Mine {
messages,
extra,
};
Ok(warp::reply::json(&mine))
}

View File

@ -15,6 +15,7 @@ pub fn vote(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("votes"))
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and(warp::body::content_length_limit(3))
.and(warp::body::json())
.and_then(move |message_id: Uuid, (id, _), vote: i8| logic(Arc::clone(&state), id, message_id, vote))
.boxed()

View File

@ -14,6 +14,7 @@ pub fn write(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("messages"))
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and(warp::body::content_length_limit(1024))
.and(warp::body::json())
.and_then(move |(id, extra), message: Message| logic(Arc::clone(&state), id, extra, message))
.boxed()