Initial commit

This commit is contained in:
2022-03-05 14:35:02 -07:00
commit fd878c3721
8 changed files with 2099 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
use serenity::prelude::Context;
use serenity::model::prelude::{Message, UserId};
use serenity::framework::standard::{CommandResult, Args};
use serenity::framework::StandardFramework;
use serenity::framework::standard::macros::{command, group};
use crate::data::{GlobalData, BotConfig, PlayerData};
use rand::Rng;
use serenity::model::guild::{Member, Guild};
use serenity::model::id::ChannelId;
use serenity::model::Permissions;
use serenity::model::channel::{PermissionOverwrite, PermissionOverwriteType};
use serenity::utils::MessageBuilder;
#[group]
#[commands(start)]
struct Host;
fn generate_codename(config: &BotConfig) -> String {
let mut rng = rand::thread_rng();
let occupation = &config.occupation[rng.gen_range(0..config.occupation.len())];
let adj = &config.adjective[rng.gen_range(0..config.adjective.len())];
format!("{} {}", adj, occupation)
}
async fn add_user_to_game(ctx: &Context, guild: &Guild, global_data: &mut GlobalData, discord_user: &Member) {
let mut codename = generate_codename(&global_data.cfg);
while global_data.game_state.codename_exists(&mut codename) {
codename = generate_codename(&global_data.cfg);
}
let channel = guild.create_channel(&ctx.http, |c| {
c
.category(&ChannelId::from(global_data.cfg.category))
.name(format!("{}'s Channel", discord_user.display_name()))
}).await.unwrap();
let allow = Permissions::SEND_MESSAGES | Permissions::READ_MESSAGE_HISTORY | Permissions::READ_MESSAGE_HISTORY;
let overwrite = PermissionOverwrite {
allow,
deny: Default::default(),
kind: PermissionOverwriteType::Member(discord_user.user.id),
};
channel.create_permission(&ctx.http, &overwrite).await.unwrap();
let msg = channel.send_message(&ctx.http, |m| {
m.content(MessageBuilder::new()
.push("Welcome ")
.mention(discord_user)
.push_line(" to your WOxlf Terminal. You may use this terminal to communicate to other subjects.")
.push_line("You will also use this terminal for choosing one of your fellow subjects for termination.")
.push_line("Happy testing :)")
.push_line("")
.push("SUBJECT CODENAME: ")
.push_line(&codename)
)
}).await.unwrap();
channel.pin(&ctx.http, msg.id).await.unwrap();
let player_data = PlayerData {
discord_id: discord_user.user.id.0,
codename: codename
};
global_data.game_state.player_channels.insert(channel.id.0, player_data);
}
#[command]
#[only_in(guilds)]
#[allowed_roles("wolfx host")]
async fn start(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
msg.channel_id.say(&ctx.http, "Starting game").await?;
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let guild = msg.guild(&ctx.cache).await.unwrap();
let mut global_data = global_data.lock().await;
global_data.game_state.clear();
for player in args.iter::<u64>() {
if let Ok(player) = player {
if let Some(discord_user) = guild.members.get(&UserId::from(player)) {
add_user_to_game(ctx, &guild, &mut global_data, &discord_user).await;
}
else {
msg.reply(&ctx.http,format!("User {} is invalid or not in this server!", player)).await.unwrap();
break;
}
}
}
Ok(())
}
pub fn command_framework() -> StandardFramework {
StandardFramework::new()
.configure(|c| {
c.prefix("!")
})
.group(&HOST_GROUP)
}
+93
View File
@@ -0,0 +1,93 @@
use config::{Config, File};
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::collections::HashMap;
use serenity::prelude::TypeMapKey;
use std::sync::Arc;
use std::path::PathBuf;
use structopt::StructOpt;
use tokio::sync::Mutex;
#[derive(Debug, StructOpt)]
#[structopt(name = "WOXlf", about = "WOXlf discord bot")]
pub struct Args {
pub cfg_path: PathBuf
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BotConfig {
pub token: String,
pub app_id: u64,
pub host_channel: u64,
pub category: u64,
pub occupation: Vec<String>,
pub adjective: Vec<String>
}
impl BotConfig {
pub fn new(config_path: &Path) -> Result<Self, config::ConfigError> {
let cfg = Config::builder()
.add_source(File::from(config_path))
.build()?;
cfg.try_deserialize()
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum Phase {
Day,
Night
}
impl Default for Phase {
fn default() -> Self {
Self::Night
}
}
#[derive(Debug, Deserialize, Serialize, Clone, Default, Hash)]
pub struct PlayerData {
pub discord_id: u64,
pub codename: String,
}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct GameState {
pub player_channels: HashMap<u64, PlayerData>,
pub current_phase: Phase,
}
impl GameState {
pub fn codename_exists(&self, codename: &str) -> bool {
self.player_channels.iter().any(|(_, data)| {
data.codename.to_lowercase() == codename
})
}
pub fn clear(&mut self) {
self.player_channels.clear();
self.current_phase = Phase::Night;
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GlobalData {
pub cfg: BotConfig,
pub game_state: GameState
}
impl GlobalData {
pub fn new(cfg: BotConfig) -> Self {
Self {
cfg,
game_state: GameState::default()
}
}
}
impl TypeMapKey for GlobalData {
type Value = Arc<Mutex<GlobalData>>;
}
+78
View File
@@ -0,0 +1,78 @@
mod data;
mod commands;
use serenity::prelude::*;
use serenity::model::prelude::{Message, Ready};
use std::sync::Arc;
use serenity::client::bridge::gateway::GatewayIntents;
use crate::data::{GlobalData, Args, BotConfig};
use serenity::async_trait;
use crate::commands::command_framework;
use structopt::StructOpt;
use serenity::model::id::ChannelId;
struct Handler {}
#[async_trait]
impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) {
if msg.author.bot {
return;
}
let data = ctx.data.read().await;
let game_data = data.get::<GlobalData>().unwrap();
let game_data = game_data.lock().await;
if let Some(player_data) = game_data.game_state.player_channels.get(&msg.channel_id.0) {
let guild = msg.guild(&ctx.cache).await.unwrap();
let user_msg = format!("{} > {}", player_data.codename, msg.content);
for (channel, _) in &game_data.game_state.player_channels {
if *channel == msg.channel_id.0 {
continue;
}
let channel = guild.channels.get(&ChannelId::from(*channel)).unwrap();
channel.send_message(&ctx.http, |m| {
m
.content(&user_msg)
}).await.unwrap();
}
let host_channel = guild.channels.get(&ChannelId::from(game_data.cfg.host_channel)).unwrap();
host_channel.send_message(&ctx.http, |m| {
m
.content(&user_msg)
}).await.unwrap();
}
}
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
#[tokio::main]
async fn main() {
let args: Args = Args::from_args();
let bot_cfg: BotConfig = BotConfig::new(&args.cfg_path).expect("Unable to parse cfg");
let mut client = Client::builder(&bot_cfg.token)
.event_handler(Handler {})
.framework(command_framework())
.intents(GatewayIntents::all())
.type_map_insert::<GlobalData>(Arc::new(Mutex::new(GlobalData::new(bot_cfg))))
.await
.expect("Err creating client");
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);
}
}