pinger? I hardly know 'er!

This commit is contained in:
Anna 2022-09-06 08:07:23 -04:00
parent 07efd7f71a
commit 54d56eb194
12 changed files with 147 additions and 54 deletions

View File

@ -58,17 +58,17 @@
</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="vfx/b0941trp1*_o.avfx"/>
<EmbeddedResource Include="img/sign_*.jpg"/>
<EmbeddedResource Include="../server/packs/*.yaml" LinkBase="packs" />
<EmbeddedResource Remove="../server/packs/*_old*.yaml" />
<EmbeddedResource Include="vfx/b0941trp1*_o.avfx" />
<EmbeddedResource Include="img/sign_*.jpg" />
</ItemGroup>
</Project>

52
client/Pinger.cs Normal file
View File

@ -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}");
}
});
}
}

View File

@ -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();

View File

@ -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;

View File

@ -19,7 +19,6 @@ mod message;
mod web;
mod util;
mod config;
mod task;
static MIGRATOR: Migrator = sqlx::migrate!();

View File

@ -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)]

View File

@ -1 +0,0 @@
mod delete_old;

View File

@ -1,8 +0,0 @@
use std::sync::Arc;
use crate::State;
pub fn delete_old(state: Arc<State>) {
tokio::task::spawn(async move {
});
}

View File

@ -17,6 +17,7 @@ mod vote;
mod get_mine;
mod get_message;
mod claim;
mod ping;
pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
register::register(Arc::clone(&state))
@ -28,6 +29,7 @@ pub fn routes(state: Arc<State>) -> 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()
}

View File

@ -38,10 +38,12 @@ async fn logic(state: Arc<State>, id: i64, location: u32) -> Result<impl Reply,
v2.vote as user_vote,
m.glyph,
m.created,
m.user
m.user,
cast((julianday(current_timestamp) - julianday(created)) * 1440 as int) as last_seen_minutes
from messages m
left join votes v on m.id = v.message
left join votes v2 on m.id = v2.message and v2.user = ?
inner join users u on m.user = u.id
where m.territory = ?
group by m.id"#,
id,
@ -63,8 +65,17 @@ fn filter_messages(messages: &mut Vec<RetrievedMessage>, 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<RetrievedMessage>, 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());
}
}

32
server/src/web/ping.rs Normal file
View File

@ -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<State>) -> 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<State>, id: i64) -> Result<impl Reply, Rejection> {
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())
}

View File

@ -21,7 +21,7 @@ async fn logic(state: Arc<State>) -> Result<impl Reply, Rejection> {
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)