First Version of Drunk-Venti-Rust

This commit is contained in:
Evann Regnault 2022-01-15 17:38:28 +01:00
commit c8a846cd5a
22 changed files with 1620 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

8
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

25
Cargo.toml Normal file
View file

@ -0,0 +1,25 @@
[package]
name = "drunk-venti-rust"
version = "1.0.0"
edition = "2021"
authors = ["Evann Regnault"]
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serenity = { version = "0.10.9", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "unstable_discord_api", "collector"] }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
reqwest = "0.11.7"
serde_derive = "1.0.132"
serde = "1.0.132"
serde_json = {version = "1.0.73", features = ["indexmap"]}
levenshtein = "1.0.5"
regex = "1.5.4"
linked-hash-map = "0.5.4"
getset = "0.1.2"
mongodb = "2.1.0"
futures = "0.3.19"
rand = "0.8.4"
dotenv = "0.15.0"
dotenv_codegen = "0.15.0"

91
src/data/artifacts.rs Normal file
View file

@ -0,0 +1,91 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
use serenity::builder::CreateEmbed;
use crate::data::domains::Domain;
#[derive(Serialize, Deserialize, Debug)]
pub struct Set {
pub goblet: Option<Box<str>>,
pub plume: Option<Box<str>>,
pub circlet: Option<Box<str>>,
pub flower: Option<Box<str>>,
pub sands: Option<Box<str>>
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Artifact {
pub id: Box<str>,
pub name: Box<str>,
pub set_piece: Vec<u32>,
pub sets: Set,
pub bonuses: Vec<Box<str>>,
pub rarity: Vec<u8>,
pub domain: Option<Box<str>>
}
impl Artifact{
pub async fn get(artifact: &str) -> Artifact {
let url = format!("http://localhost:3000/api/artifacts/{}", artifact);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Artifact>().await.expect("Wrong json format");
}
async fn get_all() -> Vec<Box<str>> {
let url = format!("http://localhost:3000/api/artifacts");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Box<str>>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub(crate) async fn search(artifact: &str) -> Vec<Artifact> {
let url = format!("http://localhost:3000/api/artifacts/search/{}", artifact);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Artifact>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub async fn to_embed(&self) -> CreateEmbed {
let mut embed = CreateEmbed::default();
embed.title(format!("{} | {}", self.name, ":star:".repeat(self.rarity.get(0).expect("").to_owned() as usize)));
embed.thumbnail(format!("https://raw.githubusercontent.com/MadeBaruna/paimon-moe/main/static/images/artifacts/{}_circlet.png", self.id));
for i in 0..self.set_piece.len(){
embed.field(format!("{}-Pieces", self.set_piece.get(i).expect("No such piece set")),
format!("{}", self.bonuses.get(i).expect("Mo such effects")),
false
);
}
match &self.domain {
Some(d) => {
let domain = Domain::get(d).await;
embed.field("Domain", domain.name(), true);
}
_ => {}
}
return embed;
}
#[allow(dead_code)]
pub fn id(&self) -> &str {
return self.id.as_ref()
}
#[allow(dead_code)]
pub fn name(&self) -> &str {
return self.name.as_ref()
}
}
pub async fn test_artifacts() {
for a in Artifact::get_all().await {
println!("{}", a);
let arti = Artifact::get(&a).await;
println!("Name : {}\nCirclet : {}\nBonuses {:?}", arti.name, arti.sets.goblet.unwrap_or(Box::from("")), arti.bonuses);
}
}

61
src/data/builds.rs Normal file
View file

@ -0,0 +1,61 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RoleWeapon {
pub id: Box<str>,
pub refine: Option<Vec<u8>>
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RoleStat {
pub sands: Box<str>,
pub goblet: Box<str>,
pub circlet: Box<str>
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Role{
pub name: Box<str>,
pub recommended: bool,
pub weapons: Vec<RoleWeapon>,
pub artifacts: Vec<Vec<Box<str>>>,
pub main_stats: RoleStat,
pub sub_stats: Vec<Box<str>>,
pub talent: Vec<Box<str>>,
pub tip: Box<str>,
pub note: Box<str>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Builds {
pub roles: Vec<Role>
}
impl Builds {
pub(crate) async fn get(build: &str) -> Builds {
let url = format!("http://localhost:3000/api/builds/{}", build);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Builds>().await.expect("Wrong json format");
}
async fn get_all() -> Vec<Box<str>> {
let url = format!("http://localhost:3000/api/builds");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Box<str>>>().await.expect("Wrong json format");
}
}
pub async fn test_builds() {
for a in Builds::get_all().await {
println!("------------------");
let builds = Builds::get(&a).await;
println!("Roles for {} : {:?}\n", a, builds.roles.into_iter().map(|x| x.name).collect::<Vec<Box<str>>>());
}
}

76
src/data/characters.rs Normal file
View file

@ -0,0 +1,76 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
use crate::data::builds::{Role};
use crate::data::elements::Element;
use crate::data::items::Item;
use crate::data::shared_structs::{Ascension, WeaponType};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CharacterStats {
pub hp: u64,
pub atk: u64,
pub def: u64
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CharacterMaterials {
pub book: Vec<Item>,
pub material: Vec<Item>,
pub boss: Item
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Character {
pub name: Box<str>,
pub id: Box<str>,
pub rarity: u8,
pub element: Element,
pub weapon: WeaponType,
pub ascension: Vec<Ascension>,
pub stats: CharacterStats,
pub material: CharacterMaterials,
pub builds: Vec<Role>
}
impl Clone for Character {
fn clone(&self) -> Self {
let x = serde_json::to_string(self).expect("str");
serde_json::from_str::<Character>(x.as_str()).expect("An error occurred")
}
}
impl Character {
#[allow(dead_code)]
pub(crate) async fn get(character: &str) -> Character {
let url = format!("http://localhost:3000/api/characters/{}", character);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Character>().await.expect("Wrong json format");
}
#[allow(dead_code)]
async fn get_all() -> Vec<Box<str>> {
let url = format!("http://localhost:3000/api/characters");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Box<str>>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub(crate) async fn search(character: &str) -> Vec<Character> {
let url = format!("http://localhost:3000/api/characters/search/{}", character);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Character>>().await.expect("Wrong json format");
}
}
#[allow(dead_code)]
pub async fn test_character () {
for a in Character::get_all().await {
println!("{}", a);
let char = Character::get(&a).await;
println!("Name : {}", char.name);
}
}

70
src/data/domains.rs Normal file
View file

@ -0,0 +1,70 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DomainMonster {
pub id: Box<str>,
pub name: Box<str>,
pub count: u8,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DomainReward {
pub adventure_exp: Box<str>,
pub mora: Box<str>,
pub friendship_exp: Box<str>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DomainDifficulty {
pub s: u64,
pub id: Box<str>,
pub name: Box<str>,
pub ar: u8,
pub level: u8,
pub reward: DomainReward,
pub monsters: Vec<DomainMonster>,
pub disorder: Vec<Box<str>>
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Domain {
pub name: Box<str>,
pub domains: Vec<DomainDifficulty>,
pub artifacts: Vec<Box<str>>,
}
impl Domain {
#[allow(dead_code)]
pub async fn get(domain: &str) -> Domain {
let url = format!("http://localhost:3000/api/domains/{}", domain);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Domain>().await.expect("Wrong json format");
}
#[allow(dead_code)]
async fn get_all() -> Vec<Box<str>> {
let url = format!("http://localhost:3000/api/domains");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Box<str>>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub fn name(&self) -> String {
return self.name.to_string();
}
}
#[allow(dead_code)]
pub async fn test_domains () {
for a in Domain::get_all().await {
println!("{}", a);
let domain = Domain::get(&a).await;
println!("Name : {}", domain.name);
}
}

32
src/data/elements.rs Normal file
View file

@ -0,0 +1,32 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Element {
pub id: Box<str>,
pub name: Box<str>,
pub simple_name: Box<str>,
pub color: u64
}
impl Element{
async fn get(element: &str) -> Element {
let url = format!("http://localhost:3000/api/elements/{}", element);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Element>().await.expect("Wrong json format");
}
async fn get_all() -> Vec<Box<str>> {
let url = format!("http://localhost:3000/api/elements");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Box<str>>>().await.expect("Wrong json format");
}
}
pub async fn test_elem() {
println!("All elems : {:?}", Element::get_all().await);
let geo = Element::get("geo").await;
println!("Name : {}\nColor : #{:x}", geo.name, geo.color);
}

62
src/data/events.rs Normal file
View file

@ -0,0 +1,62 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Event {
pub name: Box<str>,
pub image: Option<Box<str>>,
pub start: Box<str>,
pub end: Box<str>,
pub url: Option<Box<str>>,
pub start_timestamp: u64,
pub end_timestamp: u64,
pub show_on_home: Option<bool>
}
impl Clone for Event {
fn clone(&self) -> Self {
let x = serde_json::to_string(&self).expect("");
serde_json::from_str::<Event>(x.as_str()).expect("")
}
}
impl Event {
#[allow(dead_code)]
pub(crate) async fn get_current() -> Vec<Event> {
let url = format!("http://localhost:3000/api/events/current");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Event>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub(crate) async fn get_upcoming() -> Vec<Event> {
let url = format!("http://localhost:3000/api/events/upcoming");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Event>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
async fn get_all() -> Vec<Event> {
let url = format!("http://localhost:3000/api/events");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Event>>().await.expect("Wrong json format");
}
}
#[allow(dead_code)]
pub async fn test_events() {
let upcoming = Event::get_upcoming().await;
println!("UPCOMING EVENTS\n--------");
for event in upcoming {
println!("Name : {}", event.name);
}
let current = Event::get_current().await;
println!("CURRENT EVENTS\n--------");
for event in current {
println!("Name : {}", event.name);
}
}

43
src/data/items.rs Normal file
View file

@ -0,0 +1,43 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Item {
pub id: Box<str>,
pub name: Box<str>,
pub day: Option<Vec<Box<str>>>,
pub rarity: Option<u8>,
pub parent: Option<Box<str>>
}
impl Item{
async fn get(item: &str) -> Item {
let url = format!("http://localhost:3000/api/items/{}", item);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Item>().await.expect("Wrong json format");
}
async fn get_all() -> Vec<Box<str>> {
let url = format!("http://localhost:3000/api/items");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Box<str>>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
async fn search(item: &str) -> Vec<Item> {
let url = format!("http://localhost:3000/api/items/search/{}", item);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Item>>().await.expect("Wrong json format");
}
}
pub async fn test_items() {
println!("List all items : {:?}", Item::get_all().await);
let item = Item::get("relic_from_guyun").await;
println!("Name : {}\nDays : {:?}", item.name, item.day.unwrap_or(vec![]));
let item = Item::get("mora").await;
println!("Name : {}\nDays : {:?}", item.name, item.day.unwrap_or(vec![]));
}

20
src/data/mod.rs Normal file
View file

@ -0,0 +1,20 @@
pub mod elements;
pub mod artifacts;
pub mod items;
pub mod weapons;
pub mod builds;
pub mod characters;
pub mod events;
pub mod shared_structs;
pub mod domains;
#[allow(dead_code)]
pub async fn test() {
elements::test_elem().await;
artifacts::test_artifacts().await;
items::test_items().await;
weapons::test_weapons().await;
builds::test_builds().await;
characters::test_character().await;
events::test_events().await;
}

View file

@ -0,0 +1,24 @@
use serde_derive::{Serialize, Deserialize};
use crate::data::items::Item;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct WeaponType {
pub id: Box<str>,
pub name: Box<str>
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AscensionItem {
pub item: Item,
pub amount: u32
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Ascension {
pub items: Vec<AscensionItem>,
pub mora: u64
}

121
src/data/weapons.rs Normal file
View file

@ -0,0 +1,121 @@
use reqwest::Url;
use serde_derive::{Serialize, Deserialize};
use serenity::builder::CreateEmbed;
use regex::Regex;
use crate::data::shared_structs::Ascension;
use crate::data::shared_structs::WeaponType;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Skill {
pub name: Option<Box<str>>,
pub description: Option<Box<str>>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Secondary {
pub name: Option<Box<str>>,
pub stats: Option<Vec<Option<f64>>>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Extras {
pub id: Box<str>,
pub name: Box<str>,
#[serde(rename = "type")]
pub weapon_type: Box<str>,
pub rarity: u8,
pub description: Box<str>,
pub skill: Skill,
pub secondary: Secondary,
pub atk: Vec<Option<f64>>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Weapon {
pub name: Box<str>,
pub id: Box<str>,
pub rarity: u8,
pub atk: u64,
pub secondary: Box<str>,
#[serde(rename = "type")]
pub weapon_type: WeaponType,
pub source: Box<str>,
pub ascension: Vec<Ascension>,
pub extras: Extras,
}
impl Weapon {
#[allow(dead_code)]
pub async fn get(weapon: &str) -> Weapon {
let url = format!("http://localhost:3000/api/weapons/{}", weapon);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Weapon>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub async fn get_all() -> Vec<Box<str>> {
let url = format!("http://localhost:3000/api/weapons");
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Box<str>>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub async fn search(weapon: &str) -> Vec<Weapon> {
let url = format!("http://localhost:3000/api/weapons/search/{}", weapon);
let url = Url::parse(&*url).expect("Can't convert url");
return reqwest::get(url).await.expect("Can't access Url").json::<Vec<Weapon>>().await.expect("Wrong json format");
}
#[allow(dead_code)]
pub fn name(&self) -> &str {
return &self.name;
}
#[allow(dead_code)]
pub fn id(&self) -> &str {
return &self.id;
}
#[allow(dead_code)]
pub fn to_embed(&self) -> CreateEmbed {
let mut embed = CreateEmbed::default();
embed.title(format!("{} | {}", self.name, ":star:".repeat(self.rarity as usize)));
embed.description(format!("{}", self.extras.description));
embed.thumbnail(format!("https://raw.githubusercontent.com/MadeBaruna/paimon-moe/main/static/images/weapons/{}.png", self.id));
// Fields
match self.extras.skill.name.as_ref() {
Some(t) => {
let re = Regex::new("(<|</)span(?: [^>]*)?>").expect("Unknown regex");
embed.field(format!("Passive : {}", t),
format!("{}",
re.replace_all(self.extras.skill.description.as_ref()
.unwrap_or(&Box::from(""))
.to_string().as_str(), "**")).replace("\\n","\n"),
false);
}
_ => {}
}
embed.field("Main Stat", format!("{}", self.secondary), true);
embed.field("Source", format!("{}", self.source), true);
embed.field("\u{200b}", "\u{200b}", true);
embed.field("Type", format!("{}", self.weapon_type.name), true);
embed.field("Ascension Item", format!("{}", self.ascension.get(0).expect("").items.get(0).expect("").item.name), true);
embed.field("\u{200b}", "\u{200b}", true);
return embed;
}
}
pub async fn test_weapons() {
for w in Weapon::get_all().await {
println!("------------------");
println!("{}", w);
}
}

View file

@ -0,0 +1,44 @@
use linked_hash_map::LinkedHashMap;
use serenity::client::Context;
use serenity::model::interactions::{InteractionApplicationCommandCallbackDataFlags, InteractionResponseType};
use serenity::model::interactions::application_command::{ApplicationCommandInteraction, ApplicationCommandInteractionDataOption};
use serenity::model::interactions::message_component::{MessageComponentInteraction};
use crate::data::artifacts::Artifact;
use crate::interactions::utils::create_action_row_basic;
pub async fn genshin_artifact_interaction(ctx: &Context, command: &ApplicationCommandInteraction, opt: &ApplicationCommandInteractionDataOption) {
let weapon = opt.options.get(0).expect("No argument for command Genshin artifact")
.value.as_ref().expect("").as_str().expect("Not a string");
let artifacts = Artifact::search(weapon).await;
let mut artifact_list: LinkedHashMap<String, String> = LinkedHashMap::new();
for a in artifacts {
artifact_list.insert(a.id().to_string(), a.name().to_string());
}
let ar = create_action_row_basic(artifact_list, "artifact");
command.create_interaction_response(&ctx.http, |res| {
res.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Select an Artifact")
.components(|c| c.add_action_row(ar))
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
})
}).await.expect("Message didn't got sent");
}
pub async fn show_artifact_embed(ctx: &Context, command: &MessageComponentInteraction, artifact_name: String){
let artifact = Artifact::get(artifact_name.as_str()).await;
let embed = artifact.to_embed().await;
command.create_interaction_response(&ctx.http, |res| {
res.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.embeds(vec!(embed).into_iter())
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
})
}).await.expect("Interaction failed");
}

View file

@ -0,0 +1,416 @@
use std::borrow::{Borrow};
use linked_hash_map::LinkedHashMap;
use serenity::builder::{CreateActionRow, CreateButton, CreateEmbed};
use serenity::client::Context;
use serenity::model::interactions::{InteractionApplicationCommandCallbackDataFlags, InteractionResponseType};
use serenity::model::interactions::application_command::{ApplicationCommandInteraction, ApplicationCommandInteractionDataOption};
use serenity::model::interactions::message_component::{ButtonStyle, MessageComponentInteraction};
use crate::data::artifacts::Artifact;
use crate::data::builds::{Builds, Role};
use crate::data::characters::Character;
use crate::data::weapons::Weapon;
use crate::interactions::utils::create_action_row_basic;
pub async fn genshin_build_interaction(ctx: &Context, command: &ApplicationCommandInteraction, opt: &ApplicationCommandInteractionDataOption) {
let char = opt.options.get(0).expect("No argument for command Genshin builds")
.value.as_ref().expect("").as_str().expect("Not a string");
let characters = Character::search(char).await;
let mut character_list: LinkedHashMap<String, String> = LinkedHashMap::new();
for c in characters {
character_list.insert(c.id.to_string(), c.name.to_string());
}
let ar = create_action_row_basic(character_list, "build");
command.create_interaction_response(&ctx.http, |res| {
res.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Select a Character")
.components(|c| c.add_action_row(ar))
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
})
}).await.expect("Message didn't got sent");
}
fn is_menu(arg: &str) -> bool {
match arg {
"home" | "artifacts" | "weapons" | "notes" => true,
_ => false
}
}
pub async fn build_interact(ctx: &Context, interaction: &MessageComponentInteraction, arguments: String) {
let mut args = arguments.split("_");
let command = args.next();
match command.expect("") {
c if is_menu(c) => {
let role_index = args.next().expect("").parse::<usize>().expect("");
let character_name = args.collect::<Vec<_>>().join("_");
let character = Character::get(character_name.as_str()).await;
let builds = Builds::get(character_name.as_str()).await;
let role = builds.roles.get(role_index).expect("");
let ar = nav_button(character_name, role_index);
let embed = match c {
"home" => home_embed(role, character).await,
"artifacts" => artifact_embed(role, character).await,
"weapons" => weapons_embed(role, character).await,
"notes" => note_embed(role, character).await,
_ => CreateEmbed::default()
};
interaction.create_interaction_response(&ctx.http, |res| {
res.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.add_embed(embed)
.components(|c| c.add_action_row(ar))
})
}).await.expect("Can't send response");
}
_ => {
let mut character = args.collect::<Vec<_>>().join("_");
character = [command.expect(""), character.as_str()].join("_");
character = character.strip_suffix("_").unwrap_or(character.as_str()).parse().unwrap();
build_select(&ctx, &interaction, character).await;
}
}
}
fn nav_button(character_name: String, role_index: usize) -> CreateActionRow {
let mut ar = CreateActionRow::default();
let mut btn_home = CreateButton::default();
btn_home
.custom_id(format!("build_home_{}_{}", role_index, character_name))
.style(ButtonStyle::Primary)
.label("Home");
let mut btn_artifacts = CreateButton::default();
btn_artifacts
.custom_id(format!("build_artifacts_{}_{}", role_index, character_name))
.style(ButtonStyle::Primary)
.label("Artifacts");
let mut btn_weapons = CreateButton::default();
btn_weapons
.custom_id(format!("build_weapons_{}_{}", role_index, character_name))
.style(ButtonStyle::Primary)
.label("Weapons");
let mut btn_notes = CreateButton::default();
btn_notes
.custom_id(format!("build_notes_{}_{}", role_index, character_name))
.style(ButtonStyle::Primary)
.label("Notes");
let mut btn_builds = CreateButton::default();
btn_builds
.custom_id(format!("build_{}", character_name))
.style(ButtonStyle::Success)
.label("Other builds");
ar.add_button(btn_home);
ar.add_button(btn_artifacts);
ar.add_button(btn_weapons);
ar.add_button(btn_notes);
ar.add_button(btn_builds);
ar
}
async fn build_select(ctx: &Context, command: &MessageComponentInteraction, character: String) {
let char = Character::get(character.as_str()).await;
let roles = &char.builds;
let roles = roles.into_iter().map(|c| c.name.to_string()).collect::<Vec<String>>();
let mut rolemap = LinkedHashMap::<String, String>::new();
for i in 0..roles.len() {
rolemap.insert(format!("{}_{}", i, character), roles.get(i).expect("").to_string());
}
let ar = create_action_row_basic(rolemap, "build_home");
command.create_interaction_response(&ctx.http, |res| {
res.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.components(|c| c.add_action_row(ar))
.create_embed(|e| {
e.title(format!("{}", char.name))
.thumbnail(format!("https://github.com/MadeBaruna/paimon-moe/raw/main/static/images/characters/{}.png", char.id))
.color(char.element.color)
.description("Select a build !")
.footer(|f| {
f.text(format!("Data from : https://paimon.moe/characters/{}", char.id))
})
})
})
}).await.expect("Can't send response");
return;
}
pub async fn home_embed(role: &Role, character: Character) -> CreateEmbed {
let mut embed = CreateEmbed::default();
embed.title(format!("{} | {}", character.name, role.name));
embed.thumbnail(format!("https://github.com/MadeBaruna/paimon-moe/raw/main/static/images/characters/{}.png", character.id));
embed.color(character.element.color);
match role.weapons.get(0) {
Some(t) => {
embed.field("Best Weapon",
Weapon::get(t.id.borrow()).await.name,
true,
);
}
_ => {
embed.field("Best Weapon", "TBD", true);
}
}
embed.field("Skill Order",
role.talent.to_owned().into_iter().map(|t| format!("- {}\n", t))
.collect::<Vec<String>>().join("").strip_suffix("\n").expect(""),
true,
);
match role.artifacts.get(0) {
Some(a) => {
let mut artifact_string: Vec<String> = vec![];
let str;
match a.len() {
1 => {
let artifact_name = match a.get(0).expect("").to_string().as_str() {
"+18%_atk_set" => "+18% Atk Set".to_string(),
t => Artifact::get(t).await.name.to_string(),
};
str = format!("(4) {}", artifact_name);
}
2 => {
for art in a {
let artifact_name = match art.to_string().as_str() {
"+18%_atk_set" => "+18% Atk Set".to_string(),
t => Artifact::get(t).await.name.to_string(),
};
artifact_string.push(format!("(2) {}", artifact_name));
}
str = artifact_string.join(" & ");
}
_ => {
artifact_string.push("Choose 2 sets :".to_string());
for art in a {
let artifact_name = match art.to_string().as_str() {
"+18%_atk_set" => "+18% Atk Set".to_string(),
t => Artifact::get(t).await.name.to_string(),
};
artifact_string.push(format!("(2) {}", artifact_name));
}
str = artifact_string.join(", ")
}
}
embed.field("Best Artifacts", str, false);
}
_ => { embed.field("Best Artifacts", "TBD", false); }
}
embed.field("Circlet", &role.main_stats.circlet, true);
embed.field("Goblet", &role.main_stats.goblet, true);
embed.field("Sands", &role.main_stats.sands, true);
embed.footer(|f| {
f.text(format!("Data from : https://paimon.moe/characters/{}", character.id))
});
embed
}
async fn artifact_embed(role: &Role, character: Character) -> CreateEmbed {
let mut embed = CreateEmbed::default();
embed.title(format!("{} | {}", character.name, role.name));
match role.artifacts.get(0) {
Some(t) if t.get(0).expect("").to_string() != "TBD" => {
embed.thumbnail(format!("https://raw.githubusercontent.com/MadeBaruna/paimon-moe/main/static/images/artifacts/{}_circlet.png", t.get(0).expect("")));
}
_ => {
embed.thumbnail(format!("https://github.com/MadeBaruna/paimon-moe/raw/main/static/images/characters/{}.png", character.id));
}
}
embed.color(character.element.color);
let mut all_artefacts_string: Vec<String> = vec![];
for i in 0..role.artifacts.len() {
match role.artifacts.get(i) {
Some(a) => {
let mut artifact_string: Vec<String> = vec![];
let str;
match a.len() {
1 => {
let artifact_name = match a.get(0).expect("").to_string().as_str() {
"+18%_atk_set" => "+18% Atk Set".to_string(),
t => Artifact::get(t).await.name.to_string(),
};
str = format!("(4) {}", artifact_name);
}
2 => {
for art in a {
let artifact_name = match art.to_string().as_str() {
"+18%_atk_set" => "+18% Atk Set".to_string(),
t => Artifact::get(t).await.name.to_string(),
};
artifact_string.push(format!("(2) {}", artifact_name));
}
str = artifact_string.join(" & ");
}
_ => {
artifact_string.push("Choose 2 sets :".to_string());
for art in a {
let artifact_name = match art.to_string().as_str() {
"+18%_atk_set" => "+18% Atk Set".to_string(),
t => Artifact::get(t).await.name.to_string(),
};
artifact_string.push(format!("(2) {}", artifact_name));
}
str = artifact_string.join(", ")
}
}
all_artefacts_string.push(format!("- {}", str));
}
_ => { all_artefacts_string.push(format!("- TBD")); }
}
}
if all_artefacts_string.len() > 0 {
embed.field("Best Artifacts", all_artefacts_string.join("\n"), false);
} else {
embed.field("Best Artifacts", "- TBD", false);
}
embed.field("Sub-stats", {
format!("- {}", role.sub_stats.join("\n-"))
}, false);
embed.field("Circlet", &role.main_stats.circlet, true);
embed.field("Goblet", &role.main_stats.goblet, true);
embed.field("Sands", &role.main_stats.sands, true);
embed.footer(|f| {
f.text(format!("Data from : https://paimon.moe/characters/{}", character.id))
});
embed
}
async fn weapons_embed(role: &Role, character: Character) -> CreateEmbed {
let mut embed = CreateEmbed::default();
embed.title(format!("{} | {}", character.name, role.name));
match role.weapons.get(0) {
Some(t) if t.id.to_string() != "TBD" => {
embed.thumbnail(format!("https://raw.githubusercontent.com/MadeBaruna/paimon-moe/main/static/images/weapons/{}.png", t.id));
}
_ => {
embed.thumbnail(format!("https://github.com/MadeBaruna/paimon-moe/raw/main/static/images/characters/{}.png", character.id));
}
}
embed.color(character.element.color);
let mut all_weapons_string: Vec<String> = vec![];
for i in 0..role.weapons.len() {
match role.weapons.get(i) {
Some(a) if a.id.to_string() != "TBD" => {
let weapon = Weapon::get(a.id.borrow()).await;
all_weapons_string.push(format!("- {}", weapon.name));
}
_ => { all_weapons_string.push(format!("- TBD")); }
}
}
if all_weapons_string.len() > 0 {
embed.field("Best Weapons", all_weapons_string.join("\n"), false);
} else {
embed.field("Best Weapons", "- TBD", false);
}
embed.footer(|f| {
f.text(format!("Data from : https://paimon.moe/characters/{}", character.id))
});
embed
}
async fn note_embed(role: &Role, character: Character) -> CreateEmbed {
let mut embed = CreateEmbed::default();
embed.title(format!("{} | {}", character.name, &role.name));
embed.thumbnail(format!("https://github.com/MadeBaruna/paimon-moe/raw/main/static/images/characters/{}.png", character.id));
embed.color(character.element.color);
match &role.note {
n=> {
let x = n.split("\n");
let mut first = true;
let mut add_before = "";
for note_paragraph in x.collect::<Vec<&str>>() {
if note_paragraph.len() == 0 {continue};
if note_paragraph.len() < 64 && add_before.len() == 0{
add_before = note_paragraph;
continue;
}
if add_before.len() > 0 {
let note_paragraph = format!("**{}**\n{}", add_before, note_paragraph);
embed.field(if first { "Notes" } else { "\u{200b}" }, note_paragraph, false);
add_before = "";
} else {
embed.field(if first { "Notes" } else { "\u{200b}" }, note_paragraph, false);
first = false;
}
}
}
};
match &role.tip {
n=> {
let x = n.split("\n");
let mut first = true;
let mut add_before = "";
for tip_paragraph in x.collect::<Vec<&str>>() {
if tip_paragraph.len() == 0 {continue};
if tip_paragraph.len() < 64 {
add_before = tip_paragraph;
continue;
}
if add_before.len() > 0 {
let tip_paragraph = format!("**{}**\n{}", add_before, tip_paragraph);
embed.field(if first { "Tips" } else { "\u{200b}" }, tip_paragraph, false);
add_before = "";
} else {
embed.field(if first { "Tips" } else { "\u{200b}" }, tip_paragraph, false);
first = false;
}
}
}
};
embed.footer(|f| {
f.text(format!("Data from : https://paimon.moe/characters/{}", character.id))
});
embed
}

View file

@ -0,0 +1,21 @@
use serenity::client::Context;
use serenity::model::interactions::application_command::ApplicationCommandInteraction;
use crate::interactions::genshin::artifacts::genshin_artifact_interaction;
use crate::interactions::genshin::build::genshin_build_interaction;
use crate::interactions::genshin::weapons::genshin_weapon_interaction;
pub mod build;
pub mod weapons;
pub mod artifacts;
pub async fn genshin_interaction(ctx: Context, command: ApplicationCommandInteraction) {
let sub_command = command.data.options.get(0).expect("No command provided");
match sub_command.name.as_str() {
"builds" => genshin_build_interaction(&ctx, &command, sub_command).await,
"artifact" => genshin_artifact_interaction(&ctx, &command, sub_command).await,
"weapon" => genshin_weapon_interaction(&ctx, &command, sub_command).await,
_ => println!("Unknown Command")
}
}

View file

@ -0,0 +1,45 @@
use linked_hash_map::LinkedHashMap;
use serenity::client::Context;
use serenity::model::interactions::application_command::ApplicationCommandInteraction;
use serenity::model::interactions::application_command::ApplicationCommandInteractionDataOption;
use serenity::model::interactions::InteractionResponseType;
use serenity::model::prelude::InteractionApplicationCommandCallbackDataFlags;
use serenity::model::prelude::message_component::MessageComponentInteraction;
use crate::data::weapons::Weapon;
use crate::interactions::utils::create_action_row_basic;
pub async fn genshin_weapon_interaction(ctx: &Context, command: &ApplicationCommandInteraction, opt: &ApplicationCommandInteractionDataOption) {
let weapon = opt.options.get(0).expect("No argument for command Genshin build")
.value.as_ref().expect("").as_str().expect("Not a string");
let weapons = Weapon::search(weapon).await;
let mut weapon_list: LinkedHashMap<String, String> = LinkedHashMap::new();
for w in weapons {
weapon_list.insert(w.id().to_string(), w.name().to_string());
}
let ar = create_action_row_basic(weapon_list, "weapon");
command.create_interaction_response(&ctx.http, |res| {
res.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Select a Weapon")
.components(|c| c.add_action_row(ar))
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
})
}).await.expect("The response didn't get sent");
}
pub async fn show_weapon_embed(ctx: &Context, command: &MessageComponentInteraction, weapon_name: String){
let weapon = Weapon::get(weapon_name.as_str()).await;
command.create_interaction_response(&ctx.http, |res| {
res.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.embeds(vec!(weapon.to_embed()).into_iter())
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
})
}).await.expect("The message didn't got sent");
}

30
src/interactions/mod.rs Normal file
View file

@ -0,0 +1,30 @@
pub mod status_message;
pub mod genshin;
#[path = "../data/mod.rs"]
mod data;
#[path = "../utils/mod.rs"]
pub mod utils;
use serenity::client::Context;
use serenity::model::interactions::{InteractionApplicationCommandCallbackDataFlags, InteractionResponseType};
use serenity::model::interactions::application_command::ApplicationCommandInteraction;
pub async fn pong(ctx : Context, command : ApplicationCommandInteraction) {
let res = command.create_interaction_response(ctx.http, |res| {
res.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|response| {
response.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
.content("An error has occurred")
})
}).await;
match res {
Ok(()) => {}
Err(e) =>{
println!("An error has occured : {}", e)
}
}
}

View file

@ -0,0 +1,177 @@
use std::time::Duration;
use rand::Rng;
use serenity::{
client::{
Context
}
};
use serenity::builder::CreateEmbed;
use serenity::model::id::{ChannelId, MessageId};
use serenity::model::interactions::application_command::ApplicationCommandInteraction;
use serenity::model::interactions::InteractionApplicationCommandCallbackDataFlags;
use serenity::model::prelude::{InteractionResponseType};
use serenity::utils::Colour;
use crate::interactions::data::events::Event;
use crate::utils::mongo::{add_discord_status_message, get_all_status_messages, get_discord_status_message, StatusMessage};
pub fn copy_embed(from: &Vec<CreateEmbed>) -> Vec<CreateEmbed> {
let mut cln: Vec<CreateEmbed> = vec![];
for x in from {
cln.push(x.clone());
}
cln
}
pub async fn update_status_message(ctx: Context) {
let forever = tokio::task::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(60*60));
loop {
let mut x = get_all_status_messages().await;
x.reverse();
let embeds = create_status_embed().await;
for sm in x {
let msg = ChannelId::from(sm.channel_id as u64).message(&ctx.http, sm.message_id as u64).await;
match msg {
Ok(mut m) => {
let copies = copy_embed(&embeds);
m.edit(&ctx.http, |f| {
f.set_embeds(copies)
}).await.unwrap();
}
Err(_) => {}
}
}
interval.tick().await;
}
});
forever.await.expect("Stopped for some reasons");
}
#[allow(dead_code)]
pub async fn create_status_interaction(ctx: Context, command: ApplicationCommandInteraction) {
let embeds = create_status_embed().await;
let channel_option = &command.data.resolved.channels;
let channel_id = channel_option.keys().next().expect("No options passed");
let message = get_discord_status_message(&command.guild_id.expect("Not in guild").as_u64().to_owned()).await;
match message {
Some(e) => {
let rm = ChannelId::from(e.channel_id as u64)
.message(&ctx.http, MessageId::from(e.message_id as u64))
.await;
match rm {
msg if rm.is_ok() => {
msg.unwrap().delete(&ctx.http).await.unwrap();
}
_ => {}
}
}
_ => {}
};
let msg = channel_id.send_message(&ctx.http, |f| {
f.add_embeds(embeds)
}).await.expect("Can't send Message");
let sm = StatusMessage {
message_id: *msg.id.as_u64() as i64,
channel_id: *msg.channel_id.as_u64() as i64,
guild_id: *command.guild_id.expect("Not in guild").as_u64() as i64,
};
add_discord_status_message(sm).await;
command.create_interaction_response(&ctx.http, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.create_embed(|e| {
e.title("Command Successful !");
e.color(Colour::new(0x00ff00));
e.description(format!("Status message created !"))
})
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
})
}).await.unwrap();
}
async fn create_status_embed() -> Vec<CreateEmbed> {
let mut embeds: Vec<CreateEmbed> = vec![];
let mut current = Event::get_current().await;
let mut others = current.clone();
let mut question_marks = format!("");
let mut upcoming = Event::get_upcoming().await;
current = current.into_iter().filter(|p| p.show_on_home.unwrap_or(false)).collect::<Vec<Event>>();
others = others.into_iter().filter(|p| !p.show_on_home.unwrap_or(false)).collect::<Vec<Event>>();
upcoming = upcoming.into_iter().filter(|p| p.show_on_home.unwrap_or(false)).collect::<Vec<Event>>();
let upcoming_event: Option<&Event> = upcoming.get(0);
for e in current {
question_marks = format!("{}?", question_marks);
let mut embed = CreateEmbed::default();
embed.title(e.name);
embed.color(Colour::new(rand::thread_rng().gen_range(0x000000..0xffffff)));
match &e.image {
Some(url) => {
embed.image(format!("https://github.com/MadeBaruna/paimon-moe/raw/main/static/images/events/{}", url));
}
_ => {}
};
match e.url {
Some(t) => { embed.url(format!("{}{}", t, question_marks)); }
_ => {}
};
embed.description(format!("Ends : <t:{}:R>", e.end_timestamp));
embeds.push(embed);
}
// Other events embed
let mut other_embed = CreateEmbed::default();
other_embed.title("Other Events");
other_embed.color(Colour::new(rand::thread_rng().gen_range(0x000000..0xffffff)));
for e in others {
other_embed.field(e.name, format!("Ends : <t:{}:R>", e.end_timestamp), false);
}
embeds.push(other_embed);
match upcoming_event {
Some(e) => {
let mut upcoming_embed = CreateEmbed::default();
question_marks = format!("{}?", question_marks);
upcoming_embed.title(&e.name);
upcoming_embed.description(format!("Starts : <t:{}:R>", e.start_timestamp));
upcoming_embed.color(Colour::new(rand::thread_rng().gen_range(0x000000..0xffffff)));
match &e.image {
Some(url) => {
upcoming_embed.image(format!("https://github.com/MadeBaruna/paimon-moe/raw/main/static/images/events/{}", url));
}
_ => {}
};
match &e.url {
Some(url) => { upcoming_embed.url(format!("{}{}", url, question_marks)); }
_ => {}
};
embeds.push(upcoming_embed);
}
_ => {}
};
embeds
}

164
src/main.rs Normal file
View file

@ -0,0 +1,164 @@
mod interactions;
mod data;
pub mod utils;
use std::env;
use dotenv::dotenv;
use serenity::{
async_trait,
model::{
gateway::Ready,
interactions::{
application_command::{
ApplicationCommand,
ApplicationCommandOptionType,
},
Interaction,
},
},
prelude::*,
};
use serenity::client::bridge::gateway::GatewayIntents;
use serenity::model::gateway::Activity;
use crate::interactions::genshin::artifacts::show_artifact_embed;
use crate::interactions::genshin::build::build_interact;
use crate::interactions::genshin::weapons::show_weapon_embed;
use crate::interactions::status_message::update_status_message;
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, _ctx: Context, _bot: Ready) {
println!("{} connected!", _bot.user.name);
update_status_message(_ctx.clone()).await;
_ctx.set_activity(Activity::playing("Drinking")).await;
let x = ApplicationCommand::get_global_application_commands(&_ctx.http).await.unwrap();
for i in x {
match i.name.as_str() {
"genshin" => (),
"createstatusmessage" => (),
_ => {
let _result_delete = ApplicationCommand::delete_global_application_command(&_ctx.http, i.id).await;
match _result_delete {
Ok(()) => { println!("Deleted command {}", i.name) }
Err(f) => { println!("An error occurred deleting {} : {}", i.name, f) }
}
}
}
}
let x = ApplicationCommand::create_global_application_command(&_ctx.http, |command| {
command.name("genshin").description("Get Informations about Genshin Impact.").create_option(|option| {
option.name("builds")
.description("Shows builds for a Genshin Impact Characters")
.kind(ApplicationCommandOptionType::SubCommand)
.create_sub_option(|so| {
so.name("character").description("Character to get builds for")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
})
.create_option(|option| {
option.name("weapon")
.description("Shows infos on a Genshin Impact weapon.")
.kind(ApplicationCommandOptionType::SubCommand)
.create_sub_option(|so| {
so.name("name").description("Weapon you want infos on.")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
})
.create_option(|option| {
option.name("artifact")
.description("Shows infos on a Genshin Impact artifact set.")
.kind(ApplicationCommandOptionType::SubCommand)
.create_sub_option(|so| {
so.name("artifact")
.description("Artifact Set you want infos on.")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
})
}).await;
if x.is_err() {
println!("{}", x.unwrap_err());
}
ApplicationCommand::create_global_application_command(&_ctx.http, |command| {
command.name("createstatusmessage").create_option(|opt| {
opt.name("channel").description("Channel where to put the status message")
.kind(ApplicationCommandOptionType::Channel)
.required(true)
}).description("Creates a status message of all the current events on Genshin Impact")
}).await.expect("Can't create the createstatusmessage command");
}
async fn interaction_create(&self, _ctx: Context, _interaction: Interaction) {
if let Interaction::ApplicationCommand(command) = _interaction {
match command.data.name.as_str() {
// Ping command
"genshin" => interactions::genshin::genshin_interaction(_ctx, command).await,
"createstatusmessage" => interactions::status_message::create_status_interaction(_ctx, command).await,
// Unknown command
_ => interactions::pong(_ctx, command).await
}
} else if let Interaction::MessageComponent(component) = _interaction {
let mut args = component.data.custom_id.split("_");
let command = args.next();
match command.unwrap() {
"weapon" => {
let weapon = args.collect::<Vec<_>>().join("_");
show_weapon_embed(&_ctx, &component, weapon).await;
}
"artifact" => {
let artifact = args.collect::<Vec<_>>().join("_");
show_artifact_embed(&_ctx, &component, artifact).await;
}
"build" => {
let character = args.collect::<Vec<_>>().join("_");
build_interact(&_ctx, &component, character).await;
}
_ => println!("Unknown interaction")
}
} else {
println!("ERROR");
}
}
}
#[tokio::main]
async fn main() {
dotenv().ok();
let mut token= "".to_string();
for (k, v) in env::vars() {
if k.eq("DISCORD_TOKEN") {
token = v;
break;
}
}
let application_id: u64 = "860553396578811914".parse().expect("Wrong format");
let needed_intents = [
GatewayIntents::GUILDS,
GatewayIntents::GUILD_MESSAGES,
GatewayIntents::GUILD_EMOJIS,
GatewayIntents::GUILD_WEBHOOKS,
GatewayIntents::GUILD_INTEGRATIONS
];
let mut client = Client::builder(token)
.event_handler(Handler)
.intents(GatewayIntents::from_iter(needed_intents.into_iter()))
.application_id(application_id)
.await
.expect("Error creating the client");
if let Err(why) = client.start().await {
println!("Client error {}", why);
}
}

22
src/utils/mod.rs Normal file
View file

@ -0,0 +1,22 @@
use linked_hash_map::LinkedHashMap;
use serenity::builder::{CreateActionRow, CreateButton};
use serenity::model::interactions::message_component::ButtonStyle;
pub mod mongo;
pub fn create_action_row_basic (dict: LinkedHashMap<String, String>, command: &str) -> CreateActionRow {
let mut buttons: Vec<CreateButton> = vec!();
for d in dict {
let mut b = CreateButton::default();
b.custom_id(format!("{}_{}", command, &d.0));
b.label(d.1);
b.style(ButtonStyle::Primary);
buttons.push(b)
}
let mut ar = CreateActionRow::default();
for btn in buttons {
ar.add_button(btn);
}
ar
}

67
src/utils/mongo.rs Normal file
View file

@ -0,0 +1,67 @@
use mongodb::bson::{Bson, doc, Document, from_bson, to_bson};
use mongodb::{Client, Collection, options::ClientOptions};
use futures::stream::{TryStreamExt};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct StatusMessage {
pub(crate) guild_id: i64,
pub(crate) message_id: i64,
pub(crate) channel_id: i64
}
#[allow(dead_code)]
pub async fn add_discord_status_message(status_message: StatusMessage) -> bool {
let client = get_mongo_client().await;
let serialized = to_bson(&status_message).expect("Can't serialize status_message");
let document = serialized.as_document().unwrap();
let collection: Collection<Document> = client.database("drunk_venti").collection("StatusMessages");
let updated = collection.update_one(doc! {"guild_id": status_message.guild_id}, doc! {"$set" : document}, None).await;
let is_ok = &updated.is_ok();
match updated.unwrap() {
e if e.matched_count == 0 => {
let inserted = collection.insert_one(document, None).await;
inserted.is_ok()
}
_ => *is_ok
}
}
#[allow(dead_code)]
pub async fn get_discord_status_message(gid: &u64) -> Option<StatusMessage> {
let client = get_mongo_client().await;
let status_messages: Collection<Document> = client.database("drunk_venti").collection("StatusMessages");
let infos = status_messages.find_one(doc! {"guild_id" : *gid as i64}, None).await.expect("Can't find one");
match infos {
Some(i) => {
let m_infos: StatusMessage = from_bson(Bson::Document(i)).expect("Can't get");
return Some(m_infos);
}
_ => None
}
}
#[allow(dead_code)]
pub async fn get_all_status_messages() -> Vec<StatusMessage> {
let client = get_mongo_client().await;
let collection: Collection<StatusMessage> = client.database("drunk_venti").collection::<StatusMessage>("StatusMessages");
let documents = collection.find(None, None).await.expect("Can't get everything");
let all_docs: Vec<StatusMessage> = documents.try_collect().await.unwrap_or_else(|_| vec![]);
return all_docs;
}
#[allow(dead_code)]
async fn get_mongo_client() -> Client {
let mut client_options = ClientOptions::parse("mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&directConnection=true&ssl=false").await.expect("Can't connect to db");
client_options.app_name = Some("Drunk Venti".to_string());
client_options.default_database = Some("drunk_venti".to_string());
Client::with_options(client_options).expect("Can't add options")
}