From 5d05c220ece69966b7a68fc6e8742e31d473f9a7 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 21 Feb 2022 23:21:57 +0100 Subject: [PATCH] Some changes and add links command, for now populated with eurosport. --- command_permissions.py | 70 +++++ src/commands/links/eurosport.rs | 496 ++++++++++++++++++++++++++++++++ src/commands/links/mod.rs | 46 +++ src/commands/mod.rs | 44 ++- src/commands/utils.rs | 23 +- src/main.rs | 147 +++++----- 6 files changed, 750 insertions(+), 76 deletions(-) create mode 100644 command_permissions.py create mode 100644 src/commands/links/eurosport.rs create mode 100644 src/commands/links/mod.rs diff --git a/command_permissions.py b/command_permissions.py new file mode 100644 index 0000000..76b05f4 --- /dev/null +++ b/command_permissions.py @@ -0,0 +1,70 @@ +import requests +import toml +import json +import sys + +CONFIG_FILE = "data/config/01-discord.toml" +CONFIG_FILE2 = "data/config/30-high-tier.toml" + + +data1 = toml.load(CONFIG_FILE2) +ROLE_ID = data1.get("high_tier", {}).get("role", 294950710737502209) +GUILD = data1.get("high_tier", {}).get("guild", 117992911781494787) +HT_COMS = data1.get("high_tier", {}).get("coms", ["links"]) + +data = toml.load(CONFIG_FILE) +app_id = data["discord"].get("app_id", 936990391847223316) + +print("Using token: ", data["discord"]["token"]) +token = "Bot " + data["discord"]["token"] +print(token) + + +Headers = { + "Authorization": token, + "User=Agent": "DiscordBot https://github.com/ 1 manual browser requests", +} + +r = requests.get(f"https://discord.com/api/v8/applications/{app_id}/guilds/{GUILD}/commands", headers=Headers) + +print(json.dumps(r.json(), indent=2)) +commands = r.json() +com_id = None + + +json = { + "permissions": [ + { + "id": ROLE_ID, + "type": 1, + "permission": True + } + ] +} + +count = 0 +for command in commands: + if command["name"] in HT_COMS: + id = command["id"] + url = f"https://discord.com/api/v8/applications/{app_id}/guilds/{GUILD}/commands/{str(id)}" + r = requests.patch(url, headers=Headers, json={"default_permission": False}) + print(r.json()) + r = requests.put(url+"/permissions", headers=Headers, json=json) + print(r.json()) + count += 1 + +print("Updated", count, "commands") + +# print("Which id to set") +# id = input() + +# url = f"https://discord.com/api/v8/applications/{app_id}/guilds/{GUILD}/commands/{str(id)}" + + + +# r = requests.patch(url, headers=Headers, json={"default_permission": False}) +# print(r.json()) + +# r = requests.put(url+"/permissions", headers=Headers, json=json) + +# print(r.json()) \ No newline at end of file diff --git a/src/commands/links/eurosport.rs b/src/commands/links/eurosport.rs new file mode 100644 index 0000000..49d2590 --- /dev/null +++ b/src/commands/links/eurosport.rs @@ -0,0 +1,496 @@ +use crate::{commands::utils, Context, Error}; +use std::collections::HashMap; + +use cached::proc_macro::cached; +use chrono::{DateTime, Utc}; +use log::*; +use reqwest::header::{HeaderName, ACCEPT_LANGUAGE, COOKIE}; +use serde::Deserialize; + +use super::Timeframe; + +mod es_date_time { + use chrono::{DateTime, TimeZone, Utc}; + use serde::{self, Deserialize, Deserializer}; + + const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%SZ"; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Utc.datetime_from_str(&s, FORMAT) + .map_err(serde::de::Error::custom) + } +} + +#[derive(Deserialize, Debug)] +struct VideoAttributes { + // #[serde(rename = "earliestPlayableStart")] + // #[serde(with = "es_date_time")] + // earliest_playable_start: DateTime, + name: String, + #[serde(rename = "secondaryTitle")] + #[serde(default)] + secondary: String, + #[serde(rename = "publishEnd")] + #[serde(with = "es_date_time")] + publish_end: DateTime, + #[serde(rename = "publishStart")] + #[serde(with = "es_date_time")] + publish_start: DateTime, + #[serde(default)] + description: String, + // #[serde(rename = "drmEnabled")] + // drm_enabled: bool, +} + +#[derive(Deserialize, Debug)] +struct SportVal { + id: String, +} + +#[derive(Deserialize, Debug)] +struct SportID { + data: Vec, +} + +#[derive(Deserialize, Debug)] +struct VideoRelations { + #[serde(alias = "txSports")] + #[serde(alias = "txOlympicssport")] + sport_id: Option, +} + +#[derive(Deserialize, Debug)] +struct TaxonomyNodeAttributes { + name: String, + // kind: String, +} + +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +enum Includes { + #[serde(rename = "video")] + Video { + attributes: VideoAttributes, + id: String, + relationships: VideoRelations, + }, + #[serde(rename = "taxonomyNode")] + TaxonomyNode { + attributes: TaxonomyNodeAttributes, + id: String, + }, + #[serde(rename = "channel")] + Channels, + #[serde(rename = "collection")] + Collection, + #[serde(rename = "collectionItem")] + CollectionItem, + #[serde(rename = "image")] + Image, + #[serde(rename = "link")] + Link, + #[serde(rename = "package")] + Package, + #[serde(rename = "page")] + Page, + #[serde(rename = "pageItem")] + PageItem, + #[serde(rename = "route")] + Route, + #[serde(rename = "show")] + Show, +} + +#[derive(Deserialize, Debug)] +struct Eurosport { + included: Vec, +} + +#[derive(Debug, Clone)] +struct ESEvents { + id: String, + sport: Option, + name: String, + secondary: String, + description: String, + start: DateTime, + end: DateTime, + // early_start: DateTime, + // drm_enabled: bool, +} + +impl ESEvents { + fn filter(&self, filter: &str) -> bool { + if self.name.to_lowercase().contains(filter) { + return true; + }; + if self.description.to_lowercase().contains(filter) { + return true; + }; + if self.secondary.to_lowercase().contains(filter) { + return true; + }; + if let Some(sport) = &self.sport { + if sport.to_lowercase().contains(filter) { + return true; + } + } + return false; + } + + fn comp(&self, when: &Option) -> bool { + let now = Utc::now(); + match when { + Some(Timeframe::Everything) => true, + Some(Timeframe::Current) => self.start <= now && self.end >= now, + Some(Timeframe::Future) => self.end >= now, + Some(Timeframe::Past) => self.end <= now, + _ => self.end >= now, + } + } +} + +fn get_events(v: Eurosport) -> Result, serde_json::Error> { + let mut sports: HashMap = HashMap::new(); + + let nodes = v.included.iter().filter(|x| match x { + Includes::TaxonomyNode { .. } => true, + _ => false, + }); + for node in nodes { + if let Includes::TaxonomyNode { attributes, id } = node { + sports.insert(id.to_string(), attributes.name.to_string()); + } + } + debug!("Sports: {:?}", sports); + + let mut events: Vec = vec![]; + + let nodes = v.included.iter().filter(|x| match x { + Includes::Video { .. } => true, + _ => false, + }); + for node in nodes { + if let Includes::Video { + attributes, + id, + relationships, + } = node + { + // sports.insert(id.to_string(), attributes.name.to_string()); + + let sport_name = match &relationships.sport_id { + None => None, + Some(sportid) => match sportid.data.get(0) { + None => None, + Some(sv) => match sports.get(&sv.id) { + None => None, + Some(s) => Some(s.to_owned()), + }, + }, + }; + events.push(ESEvents { + id: id.to_string(), + sport: sport_name, + name: attributes.name.to_string(), + secondary: attributes.secondary.to_string(), + description: attributes.description.to_string(), + start: attributes.publish_start, + end: attributes.publish_end, + // early_start: attributes.earliest_playable_start, + // drm_enabled: attributes.drm_enabled, + }); + } + } + events.sort_by(|a, b| b.start.cmp(&a.start)); + debug!("Events: {:?}", events); + return Ok(events); +} + +fn stringify_events(events: Vec) -> Vec { + let mut result = vec![]; + for event in events { + result.push(match event.sport { + // None => format!("```md\n({}) {}```\n", event.name, event.secondary), + None => format!( + "```md\n({}) {}```(-) {}\n https://tom.al/ms/euro/{} https://tom.al/ms/euro/{}", + event.name, + event.secondary, + event.start.timestamp(), + event.end.timestamp(), + event.description, + event.id, + event.id + ), + Some(sport) => format!( + "```md\n[{}]({}) {}```(-) {}\n https://tom.al/ms/euro/{} https://tom.al/ms/euro/{}", + sport, + event.name, + event.secondary, + event.start.timestamp(), + event.end.timestamp(), + event.description, + event.id, + event.id + ), + }); + } + + return result; +} + +#[cached(time = 3600)] +#[allow(dead_code)] +async fn get_eurosport_events(url: String) -> Option> { + let cookie = super::super::super::SETTINGS + .read() + .unwrap() + .get_table("eurosport") + .expect("Expecting an eurosport section in the config") + .get("cookie") + .expect("Config error, please set the eurosport[cookie] value") + .clone() + .into_str() + .expect("Config error, please make sure eurosport[cookie] is a string"); + let client = reqwest::Client::new(); + let x_disco_client = HeaderName::from_lowercase(b"x-disco-client").unwrap(); + let req = client + .get(url) + .header(COOKIE, cookie) + .header(ACCEPT_LANGUAGE, "en-US,en;q=0.5") + .header(x_disco_client, "WEB:UNKNOWN:esplayer:prod") + .send() + .await; + + let result: Option> = match req { + Err(e) => { + warn!("Error getting Eurosport schedule {}", e); + None + } + Ok(req) if req.status().as_u16() == 404 => { + warn!("404 on getting ES events"); + return None; + } + Ok(req) if req.status().as_u16() == 200 => { + let data = req.json::().await; + match data { + Err(e) => { + warn!("Error getting Eurosport schedule {}", e); + None + } + Ok(es) => { + debug!("Eurosport data: {:?}", es); + match get_events(es) { + Err(e) => { + warn!("Error getting Eurosport schedule {}", e); + None + } + Ok(events) => { + // events.sort_by(|a: ESEvents, b: ESEvents| b.start.cmp(a)); + Some(events) + } + } + } + } + } + _ => None, + }; + return result; +} + +// #[derive(Debug, poise::SlashChoiceParameter)] +// pub enum Timeframe { +// #[name = "Currently happening"] +// Current, +// #[name = "Currently happening or starting in the future"] +// Future, +// #[name = "Currently happening or already ended"] +// Past, +// #[name = "Everything"] +// Everything, +// } + +// /// Eurosport player events +// #[poise::command(slash_command)] +// pub async fn eurosport( +// ctx: Context<'_>, +// #[description = "Filter sessions for when they are/were happening, defaults to future"] +// timeframe: Option, +// #[description = "Content to filter on"] filter: Option, +// ) -> Result<(), Error> { +// if !utils::high_tier(ctx.channel_id()) { +// ctx.say("This command can only be used in high tier channels for security") +// .await?; +// return Ok(()); +// } +// let url = super::super::super::SETTINGS +// .read() +// .unwrap() +// .get_table("eurosport") +// .expect("Expecting an eurosport section in the config") +// .get("url") +// .expect("Config error, please set the eurosport[url] value") +// .clone() +// .into_str() +// .expect("Config error, please make sure eurosport[url] is a string"); +// let events = get_eurosport_events(url).await; +// match events { +// None => { +// ctx.say("Oh no something went wrong").await?; +// } +// Some(evs) => { +// info!("Found {} events from eurosport", evs.len()); +// let strings = stringify_events( +// evs.into_iter() +// .filter(|e| e.comp(&timeframe)) +// .filter(|e| match &filter { +// None => true, +// Some(f) => e.filter(f.as_str()), +// }) +// .collect(), +// ); +// let pages = utils::paginator(strings, 1900, "\n".to_string()); + +// utils::paginate_string(ctx, pages).await?; +// } +// } +// Ok(()) +// } + +/// Eurosport player events +pub async fn proc_eurosport( + ctx: Context<'_>, + timeframe: Option, + filter: Option, +) -> Result<(), Error> { + let url = super::super::super::SETTINGS + .read() + .unwrap() + .get_table("eurosport") + .expect("Expecting an eurosport section in the config") + .get("url") + .expect("Config error, please set the eurosport[url] value") + .clone() + .into_str() + .expect("Config error, please make sure eurosport[url] is a string"); + let events = get_eurosport_events(url).await; + match events { + None => { + ctx.say("Oh no something went wrong").await?; + } + Some(evs) => { + info!("Found {} events from eurosport", evs.len()); + let strings = stringify_events( + evs.into_iter() + .filter(|e| e.comp(&timeframe)) + .filter(|e| match &filter { + None => true, + Some(f) => e.filter(f.as_str()), + }) + .collect(), + ); + let pages = utils::paginator(strings, 1900, "\n".to_string()); + + utils::paginate_string(ctx, pages).await?; + } + } + Ok(()) +} + +// /// Eurosport olympics events +// #[poise::command(slash_command)] +// pub async fn olympics( +// ctx: Context<'_>, +// #[description = "Filter sessions for when they are/were happening, defaults to future"] +// timeframe: Option, +// #[description = "Content to filter on"] filter: Option, +// ) -> Result<(), Error> { +// // if !utils::high_tier(ctx.channel_id()) { +// // ctx.say("This command can only be used in high tier channels for security") +// // .await?; +// // return Ok(()); +// // } +// if !utils::high_tier_mess(ctx).await { +// ctx.say("This command can only be used in high tier channels for security") +// .await?; +// return Ok(()); +// } +// let url = super::super::super::SETTINGS +// .read() +// .unwrap() +// .get_table("eurosport") +// .expect("Expecting an eurosport section in the config") +// .get("olympics") +// .expect("Config error, please set the eurosport[olympics] value") +// .clone() +// .into_str() +// .expect("Config error, please make sure eurosport[olympics] is a string"); +// let events = get_eurosport_events(url).await; +// match events { +// None => { +// ctx.say("Oh no something went wrong").await?; +// } +// Some(evs) => { +// info!("Found {} events from eurosport olympics ", evs.len()); +// let strings = stringify_events( +// evs.into_iter() +// .filter(|e| e.comp(&timeframe)) +// .filter(|e| match &filter { +// None => true, +// Some(f) => e.filter(f.as_str()), +// }) +// .collect(), +// ); +// let pages = utils::paginator(strings, 1900, "\n".to_string()); + +// utils::paginate_string(ctx, pages).await?; +// } +// } + +// Ok(()) +// } + +/// Eurosport olympics events +pub async fn proc_olympics( + ctx: Context<'_>, + timeframe: Option, + filter: Option, +) -> Result<(), Error> { + let url = super::super::super::SETTINGS + .read() + .unwrap() + .get_table("eurosport") + .expect("Expecting an eurosport section in the config") + .get("olympics") + .expect("Config error, please set the eurosport[olympics] value") + .clone() + .into_str() + .expect("Config error, please make sure eurosport[olympics] is a string"); + let events = get_eurosport_events(url).await; + match events { + None => { + ctx.say("Oh no something went wrong").await?; + } + Some(evs) => { + info!("Found {} events from eurosport olympics ", evs.len()); + let strings = stringify_events( + evs.into_iter() + .filter(|e| e.comp(&timeframe)) + .filter(|e| match &filter { + None => true, + Some(f) => e.filter(f.as_str()), + }) + .collect(), + ); + let pages = utils::paginator(strings, 1900, "\n".to_string()); + + utils::paginate_string(ctx, pages).await?; + } + } + + Ok(()) +} diff --git a/src/commands/links/mod.rs b/src/commands/links/mod.rs new file mode 100644 index 0000000..240a310 --- /dev/null +++ b/src/commands/links/mod.rs @@ -0,0 +1,46 @@ +use crate::{commands::utils, Context, Error}; + +pub mod eurosport; + +#[derive(Debug, poise::SlashChoiceParameter)] +pub enum Timeframe { + #[name = "Currently happening"] + Current, + #[name = "Currently happening or starting in the future"] + Future, + #[name = "Currently happening or already ended"] + Past, + #[name = "Everything"] + Everything, +} + +#[derive(Debug, poise::SlashChoiceParameter)] +pub enum Source { + #[name = "Get links for the Eurosport player"] + Eurosport, + #[name = "Get links for the apocalympics Eurosport player"] + Olympics, +} + +/// Get links for high tier commands. +#[poise::command(slash_command)] +pub async fn links( + ctx: Context<'_>, + #[description = "Where to git the juicy links from?"] source: Source, + #[description = "Filter sessions for when they are/were happening, defaults to future"] + timeframe: Option, + #[description = "Content to filter on"] filter: Option, +) -> Result<(), Error> { + if !utils::high_tier(ctx).await { + ctx.say("This command can only be used in high tier channels for security") + .await?; + return Ok(()); + } + + match source { + Source::Eurosport => eurosport::proc_eurosport(ctx, timeframe, filter).await, + Source::Olympics => eurosport::proc_olympics(ctx, timeframe, filter).await, + } + + // Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a782477..e043fb6 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,6 +2,7 @@ use crate::{Context, Error}; use poise::serenity_prelude as serenity; pub mod invites; +pub mod links; pub mod planning; pub mod schedule; pub mod utils; @@ -12,7 +13,7 @@ pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { let uuid_boop = ctx.id(); ctx.send(|m| { - m.content("I want some boops!").components(|c| { + m.content("I want some boops! 🐇").components(|c| { c.create_action_row(|ar| { ar.create_button(|b| { b.style(serenity::ButtonStyle::Primary) @@ -24,11 +25,10 @@ pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { }) .await?; - let mut boop_count = 0; + let mut boop_count: i32 = 0; while let Some(mci) = serenity::CollectComponentInteraction::new(ctx.discord()) - .author_id(ctx.author().id) .channel_id(ctx.channel_id()) - .timeout(std::time::Duration::from_secs(120)) + // .timeout(std::time::Duration::from_secs(1200)) .filter(move |mci| mci.data.custom_id == uuid_boop.to_string()) .await { @@ -36,7 +36,26 @@ pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { let mut msg = mci.message.clone(); msg.edit(ctx.discord(), |m| { - m.content(format!("Boop count: {}", boop_count)) + m.content(match boop_count { + 2 => "Boop count: <:HamSmile:738765923401596991>".to_string(), + 3 => "Boop 3".to_string(), + 4 => "Boop four".to_string(), + 5 => "Boop five".to_string(), + 42 => "Boop count: 42, but what is the question?".to_string(), + 43 => "Boop count: 43 A wild <@230001507481681920> appeared".to_string(), + 69 => "Boop count: 69 Nice!".to_string(), + 77 => "Boop count: 77 <@547041420733841409> Approved".to_string(), + 308 => "Redirect 308: Boop count is in another castle".to_string(), + 400 => "ERROR 400: Bad booping".to_string(), + 401 => "ERROR 401: Unauthorized booping".to_string(), + 402 => "ERROR 402: Payment required, no free boops".to_string(), + 403 => "ERROR 403: Forbidden boop".to_string(), + 404 => "ERROR 404: Boop count not found".to_string(), + 420 => "Boop count: 420 Blaze it".to_string(), + 666 => "Boop count: 666 😈".to_string(), + 777 => "Boop count: 777 A wild <@117992484310745097> appeared".to_string(), + _ => format!("Boop count: {}", boop_count), + }) }) .await?; @@ -48,3 +67,18 @@ pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } + +pub fn get_links( +) -> poise::Command> +{ + // poise::Command { + // subcommands: vec![ + // links::eurosport::eurosport(), + // links::eurosport::olympics(), + // links::links(), + // // Let's make sure poise isn't confused by the duplicate names! + // ], + // ..links::links() + // }; + links::links() +} diff --git a/src/commands/utils.rs b/src/commands/utils.rs index 76afafb..b947f5a 100644 --- a/src/commands/utils.rs +++ b/src/commands/utils.rs @@ -1,7 +1,28 @@ use std::vec; use crate::{Context, Error}; -use poise::serenity_prelude as serenity; +use poise::serenity_prelude::{self as serenity, ChannelId}; + +pub async fn high_tier(ctx: Context<'_>) -> bool { + let ChannelId(chan_id) = ctx.channel_id(); + match chan_id { + 117992911781494787 => return true, // private general + 332337657113739265 => return true, // Testing + _ => (), + } + + match ctx.discord().cache.guild_channel(ctx.channel_id()) { + None => return false, + Some(chan) => match chan.parent_id { + None => return false, + Some(cat_id) => match cat_id { + ChannelId(547551264498515978) => return true, + ChannelId(884698356360818708) => return true, + _ => return false, + }, + }, + } +} pub fn paginator(input: Vec, chunk_size: usize, join_string: String) -> Vec { if input.len() == 0 { diff --git a/src/main.rs b/src/main.rs index 42d2128..132ab19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,65 +35,57 @@ lazy_static! { owners_only=true, )] async fn register(ctx: Context<'_>, #[flag] global: bool) -> Result<(), Error> { - poise::builtins::register_application_commands(ctx, global).await?; + // poise::builtins::register_application_commands(ctx, global).await?; - Ok(()) -} - -/// Boop the bot! -#[poise::command(prefix_command, track_edits, slash_command)] -pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { - let uuid_boop = ctx.id(); - - ctx.send(|m| { - m.content("I want some boops! 🐇").components(|c| { - c.create_action_row(|ar| { - ar.create_button(|b| { - b.style(serenity::ButtonStyle::Primary) - .label("Boop me!") - .custom_id(uuid_boop) - }) - }) - }) - }) - .await?; - - let mut boop_count = 0; - while let Some(mci) = serenity::CollectComponentInteraction::new(ctx.discord()) - .channel_id(ctx.channel_id()) - .timeout(std::time::Duration::from_secs(1200)) - .filter(move |mci| mci.data.custom_id == uuid_boop.to_string()) - .await - { - boop_count += 1; - - let mut msg = mci.message.clone(); - msg.edit(ctx.discord(), |m| { - m.content(match &boop_count { - 2 => "Boop count: <:HamSmile:738765923401596991>".to_string(), - 42 => "Boop count: 42, but what is the question?".to_string(), - 43 => "Boop count: 43 A wild <@230001507481681920> appeared".to_string(), - 69 => "Boop count: 69 Nice!".to_string(), - 77 => "Boop count: 77 <@547041420733841409> Aproved".to_string(), - 308 => "Redirect 308: Boop count is in another castle".to_string(), - 400 => "ERROR 400: Bad booping".to_string(), - 401 => "ERROR 401: Unauthorized booping".to_string(), - 402 => "ERROR 402: Payment required, no free boops".to_string(), - 403 => "ERROR 403: Forbidden boop".to_string(), - 404 => "ERROR 404: Boop count not found".to_string(), - 420 => "Boop count: 420 Blaze it".to_string(), - 666 => "Boop count: 666 😈".to_string(), - 777 => "Boop count: 777 A wild <@117992484310745097> appeared".to_string(), - _ => format!("Boop count: {}", boop_count), - }) - }) - .await?; - - mci.create_interaction_response(ctx.discord(), |ir| { - ir.kind(serenity::InteractionResponseType::DeferredUpdateMessage) - }) - .await?; + let mut commands_builder = serenity::CreateApplicationCommands::default(); + let commands = &ctx.framework().options().commands; + for command in commands { + if let Some(slash_command) = command.create_as_slash_command() { + commands_builder.add_application_command(slash_command); + } + if let Some(context_menu_command) = command.create_as_context_menu_command() { + commands_builder.add_application_command(context_menu_command); + } } + let commands_builder = serenity::json::Value::Array(commands_builder.0); + + let is_bot_owner = ctx.framework().options().owners.contains(&ctx.author().id); + if global { + if !is_bot_owner { + ctx.say("Can only be used by bot owner").await?; + return Ok(()); + } + + ctx.say(format!("Registering {} commands...", commands.len())) + .await?; + ctx.discord() + .http + .create_global_application_commands(&commands_builder) + .await?; + } else { + let guild = match ctx.guild() { + Some(x) => x, + None => { + ctx.say("Must be called in guild").await?; + return Ok(()); + } + }; + let is_guild_owner = ctx.author().id == guild.owner_id; + + if !is_guild_owner && !is_bot_owner { + ctx.say("Can only be used by server owner").await?; + return Ok(()); + } + + ctx.say(format!("Registering {} commands...", commands.len())) + .await?; + ctx.discord() + .http + .create_guild_application_commands(guild.id.0, &commands_builder) + .await?; + } + + ctx.say("Done!").await?; Ok(()) } @@ -162,8 +154,8 @@ async fn app() -> Result<(), Error> { let prefix: String = discord .get("prefix") .expect("Config error, please set the discord[token] value") - .clone() - .into_str() + .to_owned() + .try_into() .expect("Config error, please make sure discord[token] is a string"); let options = poise::FrameworkOptions { commands: vec![ @@ -173,16 +165,7 @@ async fn app() -> Result<(), Error> { commands::schedule::schedule(), commands::boop(), commands::planning::get_command(), - // poise::Command { - // subcommands: vec![ - // commands::planning::series(), - // commands::planning::sessions(), - // commands::planning::events(), - // // Let's make sure poise isn't confused by the duplicate names! - // // commands::planning::planning(), - // ], - // ..commands::planning::planning() - // }, + commands::get_links(), ], prefix_options: poise::PrefixFrameworkOptions { prefix: Some(prefix.into()), @@ -247,7 +230,31 @@ async fn main() { setup_config().unwrap(); // env_logger::init(); // init_log(); - SimpleLogger::init(LevelFilter::Warn, simplelog::Config::default()).unwrap(); + let log_default = LevelFilter::Warn; + let logger = match SETTINGS + .read() + .unwrap() + .get_table("logging") + .unwrap_or_default() + .get("level") + { + Some(val) => match val.clone().into_str() { + Ok(level) => match level.to_lowercase().as_str() { + "trace" => LevelFilter::Trace, + "debug" => LevelFilter::Debug, + "info" => LevelFilter::Info, + "warn" => LevelFilter::Warn, + "error" => LevelFilter::Error, + _ => log_default, + }, + _ => log_default, + }, + _ => log_default, + }; + + println!("Logging level: {:?}", logger); + + SimpleLogger::init(logger, simplelog::Config::default()).unwrap(); if let Err(e) = app().await { log::error!("{}", e);