feat: init setup api avec access au minimal au bot discord

This commit is contained in:
Max batleforc 2024-06-18 02:55:12 +02:00
parent 070153be8a
commit 458e239b35
No known key found for this signature in database
GPG Key ID: 25D243AB4B6AC9E7
32 changed files with 270 additions and 797 deletions

View File

@ -28,20 +28,23 @@ rand = "0.8.5"
walkdir = "2.4.0"
surrealdb = "1.1.1"
once_cell = "1.19.0"
poise = "0.6.1"
poise = { version = "0.6.1", features = ["cache"] }
tracing = "0.1"
tracing-actix-web = "0.7.9"
serde_repr = "0.1"
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter","time"] }
tracing-subscriber = { version = "0.3", features = [
"registry",
"env-filter",
"time",
] }
time = "0.3.36"
tracing-bunyan-formatter = "0.3"
opentelemetry = "0.23"
opentelemetry_sdk = { version = "0.23", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.16"}
opentelemetry-otlp = { version = "0.16" }
tracing-opentelemetry = "0.24"
[[bin]]
name = "botdiscord"
path = "src/main.rs"

View File

@ -1,24 +0,0 @@
from_latest_tag = false
ignore_merge_commits = false
disable_changelog = false
disable_bump_commit = false
generate_mono_repository_global_tag = true
branch_whitelist = []
skip_ci = "[skip ci]"
skip_untracked = false
pre_bump_hooks = []
post_bump_hooks = []
pre_package_bump_hooks = []
post_package_bump_hooks = []
[git_hooks]
[commit_types]
[changelog]
path = "CHANGELOG.md"
authors = []
[bump_profiles]
[packages]

26
src/api/apidocs.rs Normal file
View File

@ -0,0 +1,26 @@
use utoipa::OpenApi;
use crate::api::bot::info;
#[derive(OpenApi)]
#[openapi(
info(
title = "BotDiscordApi",
version = "0.1.0",
description = "Api for discord bot",
),
tags(
(name= "Auth", description = "Authentication"),
(name= "User", description = "User operations"),
(name= "Bot", description = "Bot operations")
),
components(
schemas(
info::Info,
)
),
paths(
info::get_info,
)
)]
pub struct ApiDocs;

40
src/api/bot/info.rs Normal file
View File

@ -0,0 +1,40 @@
use std::sync::Arc;
use actix_web::{get, web, HttpResponse, Responder};
use poise::serenity_prelude::Http;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::config::Config;
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Info {
pub name: String,
pub version: String,
pub description: String,
pub available_guids: Vec<String>,
}
#[utoipa::path(
tag= "Bot",
operation_id = "getInfo",
path = "/api/bot/info",
responses(
(status = 200, description = "Information about the bot", body = Info),
(status = 500, description = "Internal server error")
)
)]
#[get("/info")]
#[tracing::instrument(name = "get_info", skip(config, http))]
pub async fn get_info(config: web::Data<Config>, http: web::Data<Arc<Http>>) -> impl Responder {
let list_guilds = http.get_guilds(None, None).await;
match list_guilds {
Ok(guilds) => HttpResponse::Ok().json(Info {
name: "BotDiscord".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
description: config.bot_name.clone(),
available_guids: guilds.iter().map(|g| g.name.clone()).collect(),
}),
Err(e) => HttpResponse::InternalServerError().body(format!("Error getting guilds: {}", e)),
}
}

6
src/api/bot/mod.rs Normal file
View File

@ -0,0 +1,6 @@
use actix_web::Scope;
pub mod info;
pub fn init_bot() -> Scope {
Scope::new("/bot").service(info::get_info)
}

9
src/api/init.rs Normal file
View File

@ -0,0 +1,9 @@
use actix_web::Scope;
use super::{bot, meme};
pub fn init_api() -> Scope {
Scope::new("/api")
.service(meme::init_meme())
.service(bot::init_bot())
}

5
src/api/meme/mod.rs Normal file
View File

@ -0,0 +1,5 @@
use actix_web::Scope;
pub fn init_meme() -> Scope {
Scope::new("/meme")
}

4
src/api/mod.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod apidocs;
pub mod bot;
pub mod init;
pub mod meme;

View File

