Initial Deployment
This commit is contained in:
parent
d9dcb19db0
commit
2e56f77d6c
204
src/battlemetrics.rs
Normal file
204
src/battlemetrics.rs
Normal file
@ -0,0 +1,204 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::commands::{Context, Error};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct CommandOptions {
|
||||
raw: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct CommandAttributes {
|
||||
command: String,
|
||||
options: CommandOptions,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct RconCommand {
|
||||
#[serde(rename = "type")]
|
||||
type_field: String,
|
||||
attributes: CommandAttributes,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RconData {
|
||||
data: RconCommand,
|
||||
}
|
||||
|
||||
impl RconData {
|
||||
pub fn new(type_field: &str, command: &str, raw: &str) -> Self {
|
||||
RconData {
|
||||
data: RconCommand {
|
||||
type_field: type_field.to_string(),
|
||||
attributes: CommandAttributes {
|
||||
command: command.to_string(),
|
||||
options: CommandOptions {
|
||||
raw: raw.to_string(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mint_blood(
|
||||
name: Option<String>,
|
||||
amount: String,
|
||||
ctx: Context<'_>,
|
||||
api_client: &reqwest::Client,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(name) = name.clone() {
|
||||
let player_name = name;
|
||||
let short_name = "blood";
|
||||
|
||||
let command_name = format!(
|
||||
r#"inventory.giveto "{}" "{}" {}"#,
|
||||
player_name, short_name, amount
|
||||
);
|
||||
println!("{:?}: Running Command: {}", player_name, command_name);
|
||||
|
||||
let rcon_data = RconData::new("rconCommand", "raw", &command_name);
|
||||
|
||||
let serialized_data = if let Ok(data) = serde_json::to_string(&rcon_data) {
|
||||
data
|
||||
} else {
|
||||
println!("error serializing data");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let server_id = ctx.data().server_id.clone();
|
||||
let url = format!(
|
||||
"https://api.battlemetrics.com/servers/{}/command",
|
||||
server_id
|
||||
);
|
||||
|
||||
let bm_token = ctx.data().bm_token.clone();
|
||||
|
||||
let res = api_client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", bm_token))
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serialized_data)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if res.status() == 200 {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(
|
||||
ctx.http(),
|
||||
format!("{} has been payed {} blood", player_name, amount),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
println!("{:?} blood minted.", player_name);
|
||||
Ok(())
|
||||
} else {
|
||||
println!("{:?} blood failed to mint.", player_name);
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(
|
||||
ctx.http(),
|
||||
format!("Failed to pay {} blood to {}.", amount, player_name),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("errror: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
println!("error minting blood");
|
||||
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "Failed to parse amount")
|
||||
.await;
|
||||
if let Err(e) = reply {
|
||||
println!("errror: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn unmute_player(
|
||||
name: Option<String>,
|
||||
ctx: Context<'_>,
|
||||
api_client: &reqwest::Client,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(name) = name.clone() {
|
||||
let player_name = name;
|
||||
|
||||
let command_name = format!(r#"unmute "{}""#, player_name);
|
||||
println!("{:?}: Running Command: {}", player_name, command_name);
|
||||
|
||||
let rcon_data = RconData::new("rconCommand", "raw", &command_name);
|
||||
|
||||
let serialized_data = if let Ok(data) = serde_json::to_string(&rcon_data) {
|
||||
data
|
||||
} else {
|
||||
println!("error serializing data");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let server_id = ctx.data().server_id.clone();
|
||||
let url = format!(
|
||||
"https://api.battlemetrics.com/servers/{}/command",
|
||||
server_id
|
||||
);
|
||||
|
||||
let bm_token = ctx.data().bm_token.clone();
|
||||
|
||||
let res = api_client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", bm_token))
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serialized_data)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if res.status() == 200 {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), format!("{} has been unmuted", player_name))
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
println!("{:?} unmuted.", player_name);
|
||||
Ok(())
|
||||
} else {
|
||||
println!("Failed to unmute {:?}", player_name);
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(
|
||||
ctx.http(),
|
||||
format!("Failed to unmute player {}.", player_name),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("errror: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
println!("error unmuting player");
|
||||
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "Failed to unmute player")
|
||||
.await;
|
||||
if let Err(e) = reply {
|
||||
println!("errror: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
350
src/commands.rs
Normal file
350
src/commands.rs
Normal file
@ -0,0 +1,350 @@
|
||||
use poise::serenity_prelude::{CreateAttachment, CreateEmbed, CreateMessage};
|
||||
use qrcode_generator::QrCodeEcc;
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use zebedee_rust::charges::*;
|
||||
|
||||
use crate::{
|
||||
battlemetrics::{mint_blood, unmute_player},
|
||||
Data,
|
||||
};
|
||||
|
||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
pub type Context<'a> = poise::Context<'a, Data, Error>;
|
||||
|
||||
/// Responds with ln invoice
|
||||
#[poise::command(prefix_command, track_edits, aliases("amount, name"), slash_command)]
|
||||
pub async fn giveblood(
|
||||
ctx: Context<'_>,
|
||||
#[description = "blood amount to buy"] amount: Option<String>,
|
||||
#[description = "In game name"] name: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
let zebedee_client = &ctx.data().zbd;
|
||||
let api_client = &ctx.data().api_client;
|
||||
|
||||
let name_str = name.as_deref().unwrap_or("User");
|
||||
|
||||
if let Some(amount) = amount {
|
||||
if let Ok(mut num) = amount.parse::<i32>() {
|
||||
num *= 1000;
|
||||
let new_amount = num.to_string();
|
||||
|
||||
let charge = Charge {
|
||||
amount: new_amount,
|
||||
description: "Buy Blood".to_string(),
|
||||
expires_in: 40,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match zebedee_client.create_charge(&charge).await {
|
||||
Ok(invoice) => {
|
||||
if let Some(request_data) = invoice.data {
|
||||
let requested_invoice =
|
||||
if let Some(requested_invoice) = request_data.invoice {
|
||||
requested_invoice
|
||||
} else {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "Failed to get invoice data.")
|
||||
.await;
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
match serde_json::to_string(&requested_invoice.request) {
|
||||
Ok(serialized_request_data) => {
|
||||
let data = serialized_request_data.trim_matches('"').to_string();
|
||||
let qr_invoice: Vec<u8> = qrcode_generator::to_png_to_vec(
|
||||
data.clone(),
|
||||
QrCodeEcc::Low,
|
||||
1024,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let description = format!("{:?}, please pay {} sats to the following invoice to give {} blood.", name_str, amount, amount);
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Blood Invoice")
|
||||
.description(description)
|
||||
.fields(vec![
|
||||
("Amount", amount.clone(), false),
|
||||
("Expires in", "40 seconds".to_string(), false),
|
||||
("Invoice: ", data.clone(), false),
|
||||
]);
|
||||
|
||||
let attachment =
|
||||
CreateAttachment::bytes(qr_invoice.as_slice(), "qr.png");
|
||||
|
||||
let builder =
|
||||
CreateMessage::new().embed(embed).add_file(attachment);
|
||||
|
||||
let invoice_message =
|
||||
ctx.channel_id().send_message(&ctx.http(), builder).await;
|
||||
|
||||
match invoice_message {
|
||||
Ok(_) => {
|
||||
println!("{:?}: invoice sent...", name_str);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{:?}: Failed to send invoice: {}", name_str, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(
|
||||
ctx.http(),
|
||||
format!("Failed to serialize request data: {}", e),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
sleep(Duration::from_millis(1000));
|
||||
|
||||
match zebedee_client.get_charge(request_data.id.clone()).await {
|
||||
Ok(charge) => {
|
||||
if let Some(data) = charge.data {
|
||||
match data.status.as_str() {
|
||||
"completed" => {
|
||||
println!(
|
||||
"{:?}: payment completed...sending blood...",
|
||||
name_str
|
||||
);
|
||||
let give_blood = mint_blood(
|
||||
name.clone(),
|
||||
amount.clone(),
|
||||
ctx,
|
||||
api_client,
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = give_blood {
|
||||
println!("sending blood error: {}", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
"expired" => {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "payment expired")
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
println!("{:?}: payment expired.", name_str);
|
||||
break;
|
||||
}
|
||||
"error" => {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "payment error")
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
println!("{:?}: Waiting for payment...", name_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
println!("error...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("invoice data error...");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), format!("Failed to create charge: {}", e))
|
||||
.await;
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "Please enter a valid number for the amount.")
|
||||
.await;
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(prefix_command, track_edits, aliases("name"), slash_command)]
|
||||
pub async fn unmute(
|
||||
ctx: Context<'_>,
|
||||
#[description = "In game name to unmute"] name: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
let zebedee_client = &ctx.data().zbd;
|
||||
let api_client = &ctx.data().api_client;
|
||||
|
||||
let name_str = name.as_deref().unwrap_or("User");
|
||||
|
||||
let amount = 1000;
|
||||
let new_amount = amount * 1000;
|
||||
|
||||
let charge = Charge {
|
||||
amount: new_amount.to_string(),
|
||||
description: "unmute player".to_string(),
|
||||
expires_in: 40,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match zebedee_client.create_charge(&charge).await {
|
||||
Ok(invoice) => {
|
||||
if let Some(request_data) = invoice.data {
|
||||
let requested_invoice = if let Some(requested_invoice) = request_data.invoice {
|
||||
requested_invoice
|
||||
} else {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "Failed to get invoice data.")
|
||||
.await;
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
match serde_json::to_string(&requested_invoice.request) {
|
||||
Ok(serialized_request_data) => {
|
||||
let data = serialized_request_data.trim_matches('"').to_string();
|
||||
let qr_invoice: Vec<u8> =
|
||||
qrcode_generator::to_png_to_vec(data.clone(), QrCodeEcc::Low, 1024)
|
||||
.unwrap();
|
||||
|
||||
let description = format!(
|
||||
"{:?}, please pay {} sats to the following invoice to unmute {}.",
|
||||
name_str, amount, name_str
|
||||
);
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Blood Invoice")
|
||||
.description(description)
|
||||
.fields(vec![
|
||||
("Amount", amount.to_string().clone(), false),
|
||||
("Expires in", "40 seconds".to_string(), false),
|
||||
("Invoice: ", data.clone(), false),
|
||||
]);
|
||||
|
||||
let attachment = CreateAttachment::bytes(qr_invoice.as_slice(), "qr.png");
|
||||
|
||||
let builder = CreateMessage::new().embed(embed).add_file(attachment);
|
||||
|
||||
let invoice_message =
|
||||
ctx.channel_id().send_message(&ctx.http(), builder).await;
|
||||
|
||||
match invoice_message {
|
||||
Ok(_) => {
|
||||
println!("{:?}: invoice sent...", name_str);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{:?}: Failed to send invoice: {}", name_str, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(
|
||||
ctx.http(),
|
||||
format!("Failed to serialize request data: {}", e),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
sleep(Duration::from_millis(1000));
|
||||
|
||||
match zebedee_client.get_charge(request_data.id.clone()).await {
|
||||
Ok(charge) => {
|
||||
if let Some(data) = charge.data {
|
||||
match data.status.as_str() {
|
||||
"completed" => {
|
||||
println!(
|
||||
"{:?}: payment completed...unmuting player...",
|
||||
name_str
|
||||
);
|
||||
let unmute_player =
|
||||
unmute_player(name.clone(), ctx, api_client).await;
|
||||
if let Err(e) = unmute_player {
|
||||
println!("unmuting player error: {}", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
"expired" => {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), "payment expired")
|
||||
.await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
println!("{:?}: payment expired.", name_str);
|
||||
break;
|
||||
}
|
||||
"error" => {
|
||||
let reply =
|
||||
ctx.channel_id().say(ctx.http(), "payment error").await;
|
||||
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
println!("{:?}: Waiting for payment...", name_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
println!("error...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("invoice data error...");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let reply = ctx
|
||||
.channel_id()
|
||||
.say(ctx.http(), format!("Failed to create charge: {}", e))
|
||||
.await;
|
||||
if let Err(e) = reply {
|
||||
println!("error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
63
src/main.rs
Normal file
63
src/main.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use anyhow::Context as _;
|
||||
use poise::serenity_prelude::{ClientBuilder, GatewayIntents};
|
||||
use shuttle_runtime::SecretStore;
|
||||
use shuttle_serenity::ShuttleSerenity;
|
||||
use zebedee_rust::ZebedeeClient;
|
||||
|
||||
mod commands;
|
||||
use commands::*;
|
||||
|
||||
mod battlemetrics;
|
||||
|
||||
pub struct Data {
|
||||
zbd: ZebedeeClient,
|
||||
api_client: reqwest::Client,
|
||||
bm_token: String,
|
||||
server_id: String,
|
||||
}
|
||||
|
||||
#[shuttle_runtime::main]
|
||||
async fn poise(#[shuttle_runtime::Secrets] secret_store: SecretStore) -> ShuttleSerenity {
|
||||
let zbd_token = secret_store
|
||||
.get("ZBD_TOKEN")
|
||||
.context("'ZBD_TOKEN' was not found")?;
|
||||
|
||||
let zebedee_client = ZebedeeClient::new(zbd_token);
|
||||
|
||||
let api_client = reqwest::Client::new();
|
||||
|
||||
let discord_token = secret_store
|
||||
.get("DISCORD_TOKEN")
|
||||
.context("'DISCORD_TOKEN' was not found")?;
|
||||
let server_id = secret_store
|
||||
.get("SERVER_ID")
|
||||
.context("'SERVER_ID' was not found")?;
|
||||
let bm_token = secret_store
|
||||
.get("BM_TOKEN")
|
||||
.context("'BM_TOKEN' was not found")?;
|
||||
|
||||
let framework = poise::Framework::builder()
|
||||
.options(poise::FrameworkOptions {
|
||||
commands: vec![giveblood(), unmute()],
|
||||
..Default::default()
|
||||
})
|
||||
.setup(|ctx, _ready, framework| {
|
||||
Box::pin(async move {
|
||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||
Ok(Data {
|
||||
zbd: zebedee_client,
|
||||
api_client,
|
||||
bm_token,
|
||||
server_id,
|
||||
})
|
||||
})
|
||||
})
|
||||
.build();
|
||||
|
||||
let client = ClientBuilder::new(discord_token, GatewayIntents::non_privileged())
|
||||
.framework(framework)
|
||||
.await
|
||||
.map_err(shuttle_runtime::CustomError::new)?;
|
||||
|
||||
Ok(client.into())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user