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" walkdir = "2.4.0"
surrealdb = "1.1.1" surrealdb = "1.1.1"
once_cell = "1.19.0" once_cell = "1.19.0"
poise = "0.6.1" poise = { version = "0.6.1", features = ["cache"] }
tracing = "0.1" tracing = "0.1"
tracing-actix-web = "0.7.9" tracing-actix-web = "0.7.9"
serde_repr = "0.1" 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" time = "0.3.36"
tracing-bunyan-formatter = "0.3" tracing-bunyan-formatter = "0.3"
opentelemetry = "0.23" opentelemetry = "0.23"
opentelemetry_sdk = { version = "0.23", features = ["rt-tokio"] } opentelemetry_sdk = { version = "0.23", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.16"} opentelemetry-otlp = { version = "0.16" }
tracing-opentelemetry = "0.24" tracing-opentelemetry = "0.24"
[[bin]] [[bin]]
name = "botdiscord" name = "botdiscord"
path = "src/main.rs" 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::{ use crate::botv2::{
botv2::init::{Context, Error},
domain::meme::answer::{answer_meme, AnswerResult}, domain::meme::answer::{answer_meme, AnswerResult},
init::{Context, Error},
}; };
use poise::CreateReply; use poise::CreateReply;
use tracing::instrument; use tracing::instrument;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::botv2::{
botv2::init::{Context, Error},
domain::meme::change_auto_meme::change_auto_meme, domain::meme::change_auto_meme::change_auto_meme,
init::{Context, Error},
}; };
use poise::CreateReply; use poise::CreateReply;
use tracing::instrument; use tracing::instrument;
@ -16,7 +16,11 @@ pub async fn enable(
Some(guild) => guild, Some(guild) => guild,
None => return Ok(()), 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(); let mut reply = CreateReply::default();
if let Ok(embed) = embed { if let Ok(embed) = embed {
reply.embeds = embed; reply.embeds = embed;

View File

@ -16,13 +16,13 @@ pub async fn change_auto_meme(
enable: Option<bool>, enable: Option<bool>,
user_id: u64, user_id: u64,
guild_id: u64, guild_id: u64,
bot_entity: String,
) -> Result<Vec<CreateEmbed>, MemeChangeAutoMemeError> { ) -> Result<Vec<CreateEmbed>, MemeChangeAutoMemeError> {
let footer = CreateEmbedFooter::new("WeeboBot"); let footer = CreateEmbedFooter::new(bot_entity.clone());
let mut embed = CreateEmbed::new() let mut embed = CreateEmbed::new()
.title("Enable/Disable auto meme answer") .title("Enable/Disable auto meme answer")
.footer(footer); .footer(footer);
let mut user_in_db: User = let mut user_in_db: User = match User::find_by_server_id_user_id(&guild_id, &user_id).await {
match User::find_by_server_id_user_id(&guild_id, &user_id).await {
Ok(Some(user_in_db)) => user_in_db.clone(), Ok(Some(user_in_db)) => user_in_db.clone(),
Ok(None) => { Ok(None) => {
let user_in_db = User::new(guild_id, user_id, false).unwrap(); let user_in_db = User::new(guild_id, user_id, false).unwrap();

View File

@ -4,6 +4,7 @@ use poise::serenity_prelude as serenity;
use rand::Rng; use rand::Rng;
use serenity::all::{CreateAttachment, CreateMessage}; use serenity::all::{CreateAttachment, CreateMessage};
use tokio::fs::File; use tokio::fs::File;
use tracing::info;
pub async fn event_handler( pub async fn event_handler(
ctx: &serenity::Context, ctx: &serenity::Context,
@ -13,7 +14,7 @@ pub async fn event_handler(
) -> Result<(), Error> { ) -> Result<(), Error> {
match event { match event {
serenity::FullEvent::Ready { data_about_bot, .. } => { 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 } => { serenity::FullEvent::Message { new_message } => {
if new_message.author.bot 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::botv2::cmd::{help::help, ping::ping};
use crate::config::Config; use crate::config::Config;
use crate::{botv2::handler::event_handler, img::config_file::ConfigFile}; 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 serenity::GatewayIntents;
use std::fs; use std::fs;
use std::sync::Arc;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use tokio::task::spawn_blocking; use tracing::{info, instrument};
use tracing::instrument;
pub struct Data { pub struct Data {
pub config_img: ConfigFile, pub config_img: ConfigFile,
pub config: Config, pub config: Config,
@ -30,29 +31,19 @@ async fn age(
Ok(()) Ok(())
} }
pub fn start_bot(config: Config, rx: oneshot::Receiver<()>) { pub async fn start_bot(config: Config, rx: oneshot::Receiver<()>) -> Arc<Http> {
if config.token != "" { let config_img = match fs::read_to_string(format!("{}/config.yaml", config.image.path)) {
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, Ok(content) => content,
Err(err) => { Err(err) => {
println!("Error while opening config.yaml : {:?}", err); tracing::error!("Error while opening config.yaml : {:?}", err);
return; panic!("Error while opening config.yaml : {:?}", err)
} }
}; };
let config_parsed = match ConfigFile::parse_config(config_img) { let config_parsed = match ConfigFile::parse_config(config_img) {
Ok(config) => config, Ok(config) => config,
Err(err) => { Err(err) => {
println!("Error while parsing config.yaml : {:?}", err); tracing::error!("Error while parsing config.yaml : {:?}", err);
return; panic!("Error while parsing config.yaml : {:?}", err)
} }
}; };
let intents = GatewayIntents::GUILD_MESSAGES let intents = GatewayIntents::GUILD_MESSAGES
@ -79,15 +70,15 @@ pub fn start_bot(config: Config, rx: oneshot::Receiver<()>) {
}) })
.setup(|ctx, _ready, framework| { .setup(|ctx, _ready, framework| {
Box::pin(async move { Box::pin(async move {
poise::builtins::register_globally( poise::builtins::register_globally(ctx, &framework.options().commands).await?;
ctx,
&framework.options().commands,
)
.await?;
Ok(Data { Ok(Data {
config_img: config_parsed, config_img: config_parsed,
config: config.clone(), config: config.clone(),
entity_name: config.bot_name.clone(), entity_name: format!(
"{}-{}",
config.bot_name.clone(),
env!("CARGO_PKG_VERSION").to_string()
),
}) })
}) })
}) })
@ -96,30 +87,28 @@ pub fn start_bot(config: Config, rx: oneshot::Receiver<()>) {
.framework(framework) .framework(framework)
.await .await
.expect("Error creating client"); .expect("Error creating client");
let http = client.http.clone();
let shard_manager = client.shard_manager.clone(); let shard_manager = client.shard_manager.clone();
let client_start = client.start_autosharded();
tokio::spawn(async move { tokio::spawn(async move {
match rx.await { match rx.await {
Ok(_) => { Ok(_) => {
println!("Received shutdown signal"); tracing::info!("Received shutdown signal");
shard_manager.shutdown_all().await; shard_manager.shutdown_all().await;
println!("Shutting down bot"); tracing::info!("Shutting down bot");
} }
Err(_) => { Err(_) => {
println!("Channel dropped signal"); tracing::info!("Channel dropped signal");
shard_manager.shutdown_all().await; shard_manager.shutdown_all().await;
println!("Shutting down bot"); tracing::info!("Shutting down bot");
} }
} }
}); });
println!("Bot is running..."); tokio::spawn(async move {
if let Err(why) = client_start.await { info!("Bot is running...");
println!("Client error: {why:?}"); if let Err(why) = client.start_autosharded().await {
tracing::error!("Client error: {why:?}");
} }
println!("Bot is stopped..."); info!("Bot is stopped...");
})
.await;
})
}); });
} http
} }

View File

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

View File

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

View File

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

View File

@ -1,11 +1,19 @@
extern crate botdiscord; extern crate botdiscord;
use std::{process, time::Duration}; use ::tracing::info;
use actix_cors::Cors; use actix_cors::Cors;
use actix_web::{App, HttpServer}; use actix_web::dev::Service;
use botdiscord::{botv2::init::start_bot, tracing}; 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 botdiscord::config::parse_local_config;
use tokio::{sync::oneshot, time::sleep};
use botdiscord::db; 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] #[tokio::main] // or #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
@ -20,21 +28,50 @@ async fn main() -> std::io::Result<()> {
} }
} }
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
start_bot(config.clone(), rx); let http = start_bot(config.clone(), rx).await;
HttpServer::new(|| { 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() let cors = Cors::default()
.allow_any_header() .allow_any_header()
.allow_any_method() .allow_any_method()
.allow_any_origin(); .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))? .bind(("0.0.0.0", port))?
.run() .run()
.await?; .await?;
println!("API Server stopped."); info!("API Server stopped.");
tx.send(()).unwrap(); tx.send(()).unwrap();
tracing::init::stop_tracing(config.tracing.clone(), config.bot_name.clone()); tracing::init::stop_tracing(config.tracing.clone(), config.bot_name.clone());
println!("Signal sent to bot.");
sleep(Duration::from_secs(2)).await; 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 process::exit(1); // This is a workaround to stop the bot, it should be replaced by a better solution
} }