@ -1,28 +0,0 @@
use serenity::client::Context;
use serenity::framework::standard::macros::help;
use serenity::framework::standard::{
help_commands, Args, CommandGroup, CommandResult, HelpOptions,
};
use serenity::model::prelude::{Message, UserId};
use std::collections::HashSet;
#[help]
#[individual_command_tip = "Hello! Pour plus d'information sur une commande, passe la en argument."]
#[command_not_found_text = "La commande `{}` n'a pas été trouver."]
#[max_levenshtein_distance(3)]
#[wrong_channel = "Strike"]
#[lacking_role = "Hide"]
#[lacking_permissions = "Hide"]
#[strikethrough_commands_tip_in_guild("")]
#[embed_success_colour = "#5F021F"]
async fn help(
context: &Context,
msg: &Message,
args: Args,
help_options: &'static HelpOptions,
groups: &[&'static CommandGroup],
owners: HashSet<UserId>,
) -> CommandResult {
let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await;
Ok(())
}

View File

@ -1,151 +0,0 @@
use rand::Rng;
use serenity::{
all::Message,
builder::{CreateAttachment, CreateEmbed, CreateEmbedFooter, CreateMessage},
client::Context,
framework::standard::{macros::command, Args, CommandResult},
};
use tokio::fs::File;
use crate::{
config::ConfigGlobal,
img::config_file::{ConfigImgGlobal, KeyWordItem},
};
#[command]
#[description = "Answer your message with a meme, the keyword need to be put under quote"]
pub async fn answer(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
if let Ok(keyword) = args.single_quoted::<String>() {
println!("Keyword: {}", keyword);
let (config_img, config) = {
let data_read = ctx.data.read().await;
let config_img = data_read
.get::<ConfigImgGlobal>()
.expect("Config img not found")
.clone();
let config = data_read
.get::<ConfigGlobal>()
.expect("Main config not found")
.clone();
(config_img, config)
};
let folder_container = match config_img
.keyword
.iter()
.find(|keyword_under_search| keyword_under_search.does_value_match(keyword.clone()))
{
Some(keyword_matching) => {
println!("{} match {:?}", keyword, keyword_matching);
let keyword_path = match keyword_matching.path.len() {
0 => keyword_matching.path[0].clone(),
_ => {
let id: usize = {
let mut rng = rand::thread_rng();
rng.gen_range(0..keyword_matching.path.len())
};
keyword_matching.path[id].clone()
}
};
keyword_path.clone()
}
None => {
if let Err(why) = msg
.channel_id
.say(&ctx.http, format!("No match for {}", keyword))
.await
{
println!("Error sending message: {:?}", why)
}
return Ok(());
}
};
let path = format!("{}/{}", config.image.path.clone(), folder_container);
let file_folder = KeyWordItem::output_folder_content(path.clone());
let id_rand: usize = {
let mut rng = rand::thread_rng();
rng.gen_range(0..file_folder.len())
};
let filename = match file_folder.get(id_rand) {
Some(file) => file.file_name().to_str().unwrap(),
None => {
if let Err(why) = msg
.channel_id
.say(
&ctx.http,
format!("Couldn't find file in folder for {}", keyword),
)
.await
{
println!("Error sending message: {:?}", why)
}
return Ok(());
}
};
let file_path = format!("{}/{}", path, filename);
let file = match File::open(file_path).await {
Ok(file) => file,
Err(why) => {
if let Err(why) = msg
.channel_id
.say(
&ctx.http,
format!("Error opening file {} => {:?}", filename, why),
)
.await
{
println!("Error sending message: {:?}", why)
}
return Ok(());
}
};
let attachment = match CreateAttachment::file(&file, filename).await {
Ok(attachment) => attachment,
Err(why) => {
if let Err(why) = msg
.channel_id
.say(&ctx.http, format!("Error creating attachment: {:?}", why))
.await
{
println!("Error sending message: {:?}", why)
}
return Ok(());
}
};
if filename.ends_with(".mp3") || filename.ends_with(".mp4") {
let builder = CreateMessage::new()
.add_file(attachment)
.reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why);
}
return Ok(());
}
let footer = CreateEmbedFooter::new("WeeboBot");
let embed = CreateEmbed::new()
.title(keyword)
.footer(footer)
.image(format!("attachment://{}", filename));
let builder = CreateMessage::new()
.embed(embed)
.add_file(attachment)
.reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why);
}
return Ok(());
}
if let Err(why) = msg
.channel_id
.say(
&ctx.http,
"Please add keyword if you want to use this command. Example: `!meme answer \"keyword\"`",
)
.await
{
println!("Error sending message: {:?}", why)
}
Ok(())
}

