Skip to content

Commit

Permalink
feat: reintroduce PK support
Browse files Browse the repository at this point in the history
Signed-off-by: seth <[email protected]>
  • Loading branch information
getchoo committed Dec 5, 2023
1 parent 95fe620 commit f955cbb
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use once_cell::sync::Lazy;

pub mod dadjoke;
pub mod pluralkit;
pub mod prism_meta;
pub mod rory;

Expand Down
37 changes: 37 additions & 0 deletions src/api/pluralkit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use crate::api::REQWEST_CLIENT;

use color_eyre::eyre::{eyre, Context, Result};
use log::*;
use poise::serenity_prelude::{MessageId, UserId};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PluralKitMessage {
pub sender: String,
}

const PLURAL_KIT: &str = "https://api.pluralkit.me/v2";
const MESSAGES_ENDPOINT: &str = "/messages";

pub async fn get_sender(message_id: MessageId) -> Result<UserId> {
let req = REQWEST_CLIENT
.get(format!("{PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id}"))
.build()?;

info!("Making request to {}", req.url());
let resp = REQWEST_CLIENT.execute(req).await?;
let status = resp.status();

if let StatusCode::OK = status {
let data = resp.json::<PluralKitMessage>().await?;
let id: u64 = data.sender.parse().wrap_err_with(|| format!("Couldn't parse response from PluralKit as a UserId! Here's the response:\n{data:#?}"))?;
let sender = UserId::from(id);

Ok(sender)
} else {
Err(eyre!(
"Failed to get PluralKit message information from {PLURAL_KIT}{MESSAGES_ENDPOINT}/{message_id} with {status}",
))
}
}
13 changes: 12 additions & 1 deletion src/handlers/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use poise::{Event, FrameworkContext};
mod delete;
mod eta;
mod expand_link;
pub mod pluralkit;
mod support_onboard;

pub async fn handle(
ctx: &Context,
event: &Event<'_>,
_framework: FrameworkContext<'_, Data, Report>,
_data: &Data,
data: &Data,
) -> Result<()> {
match event {
Event::Ready { data_about_bot } => {
Expand All @@ -35,6 +36,16 @@ pub async fn handle(
return Ok(());
}

// detect PK users first to make sure we don't respond to unproxied messages
pluralkit::handle(ctx, new_message, data).await?;

if data.storage.is_user_plural(new_message.author.id).await?
&& pluralkit::is_message_proxied(new_message).await?
{
debug!("Not replying to unproxied PluralKit message");
return Ok(());
}

eta::handle(ctx, new_message).await?;
expand_link::handle(ctx, new_message).await?;
}
Expand Down
42 changes: 42 additions & 0 deletions src/handlers/event/pluralkit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::{api, Data};
use std::time::Duration;

use color_eyre::eyre::Result;
use log::*;
use poise::serenity_prelude::{Context, Message};
use tokio::time::sleep;

const PK_DELAY_SEC: Duration = Duration::from_secs(1000);

pub async fn is_message_proxied(message: &Message) -> Result<bool> {
debug!(
"Waiting on PluralKit API for {} seconds",
PK_DELAY_SEC.as_secs()
);
sleep(PK_DELAY_SEC).await;

let proxied = api::pluralkit::get_sender(message.id).await.is_ok();

Ok(proxied)
}

pub async fn handle(_ctx: &Context, msg: &Message, data: &Data) -> Result<()> {
if msg.webhook_id.is_some() {
debug!(
"Message {} has a webhook ID. Checking if it was sent through PluralKit",
msg.id
);

debug!(
"Waiting on PluralKit API for {} seconds",
PK_DELAY_SEC.as_secs()
);
sleep(PK_DELAY_SEC).await;

if let Ok(sender) = api::pluralkit::get_sender(msg.id).await {
data.storage.store_user_plurality(sender).await?;
}
}

Ok(())
}
10 changes: 6 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,35 @@ use log::*;
use poise::{
serenity_prelude as serenity, EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions,
};
use storage::Storage;

mod api;
mod commands;
mod config;
mod consts;
mod handlers;
mod storage;
mod tags;
mod utils;

type Context<'a> = poise::Context<'a, Data, Report>;

#[derive(Clone)]
pub struct Data {
config: config::Config,
redis: redis::Client,
config: Config,
storage: Storage,
octocrab: Arc<octocrab::Octocrab>,
}

impl Data {
pub fn new() -> Result<Self> {
let config = Config::new_from_env()?;
let redis = redis::Client::open(config.redis_url.clone())?;
let storage = Storage::new(&config.redis_url)?;
let octocrab = octocrab::instance();

Ok(Self {
config,
redis,
storage,
octocrab,
})
}
Expand Down
40 changes: 40 additions & 0 deletions src/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use color_eyre::eyre::Result;
use log::*;
use poise::serenity_prelude::UserId;
use redis::{AsyncCommands as _, Client};

pub const USER_KEY: &str = "users-v1";

#[derive(Clone, Debug)]
pub struct Storage {
client: Client,
}

impl Storage {
pub fn new(redis_url: &str) -> Result<Self> {
let client = Client::open(redis_url)?;

Ok(Self { client })
}

pub async fn store_user_plurality(&self, sender: UserId) -> Result<()> {
let mut con = self.client.get_async_connection().await?;

info!("Marking {sender} as a PluralKit user");
let key = format!("{USER_KEY}:{sender}:pk");

// Just store some value. We only care about the presence of this key
con.set(&key, 0).await?;
con.expire(key, 7 * 24 * 60 * 60).await?;

Ok(())
}

pub async fn is_user_plural(&self, user_id: UserId) -> Result<bool> {
let key = format!("{USER_KEY}:{user_id}:pk");
let mut con = self.client.get_async_connection().await?;

let exists: bool = con.exists(key).await?;
Ok(exists)
}

This comment has been minimized.

Copy link
@TheKodeToad

TheKodeToad Dec 5, 2023

Member

Maybe pluralkit storage should be separate to mod storage?

This comment has been minimized.

Copy link
@getchoo

getchoo Dec 5, 2023

Author Member

im guessing by "mod storage" you mean this file?

anyways, this is basically following the current setup in src/storage.ts, and imo it makes sense to have the interactions we'll have with our storage in our storage object. these could definitely be generalized though, similar to what i did here. it'd make it a bit easier to extend or change storage solutions in the future, but this should be fine for now

This comment has been minimized.

Copy link
@TheKodeToad

TheKodeToad Dec 5, 2023

Member

im guessing by "mod storage" you mean this file?

Moderation
I meant maybe the functions for pluralkit storage should be in another module

This comment has been minimized.

Copy link
@getchoo

getchoo Dec 6, 2023

Author Member

it's better to have it here since it can still access the client contained in self. otherwise, callers would need to pass around the client from Data in the Context every time they wanted to use these functions

}

0 comments on commit f955cbb

Please sign in to comment.