diff --git a/client/OrangeGuidanceTomestone.csproj b/client/OrangeGuidanceTomestone.csproj
index 5b9d148..b50eb8c 100755
--- a/client/OrangeGuidanceTomestone.csproj
+++ b/client/OrangeGuidanceTomestone.csproj
@@ -58,17 +58,17 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/client/Pinger.cs b/client/Pinger.cs
new file mode 100644
index 0000000..fa2759d
--- /dev/null
+++ b/client/Pinger.cs
@@ -0,0 +1,52 @@
+using System.Diagnostics;
+using Dalamud.Game;
+using Dalamud.Logging;
+using OrangeGuidanceTomestone.Helpers;
+
+namespace OrangeGuidanceTomestone;
+
+internal class Pinger : IDisposable {
+ private Plugin Plugin { get; }
+ private Stopwatch Stopwatch { get; } = new();
+ private int _waitSecs;
+
+ internal Pinger(Plugin plugin) {
+ this.Plugin = plugin;
+
+ this.Stopwatch.Start();
+
+ this.Plugin.Framework.Update += this.Ping;
+ }
+
+ public void Dispose() {
+ this.Plugin.Framework.Update -= this.Ping;
+ }
+
+ private void Ping(Framework framework) {
+ if (this.Stopwatch.Elapsed < TimeSpan.FromSeconds(this._waitSecs)) {
+ return;
+ }
+
+ this.Stopwatch.Restart();
+
+ if (this.Plugin.Config.ApiKey == string.Empty) {
+ this._waitSecs = 5;
+ return;
+ }
+
+ // 30 mins
+ this._waitSecs = 1_800;
+
+ Task.Run(async () => {
+ var resp = await ServerHelper.SendRequest(
+ this.Plugin.Config.ApiKey,
+ HttpMethod.Post,
+ "/ping"
+ );
+
+ if (!resp.IsSuccessStatusCode) {
+ PluginLog.LogWarning($"Failed to ping, status {resp.StatusCode}");
+ }
+ });
+ }
+}
diff --git a/client/Plugin.cs b/client/Plugin.cs
index ddcd118..aa7e624 100644
--- a/client/Plugin.cs
+++ b/client/Plugin.cs
@@ -40,6 +40,7 @@ public class Plugin : IDalamudPlugin {
internal Messages Messages { get; }
internal VfxReplacer VfxReplacer { get; }
internal Commands Commands { get; }
+ internal Pinger Pinger { get; }
internal string AvfxFilePath { get; }
@@ -52,6 +53,7 @@ public class Plugin : IDalamudPlugin {
this.Ui = new PluginUi(this);
this.VfxReplacer = new VfxReplacer(this);
this.Commands = new Commands(this);
+ this.Pinger = new Pinger(this);
if (this.Config.ApiKey == string.Empty) {
this.GetApiKey();
@@ -59,6 +61,7 @@ public class Plugin : IDalamudPlugin {
}
public void Dispose() {
+ this.Pinger.Dispose();
this.Commands.Dispose();
this.VfxReplacer.Dispose();
this.Ui.Dispose();
diff --git a/server/migrations/8_add_last_seen.sql b/server/migrations/8_add_last_seen.sql
new file mode 100644
index 0000000..45667d4
--- /dev/null
+++ b/server/migrations/8_add_last_seen.sql
@@ -0,0 +1,6 @@
+alter table users
+ add column last_seen timestamp not null default 0;
+
+-- noinspection SqlWithoutWhere
+update users
+set last_seen = current_timestamp;
diff --git a/server/src/main.rs b/server/src/main.rs
index 7032a0e..1f6dd0e 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -19,7 +19,6 @@ mod message;
mod web;
mod util;
mod config;
-mod task;
static MIGRATOR: Migrator = sqlx::migrate!();
diff --git a/server/src/message.rs b/server/src/message.rs
index 97706dd..c2f2869 100644
--- a/server/src/message.rs
+++ b/server/src/message.rs
@@ -1,4 +1,3 @@
-use chrono::{Duration, Utc};
use serde::{Deserialize, Serialize};
use sqlx::types::chrono::NaiveDateTime;
use uuid::Uuid;
@@ -45,13 +44,8 @@ pub struct RetrievedMessage {
pub created: NaiveDateTime,
#[serde(skip)]
pub user: String,
-}
-
-impl RetrievedMessage {
- pub fn adjusted_time_since_posting(&self) -> Duration {
- let score = (self.positive_votes - self.negative_votes).max(0);
- Utc::now().naive_utc().signed_duration_since(self.created) - Duration::weeks(score as i64)
- }
+ #[serde(skip)]
+ pub last_seen_minutes: i64,
}
#[derive(Debug, Serialize)]
diff --git a/server/src/task.rs b/server/src/task.rs
deleted file mode 100644
index d0835d6..0000000
--- a/server/src/task.rs
+++ /dev/null
@@ -1 +0,0 @@
-mod delete_old;
diff --git a/server/src/task/delete_old.rs b/server/src/task/delete_old.rs
deleted file mode 100644
index 9fa0768..0000000
--- a/server/src/task/delete_old.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use std::sync::Arc;
-use crate::State;
-
-pub fn delete_old(state: Arc) {
- tokio::task::spawn(async move {
-
- });
-}
diff --git a/server/src/web.rs b/server/src/web.rs
index 2afccb3..b20e38b 100644
--- a/server/src/web.rs
+++ b/server/src/web.rs
@@ -17,6 +17,7 @@ mod vote;
mod get_mine;
mod get_message;
mod claim;
+mod ping;
pub fn routes(state: Arc) -> BoxedFilter<(impl Reply, )> {
register::register(Arc::clone(&state))
@@ -28,6 +29,7 @@ pub fn routes(state: Arc) -> BoxedFilter<(impl Reply, )> {
.or(get_location::get_location(Arc::clone(&state)))
.or(get_mine::get_mine(Arc::clone(&state)))
.or(claim::claim(Arc::clone(&state)))
+ .or(ping::ping(Arc::clone(&state)))
.recover(handle_rejection)
.boxed()
}
diff --git a/server/src/web/get_location.rs b/server/src/web/get_location.rs
index a9c1732..7718512 100644
--- a/server/src/web/get_location.rs
+++ b/server/src/web/get_location.rs
@@ -38,10 +38,12 @@ async fn logic(state: Arc, id: i64, location: u32) -> Result, id: i64) {
// just count nearby messages. this is O(n^2) but alternatives are hard
let mut ids = Vec::with_capacity(messages.len());
for a in messages.iter() {
- let mut nearby = 0;
+ if a.user == id_str {
+ // always include own messages
+ ids.push(a.id.clone());
+ continue;
+ }
+ if a.last_seen_minutes >= 35 {
+ continue;
+ }
+
+ let mut nearby = 0;
for b in messages.iter() {
if a.id == b.id {
continue;
@@ -81,36 +92,39 @@ fn filter_messages(messages: &mut Vec, id: i64) {
nearby += 1;
}
- if a.user == id_str || nearby <= 2 {
- // always include groups of three or fewer
- ids.push(a.id.clone());
- continue;
- }
+ let (numerator, denominator) = if nearby <= 2 {
+ // no need to do calculations for groups of three or fewer
+ (17, 20)
+ } else {
+ let time_since_creation = a.created.signed_duration_since(Utc::now().naive_utc());
+ let brand_new = time_since_creation < Duration::minutes(30);
+ let new = time_since_creation < Duration::hours(2);
- let score = (a.positive_votes - a.negative_votes).max(0);
- let time_since_creation = a.adjusted_time_since_posting();
- if time_since_creation > Duration::weeks(1) {
- continue;
- }
+ let mut numerator = 1;
+ if brand_new {
+ numerator = nearby;
+ } else if new {
+ numerator += (nearby / 3).min(1);
+ }
- // originally thresholds were 6 hours and 2 days
- let brand_new = time_since_creation < Duration::minutes(30);
- let new = time_since_creation < Duration::hours(2);
+ let score = (a.positive_votes - a.negative_votes).max(0);
+ if score > 0 {
+ let pad = score as f32 / nearby as f32;
+ let rounded = pad.round() as u32;
+ numerator += rounded.max(nearby / 2);
+ }
- let mut numerator = 1;
- if brand_new {
- numerator = nearby;
- } else if new {
- numerator += (nearby / 3).min(1);
- }
+ nearby *= 2;
- if score > 0 {
- let pad = score as f32 / nearby as f32;
- let rounded = pad.round() as u32;
- numerator += rounded.max(nearby / 2);
- }
+ if numerator * 20 > nearby * 17 {
+ numerator = 17;
+ nearby = 20;
+ }
- if rand::thread_rng().gen_ratio(numerator.min(nearby), nearby * 2) {
+ (numerator, nearby)
+ };
+
+ if rand::thread_rng().gen_ratio(numerator.min(denominator), denominator) {
ids.push(a.id.clone());
}
}
diff --git a/server/src/web/ping.rs b/server/src/web/ping.rs
new file mode 100644
index 0000000..ddacab5
--- /dev/null
+++ b/server/src/web/ping.rs
@@ -0,0 +1,32 @@
+use std::sync::Arc;
+
+use anyhow::Context;
+use warp::{Filter, Rejection, Reply};
+use warp::filters::BoxedFilter;
+
+use crate::State;
+use crate::web::AnyhowRejection;
+
+pub fn ping(state: Arc) -> BoxedFilter<(impl Reply, )> {
+ warp::post()
+ .and(warp::path("ping"))
+ .and(warp::path::end())
+ .and(super::get_id(Arc::clone(&state)))
+ .and_then(move |(id, _)| logic(Arc::clone(&state), id))
+ .boxed()
+}
+
+async fn logic(state: Arc, id: i64) -> Result {
+ sqlx::query!(
+ // language=sqlite
+ "update users set last_seen = current_timestamp where id = ?",
+ id,
+ )
+ .execute(&state.db)
+ .await
+ .context("could not update user")
+ .map_err(AnyhowRejection)
+ .map_err(warp::reject::custom)?;
+
+ Ok(warp::reply())
+}
diff --git a/server/src/web/register.rs b/server/src/web/register.rs
index 31fd7f9..261c4dc 100644
--- a/server/src/web/register.rs
+++ b/server/src/web/register.rs
@@ -21,7 +21,7 @@ async fn logic(state: Arc) -> Result {
let hashed = crate::util::hash(&auth);
sqlx::query!(
// language=sqlite
- "insert into users (auth) values (?)",
+ "insert into users (auth, last_seen) values (?, current_timestamp)",
hashed,
)
.execute(&state.db)