View File

@ -1,21 +0,0 @@
use serenity::builder::{CreateEmbed, CreateEmbedFooter, CreateMessage};
use serenity::{
all::Message,
client::Context,
framework::standard::{macros::command, CommandResult},
};
#[command]
#[description = "Info to ask for new meme"]
pub async fn ask(ctx: &Context, msg: &Message) -> CommandResult {
let footer = CreateEmbedFooter::new("WeeboBot");
let embed = CreateEmbed::new()
.title("New meme ?")
.field("Hello, si tu souhaites ajouter un nouveau meme au bot, tu dois demander à batleforc d'en ajouter un nouveau. Tu peux le faire en lui envoyant un message.".to_string(), String::new(), true)
.footer(footer);
let builder = CreateMessage::new().embed(embed);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
Ok(())
}

View File

@ -1,96 +0,0 @@
use serenity::{
all::Message,
builder::{CreateEmbed, CreateEmbedFooter, CreateMessage},
client::Context,
framework::standard::{macros::command, Args, CommandResult},
model::colour,
};
use crate::db::user_image::User;
#[command]
#[description = "Enable/Disable the auto meme answer"]
pub async fn enable(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = match msg.guild_id {
Some(guild) => guild,
None => return Ok(()),
};
let footer = CreateEmbedFooter::new("WeeboBot");
let embed = CreateEmbed::new()
.title("Enable/Disable auto meme answer")
.footer(footer);
let mut user_in_db: User =
match User::find_by_server_id_user_id(&guild.get(), &msg.author.id.get()).await {
Ok(Some(user_in_db)) => user_in_db.clone(),
Ok(None) => {
let user_in_db = User::new(guild.get(), msg.author.id.get(), false).unwrap();
match user_in_db.create().await {
Ok(_) => user_in_db,
Err(e) => {
let embed = embed
.field("Could't create user in db", e.to_string(), false)
.color(colour::Color::RED);
let builder = CreateMessage::new().embed(embed).reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
println!("Error saving user image: {:?}", e);
return Ok(());
}
}
}
Err(e) => {
let embed = embed
.field("Error finding user image", e.to_string(), false)
.color(colour::Color::RED);
let builder = CreateMessage::new().embed(embed).reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
println!("Error finding user image: {:?}", e);
return Ok(());
}
};
if let Ok(new_status) = args.single::<bool>() {
if user_in_db.enable == new_status {
let embed = embed.field(
format!("Auto answer the same : {}", new_status),
String::new(),
false,
);
let builder = CreateMessage::new().embed(embed).reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
return Ok(());
}
user_in_db.enable = new_status;
match user_in_db.update().await {
Ok(_) => {
let embed = embed.field("Auto answer", new_status.to_string(), false);
let builder = CreateMessage::new().embed(embed).reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
}
Err(e) => {
let embed = embed.field("Couldn't update user in db", e.to_string(), false);
let builder = CreateMessage::new().embed(embed).reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
println!("Error saving user image: {:?}", e);
return Ok(());
}
}
return Ok(());
} else {
let embed = embed.field("Auto answer", user_in_db.enable.to_string(), false);
let builder = CreateMessage::new().embed(embed).reference_message(msg);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
}
Ok(())
}

View File

@ -1,15 +0,0 @@
use super::{answer::ANSWER_COMMAND, ask::ASK_COMMAND, enable::ENABLE_COMMAND, list::LIST_COMMAND};
use serenity::all::standard::macros::group;
// Include the meme commands
// - enable/disable the meme answer
// - trigger a one time meme answer (default command)
// - make a request to add a new meme
// - list meme categories
#[group]
#[prefixes("meme", "m")]
#[summary = "Meme related commands"]
#[description = "Commands to handle the meme related stuffs, it allow you to enable/disable the meme answer or to trigger a one time meme answer."]
#[commands(enable, answer, ask, list)]
#[default_command(answer)]
pub struct Meme;

View File

@ -1,44 +0,0 @@
use serenity::builder::{CreateEmbed, CreateEmbedFooter, CreateMessage};
use serenity::{
all::Message,
client::Context,
framework::standard::{macros::command, CommandResult},
};
use crate::img::config_file::ConfigImgGlobal;
#[command]
#[description = "List supported meme keywords"]
pub async fn list(ctx: &Context, msg: &Message) -> CommandResult {
let config_img = {
let data_read = ctx.data.read().await;
data_read
.get::<ConfigImgGlobal>()
.expect("Config img not found")
.clone()
};
let list_value: Vec<(String, String, bool)> = config_img
.keyword
.iter()
.map(|x| (x.value.clone().join(", "), String::new(), true))
.collect();
if list_value.is_empty() {
let builder = CreateMessage::new().content("No meme keyword found");
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
return Ok(());
}
for chunks in list_value.chunks(25) {
let footer = CreateEmbedFooter::new("WeeboBot");
let embed = CreateEmbed::new()
.title("Meme List")
.fields(chunks.to_vec())
.footer(footer);
let builder = CreateMessage::new().embed(embed);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why)
}
}
Ok(())
}

View File

@ -1,5 +0,0 @@
pub mod answer;
pub mod ask;
pub mod enable;
pub mod init;
pub mod list;

View File

@ -1,3 +0,0 @@
pub mod help;
pub mod meme;
pub mod ping;

View File

@ -1,14 +0,0 @@
use serenity::{
all::Message,
client::Context,
framework::standard::{macros::command, CommandResult},
};
#[command]
#[description = "Pong!"]
pub async fn ping(ctx: &Context, msg: &Message) -> CommandResult {
if let Err(why) = msg.channel_id.say(&ctx.http, "Pong from framework!").await {
println!("Error sending message: {:?}", why)
}
Ok(())
}

View File

@ -1,125 +0,0 @@
use rand::Rng;
use serenity::prelude::*;
use serenity::{
all::{Message, Ready},
async_trait,
builder::{CreateAttachment, CreateMessage},
client::Context,
};
use tokio::fs::File;
use crate::{
config::ConfigGlobal,
db::user_image::User,
img::config_file::{ConfigImgGlobal, KeyWordItem},
};
pub struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) {
if msg.author.bot || msg.content.starts_with("!") || msg.content.len() == 0 {
return;
}
let guild = match msg.guild_id {
Some(guild) => guild,
None => return,
};
let user_in_db: User =
match User::find_by_server_id_user_id(&guild.get(), &msg.author.id.get()).await {
Ok(Some(user_in_db)) => user_in_db.clone(),
Ok(None) => {
let user_in_db =
User::new(guild.get(), msg.author.id.get(), false).unwrap();
match user_in_db.create().await {
Ok(_) => user_in_db,
Err(e) => {
println!("Error saving user image: {:?}", e);
return;
}
}
}
Err(e) => {
println!("Error finding user image: {:?}", e);
return;
}
};
if !user_in_db.enable {
println!("User image is not enable");
return;
}
let (config_img, config) = {
let data_read = ctx.data.read().await;
let config_img = data_read
.get::<ConfigImgGlobal>()
.expect("Config img not found")
.clone();
let config = data_read
.get::<ConfigGlobal>()
.expect("Main config not found")
.clone();
(config_img, config)
};
if config_img.keyword.len() == 0 || msg.content.len() > 50 {
return;
}
let folder_container = match config_img
.keyword
.iter()
.find(|keyword| keyword.does_value_match(msg.content.clone()))
{
Some(keyword_matching) => {
println!("{} match {:?}", msg.content, keyword_matching);
let keyword_path = match keyword_matching.path.len() {
0 => keyword_matching.path[0].clone(),
_ => {
let id: usize = {
let mut rng = rand::thread_rng();
rng.gen_range(0..keyword_matching.path.len())
};
keyword_matching.path[id].clone()
}
};
keyword_path.clone()
}
None => return,
};
let path = format!("{}/{}", config.image.path.clone(), folder_container);
let file_folder = KeyWordItem::output_folder_content(path.clone());
let id_rand: usize = {
let mut rng = rand::thread_rng();
rng.gen_range(0..file_folder.len())
};
let filename = match file_folder.get(id_rand) {
Some(file) => file.file_name().to_str().unwrap(),
None => return,
};
let file_path = format!("{}/{}", path, filename);
let file = match File::open(file_path).await {
Ok(file) => file,
Err(why) => {
println!("Error opening file: {:?}", why);
return;
}
};
let attachment = match CreateAttachment::file(&file, filename).await {
Ok(attachment) => attachment,
Err(why) => {
println!("Error creating attachment: {:?}", why);
return;
}
};
let builder = CreateMessage::new().add_file(attachment);
if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await {
println!("Error sending message: {:?}", why);
}
}
async fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}

View File

@ -1,124 +0,0 @@
use super::cmd::meme::init::MEME_GROUP;
use super::cmd::{help::HELP, ping::PING_COMMAND};
use super::handler::Handler;
use crate::config::{Config, ConfigGlobal};
use crate::img::config_file::{ConfigFile, ConfigImgGlobal};
use serenity::framework::standard::Configuration;
use serenity::{
all::GatewayIntents,
framework::{standard::macros::group, StandardFramework},
http::Http,
Client,
};
use std::collections::HashSet;
use std::fs;
use tokio::sync::oneshot;
use tokio::task::spawn_blocking;
#[group]
#[commands(ping)]
struct General;
pub fn start_bot(config: Config, rx: oneshot::Receiver<()>) {
if config.token != "" {
spawn_blocking(move || {
let rt = tokio::runtime::Handle::current();
rt.block_on(async move {
let local = tokio::task::LocalSet::new();
let _ = local
.run_until(async move {
let config_img = match fs::read_to_string(format!(
"{}/config.yaml",
config.image.path
)) {
Ok(content) => content,
Err(err) => {
println!("Error while opening config.yaml : {:?}", err);
return;
}
};
let config_parsed = match ConfigFile::parse_config(config_img) {
Ok(config) => config,
Err(err) => {
println!("Error while parsing config.yaml : {:?}", err);
return;
}
};
let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILD_VOICE_STATES
| GatewayIntents::GUILDS
| GatewayIntents::GUILD_MEMBERS
| GatewayIntents::GUILD_PRESENCES
| GatewayIntents::GUILD_MESSAGE_REACTIONS;
let http = Http::new(&config.token);
let (owners, _bot_id) = match http.get_current_application_info().await {
Ok(info) => {
let mut owners = HashSet::new();
match info.owner {
Some(user) => {
owners.insert(user.id);
println!("{}", user.global_name.unwrap())
}
None => {}
}
(owners, info.id)
}
Err(why) => panic!("Could not access application info: {:?}", why),
};
let framework = StandardFramework::new()
.help(&HELP)
.group(&GENERAL_GROUP)
.group(&MEME_GROUP);
framework.configure(
Configuration::new()
.with_whitespace(true)
.prefix(config.prefix.clone())
.owners(owners),
);
let mut client = Client::builder(&config.token, intents)
.event_handler(Handler)
.framework(framework)
.await
.expect("Err creating client");
{
// Open the data lock in write mode, so keys can be inserted to it.
let mut data = client.data.write().await;
data.insert::<ConfigImgGlobal>(config_parsed.clone());
data.insert::<ConfigGlobal>(config.clone());
}
let shard_manager = client.shard_manager.clone();
let client_start = client.start_autosharded();
tokio::spawn(async move {
match rx.await {
Ok(_) => {
println!("Received shutdown signal");
shard_manager.shutdown_all().await;
println!("Shutting down bot");
}
Err(_) => {
println!("Channel dropped signal");
shard_manager.shutdown_all().await;
println!("Shutting down bot");
}
}
});
if let Err(why) = client_start.await {
println!("Client error: {why:?}");
}
println!("Bot stopped.");
})
.await;
})
});
}
}

View File

@ -1,3 +0,0 @@
pub mod cmd;
pub mod handler;
pub mod init;

View File

@ -1,6 +1,6 @@
use crate::{
botv2::init::{Context, Error},
use crate::botv2::{
domain::meme::answer::{answer_meme, AnswerResult},
init::{Context, Error},
};
use poise::CreateReply;
use tracing::instrument;

View File

@ -1,6 +1,6 @@
use crate::{
botv2::init::{Context, Error},
use crate::botv2::{
domain::meme::change_auto_meme::change_auto_meme,
init::{Context, Error},
};
use poise::CreateReply;
use tracing::instrument;
@ -16,7 +16,11 @@ pub async fn enable(
Some(guild) => guild,
None => return Ok(()),
};
let embed = change_auto_meme(enable, ctx.author().id.get(), guild.get()).await;
let entity_name = ctx.data().entity_name.clone();
let embed: Result<
Vec<poise::serenity_prelude::CreateEmbed>,
crate::botv2::domain::meme::change_auto_meme::MemeChangeAutoMemeError,
> = change_auto_meme(enable, ctx.author().id.get(), guild.get(), entity_name).await;
let mut reply = CreateReply::default();
if let Ok(embed) = embed {
reply.embeds = embed;

View File

@ -16,33 +16,33 @@ pub async fn change_auto_meme(
enable: Option<bool>,
user_id: u64,
guild_id: u64,
bot_entity: String,
) -> Result<Vec<CreateEmbed>, MemeChangeAutoMemeError> {
let footer = CreateEmbedFooter::new("WeeboBot");
let footer = CreateEmbedFooter::new(bot_entity.clone());
let mut embed = CreateEmbed::new()
.title("Enable/Disable auto meme answer")
.footer(footer);
let mut user_in_db: User =
match User::find_by_server_id_user_id(&guild_id, &user_id).await {
Ok(Some(user_in_db)) => user_in_db.clone(),
Ok(None) => {
let user_in_db = User::new(guild_id, user_id, false).unwrap();
match user_in_db.create().await {
Ok(_) => user_in_db,
Err(e) => {
let embed = embed
.field("Could't create user in db", e.to_string(), false)
.color(colour::Color::RED);
return Ok(vec![embed]);
}
let mut user_in_db: User = match User::find_by_server_id_user_id(&guild_id, &user_id).await {
Ok(Some(user_in_db)) => user_in_db.clone(),
Ok(None) => {
let user_in_db = User::new(guild_id, user_id, false).unwrap();
match user_in_db.create().await {
Ok(_) => user_in_db,
Err(e) => {
let embed = embed
.field("Could't create user in db", e.to_string(), false)
.color(colour::Color::RED);
return Ok(vec![embed]);
}
}
Err(e) => {
let embed = embed
.field("Error finding user image", e.to_string(), false)
.color(colour::Color::RED);
return Ok(vec![embed]);
}
};
}
Err(e) => {
let embed = embed
.field("Error finding user image", e.to_string(), false)
.color(colour::Color::RED);
return Ok(vec![embed]);
}
};
if let Some(new_status) = enable {
if user_in_db.enable == new_status {
@ -66,4 +66,4 @@ pub async fn change_auto_meme(
embed = embed.field("Auto answer", user_in_db.enable.to_string(), false);
}
return Ok(vec![embed]);
}
}

View File

@ -4,6 +4,7 @@ use poise::serenity_prelude as serenity;
use rand::Rng;
use serenity::all::{CreateAttachment, CreateMessage};
use tokio::fs::File;
use tracing::info;
pub async fn event_handler(
ctx: &serenity::Context,
@ -13,7 +14,7 @@ pub async fn event_handler(
) -> Result<(), Error> {
match event {
serenity::FullEvent::Ready { data_about_bot, .. } => {
println!("{} is connected !", data_about_bot.user.name);
info!("{} is connected !", data_about_bot.user.name);
}
serenity::FullEvent::Message { new_message } => {
if new_message.author.bot

View File

@ -2,12 +2,13 @@ use crate::botv2::cmd::meme::{answer::answer, enable::enable, list::list};
use crate::botv2::cmd::{help::help, ping::ping};
use crate::config::Config;
use crate::{botv2::handler::event_handler, img::config_file::ConfigFile};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{self as serenity, Http};
use serenity::GatewayIntents;
use std::fs;
use std::sync::Arc;
use tokio::sync::oneshot;
use tokio::task::spawn_blocking;
use tracing::instrument;
use tracing::{info, instrument};
pub struct Data {
pub config_img: ConfigFile,
pub config: Config,
@ -30,96 +31,84 @@ async fn age(
Ok(())
}
pub fn start_bot(config: Config, rx: oneshot::Receiver<()>) {
if config.token != "" {
spawn_blocking(move || {
let rt = tokio::runtime::Handle::current();
rt.block_on(async move {
let local = tokio::task::LocalSet::new();
let _ = local
.run_until(async move {
let config_img = match fs::read_to_string(format!(
"{}/config.yaml",
config.image.path
)) {
Ok(content) => content,
Err(err) => {
println!("Error while opening config.yaml : {:?}", err);
return;
}
};
let config_parsed = match ConfigFile::parse_config(config_img) {
Ok(config) => config,
Err(err) => {
println!("Error while parsing config.yaml : {:?}", err);
return;
}
};
let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILD_VOICE_STATES
| GatewayIntents::GUILDS
| GatewayIntents::GUILD_MEMBERS
| GatewayIntents::GUILD_PRESENCES
| GatewayIntents::GUILD_MESSAGE_REACTIONS;
let token = config.token.clone();
let prefix = config.prefix.clone();
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![age(), ping(), help(), list(), enable(), answer()],
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some(prefix.into()),
..Default::default()
},
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
},
..Default::default()
})
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(
ctx,
&framework.options().commands,
)
.await?;
Ok(Data {
config_img: config_parsed,
config: config.clone(),
entity_name: config.bot_name.clone(),
})
})
})
.build();
let mut client = serenity::ClientBuilder::new(token, intents)
.framework(framework)
.await
.expect("Error creating client");
let shard_manager = client.shard_manager.clone();
let client_start = client.start_autosharded();
tokio::spawn(async move {
match rx.await {
Ok(_) => {
println!("Received shutdown signal");
shard_manager.shutdown_all().await;
println!("Shutting down bot");
}
Err(_) => {
println!("Channel dropped signal");
shard_manager.shutdown_all().await;
println!("Shutting down bot");
}
}
});
println!("Bot is running...");
if let Err(why) = client_start.await {
println!("Client error: {why:?}");
}
println!("Bot is stopped...");
})
.await;
pub async fn start_bot(config: Config, rx: oneshot::Receiver<()>) -> Arc<Http> {
let config_img = match fs::read_to_string(format!("{}/config.yaml", config.image.path)) {
Ok(content) => content,
Err(err) => {
tracing::error!("Error while opening config.yaml : {:?}", err);
panic!("Error while opening config.yaml : {:?}", err)
}
};
let config_parsed = match ConfigFile::parse_config(config_img) {
Ok(config) => config,
Err(err) => {
tracing::error!("Error while parsing config.yaml : {:?}", err);
panic!("Error while parsing config.yaml : {:?}", err)
}
};
let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILD_VOICE_STATES
| GatewayIntents::GUILDS
| GatewayIntents::GUILD_MEMBERS
| GatewayIntents::GUILD_PRESENCES
| GatewayIntents::GUILD_MESSAGE_REACTIONS;
let token = config.token.clone();
let prefix = config.prefix.clone();
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![age(), ping(), help(), list(), enable(), answer()],
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some(prefix.into()),
..Default::default()
},
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
},
..Default::default()
})
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Data {
config_img: config_parsed,
config: config.clone(),
entity_name: format!(
"{}-{}",
config.bot_name.clone(),
env!("CARGO_PKG_VERSION").to_string()
),
})
})
});
}
})
.build();
let mut client = serenity::ClientBuilder::new(token, intents)
.framework(framework)
.await
.expect("Error creating client");
let http = client.http.clone();
let shard_manager = client.shard_manager.clone();
tokio::spawn(async move {
match rx.await {
Ok(_) => {
tracing::info!("Received shutdown signal");
shard_manager.shutdown_all().await;
tracing::info!("Shutting down bot");
}
Err(_) => {
tracing::info!("Channel dropped signal");
shard_manager.shutdown_all().await;
tracing::info!("Shutting down bot");
}
}
});
tokio::spawn(async move {
info!("Bot is running...");
if let Err(why) = client.start_autosharded().await {
tracing::error!("Client error: {why:?}");
}
info!("Bot is stopped...");
});
http
}

View File

@ -1,3 +1,4 @@
pub mod init;
pub mod cmd;
pub mod domain;
pub mod handler;
pub mod cmd;
pub mod init;

View File

@ -1,10 +1,11 @@
use crate::tracing::tracing_kind::Tracing;
use dotenvy::dotenv;
use serde::Deserialize;
use poise::serenity_prelude::prelude::TypeMapKey;
use serde::Deserialize;
use std::env;
use std::fs::read_to_string;
use std::path::PathBuf;
use crate::tracing::tracing_kind::Tracing;
use tracing::{info, trace};
const DB_URL: &str = "DB_URL";
const DB_USER: &str = "DB_USER";
@ -60,10 +61,10 @@ pub struct PersistenceConfig {
pub fn parse_local_config() -> Config {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
println!(env!("CARGO_MANIFEST_DIR"));
trace!(env!("CARGO_MANIFEST_DIR"));
d.push("resources/config.toml");
match dotenv() {
Ok(_) => println!("Loaded .env file"),
Ok(_) => info!("Loaded .env file"),
Err(err) => println!("No .env file found: {:?}", err),
}
parse_config(d)

View File

@ -1,6 +1,6 @@
pub mod api;
pub mod botv2;
pub mod config;
pub mod db;
pub mod img;
pub mod botv2;
pub mod domain;
pub mod tracing;
pub mod tracing;

View File

@ -1,11 +1,19 @@
extern crate botdiscord;
use std::{process, time::Duration};
use ::tracing::info;
use actix_cors::Cors;
use actix_web::{App, HttpServer};
use botdiscord::{botv2::init::start_bot, tracing};
use actix_web::dev::Service;
use actix_web::http::header;
use actix_web::{web, App, HttpServer};
use botdiscord::api::apidocs::ApiDocs;
use botdiscord::api::init::init_api;
use botdiscord::config::parse_local_config;
use tokio::{sync::oneshot, time::sleep};
use botdiscord::db;
use botdiscord::{botv2::init::start_bot, tracing};
use std::{process, time::Duration};
use tokio::{sync::oneshot, time::sleep};
use tracing_actix_web::{RequestId, TracingLogger};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
#[tokio::main] // or #[actix_web::main]
async fn main() -> std::io::Result<()> {
@ -20,21 +28,50 @@ async fn main() -> std::io::Result<()> {
}
}
let (tx, rx) = oneshot::channel();
start_bot(config.clone(), rx);
HttpServer::new(|| {
let http = start_bot(config.clone(), rx).await;
info!("API Server started on port {}", port);
let mut openapi = ApiDocs::openapi();
openapi.info.title = format!("{} Api", config.bot_name.clone());
openapi.info.version = env!("CARGO_PKG_VERSION").to_string();
let api_config = config.clone();
HttpServer::new(move || {
let cors = Cors::default()
.allow_any_header()
.allow_any_method()
.allow_any_origin();
App::new().wrap(cors)
let swagger_ui =
SwaggerUi::new("/api/docs/{_:.*}").url("/api/docs/docs.json", openapi.clone());
App::new()
.app_data(web::Data::new(api_config.clone()))
.app_data(web::Data::new(http.clone()))
.wrap(cors)
.service(swagger_ui)
.service(
init_api()
.wrap_fn(|mut req, srv| {
let request_id_asc = req.extract::<RequestId>();
let fut = srv.call(req);
async move {
let mut res = fut.await?;
let request_id: RequestId = request_id_asc.await.unwrap();
let request_id_str = format!("{}", request_id);
let headers = res.headers_mut();
headers.insert(
header::HeaderName::from_static("x-request-id"),
header::HeaderValue::from_str(request_id_str.as_str()).unwrap(),
);
Ok(res)
}
})
.wrap(TracingLogger::default()),
)
})
.bind(("0.0.0.0", port))?
.run()
.await?;
println!("API Server stopped.");
info!("API Server stopped.");
tx.send(()).unwrap();
tracing::init::stop_tracing(config.tracing.clone(), config.bot_name.clone());
println!("Signal sent to bot.");
sleep(Duration::from_secs(2)).await;
process::exit(1); // This is a workaround to stop the bot, it should be replaced by a better solution
}