Compare commits

...

2 commits

Author SHA1 Message Date
387f6ff587 gamer 2 2025-11-01 15:25:58 +01:00
bb2c954418 gamer 2025-11-01 15:20:59 +01:00
7 changed files with 816 additions and 520 deletions

1140
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,9 +11,10 @@ tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
uwuify = "^0.2" uwuify = "^0.2"
async-trait = "0.1.50" async-trait = "0.1.50"
lazy_static = "1.4.0" lazy_static = "1.4.0"
toml = "0.8.8" toml = "0.9.8"
fancy-regex = "0.13.0" fancy-regex = "0.16.2"
rand = "0.8.3" rand = "0.9.2"
serde = { version = "1.0.228", features = ["derive"] }
[dependencies.serenity] [dependencies.serenity]
version = "0.12.0" version = "0.12.0"

View file

@ -1,30 +0,0 @@
FROM rust as build
RUN rustup toolchain install nightly
RUN rustup default nightly
RUN rustup target add x86_64-unknown-linux-musl
RUN apt-get update
RUN apt-get install musl-tools -y
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src
RUN echo "fn main() {}" > src/main.rs
RUN cargo build --release --target x86_64-unknown-linux-musl
COPY src ./src
# now rebuild with the proper main
RUN touch src/main.rs
RUN cargo build --release --target x86_64-unknown-linux-musl
### RUN
FROM scratch
WORKDIR /app
COPY --from=build /app/target/x86_64-unknown-linux-musl/release/widertom widertom
CMD ["/app/widertom"]

4
run.sh
View file

@ -1,4 +0,0 @@
#!/usr/bin/env bash
docker run -d -v "$(pwd)/config.toml:/app/config.toml" -v "$(pwd)/bot_token:/app/bot_token" \
-e CONFIG_PATH=/app/config.toml -e BOT_TOKEN_PATH=/app/bot_token --name widertom widertom:1.0

View file

@ -1,7 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use lazy_static::lazy_static; use rand::seq::IndexedRandom;
use rand::Rng;
use serenity::all::{CreateEmbed, CreateMessage}; use serenity::all::{CreateEmbed, CreateMessage};
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::{ use serenity::framework::standard::{
@ -12,11 +11,8 @@ use serenity::framework::standard::{
use serenity::model::channel::Message; use serenity::model::channel::Message;
use serenity::model::id::UserId; use serenity::model::id::UserId;
use serenity::utils::{content_safe, ContentSafeOptions}; use serenity::utils::{content_safe, ContentSafeOptions};
use toml::Value;
use uwuifier::uwuify_str_sse;
use crate::general::{reply, CONFIG, CONFIG_ERR, REACTION_EMOTES}; use crate::{Config, LastMessageInChannel};
use crate::LastMessageInChannel;
#[group] #[group]
#[commands(say, list)] #[commands(say, list)]
@ -28,21 +24,18 @@ struct General;
#[description = "meme commands"] #[description = "meme commands"]
struct Meme; struct Meme;
#[group]
#[commands(shutdown)]
#[owners_only]
#[description = "bot admin commands"]
struct Admin;
#[command] #[command]
#[description("lists all the commands")] #[description("lists all the commands")]
async fn list(ctx: &Context, msg: &Message, _: Args) -> CommandResult { async fn list(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
let data = ctx.data.read().await;
let config = data.get::<Config>().unwrap();
msg.channel_id msg.channel_id
.send_message( .send_message(
&ctx.http, &ctx.http,
CreateMessage::new().embed( CreateMessage::new().embed(
CreateEmbed::new().title("Widetom reaction emotes").fields( CreateEmbed::new().title("Widetom reaction emotes").fields(
REACTION_EMOTES config
.emotes
.iter() .iter()
.map(|em| (em.0, format!("<:{}:{}>", em.0, em.1.get()), false)), .map(|em| (em.0, format!("<:{}:{}>", em.0, em.1.get()), false)),
), ),
@ -65,7 +58,7 @@ async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
.clean_role(false) .clean_role(false)
}; };
let content = content_safe(&ctx.cache, &args.rest(), &settings, &[]); let content = content_safe(&ctx.cache, args.rest(), &settings, &[]);
msg.delete(&ctx.http).await?; msg.delete(&ctx.http).await?;
msg.channel_id.say(&ctx.http, &content).await?; msg.channel_id.say(&ctx.http, &content).await?;
Ok(()) Ok(())
@ -75,7 +68,7 @@ async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
#[description("uwuifies the arguments, or the last message in the channel if no args are supplied")] #[description("uwuifies the arguments, or the last message in the channel if no args are supplied")]
async fn uwuify(ctx: &Context, msg: &Message, args: Args) -> CommandResult { async fn uwuify(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if let Some(parent) = &msg.referenced_message { if let Some(parent) = &msg.referenced_message {
let uwu = uwuify_str_sse(&*parent.content); let uwu = uwuifier::uwuify_str_sse(&parent.content);
msg.channel_id.say(&ctx.http, uwu).await?; msg.channel_id.say(&ctx.http, uwu).await?;
} else if args.is_empty() { } else if args.is_empty() {
let mut data = ctx.data.write().await; let mut data = ctx.data.write().await;
@ -85,7 +78,7 @@ async fn uwuify(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let old_message = map.get(&msg.channel_id); let old_message = map.get(&msg.channel_id);
match old_message { match old_message {
Some(s) => { Some(s) => {
let uwu = uwuify_str_sse(s); let uwu = uwuifier::uwuify_str_sse(s);
msg.channel_id.say(&ctx.http, uwu).await?; msg.channel_id.say(&ctx.http, uwu).await?;
} }
None => { None => {
@ -95,36 +88,21 @@ async fn uwuify(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
} }
} }
} else { } else {
let uwu = uwuify_str_sse(args.rest()); let uwu = uwuifier::uwuify_str_sse(args.rest());
msg.channel_id.say(&ctx.http, uwu).await?; msg.channel_id.say(&ctx.http, uwu).await?;
} }
Ok(()) Ok(())
} }
#[command]
#[description("end tom")]
async fn shutdown(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
reply(
"<:tom:811324632082415626> bye <:tom:811324632082415626>",
&msg,
&ctx,
)
.await;
std::process::exit(0);
}
#[command] #[command]
#[description("display a random answer from the xp support applications")] #[description("display a random answer from the xp support applications")]
async fn xp(ctx: &Context, msg: &Message, _: Args) -> CommandResult { async fn xp(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
lazy_static! { let data = ctx.data.read().await;
static ref XP_RESPONSES: &'static Vec<Value> = CONFIG let config = data.get::<Config>().unwrap();
.get("xp")
.expect(CONFIG_ERR) let Some(random_value) = config.xp.choose(&mut rand::rng()) else {
.as_array() return Ok(());
.expect(CONFIG_ERR); };
}
let index = rand::thread_rng().gen_range(0..XP_RESPONSES.len());
let random_value = XP_RESPONSES[index].as_str().expect(CONFIG_ERR);
msg.channel_id.say(&ctx.http, random_value).await?; msg.channel_id.say(&ctx.http, random_value).await?;
Ok(()) Ok(())
} }

View file

@ -1,50 +1,11 @@
use std::collections::HashMap; use std::sync::LazyLock;
use std::fs;
use fancy_regex::Regex; use fancy_regex::Regex;
use lazy_static::lazy_static;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::hook; use serenity::framework::standard::macros::hook;
use serenity::model::channel::{Message, ReactionType}; use serenity::model::channel::{Message, ReactionType};
use serenity::model::id::EmojiId;
use toml::Value;
use crate::LastMessageInChannel; use crate::{Config, LastMessageInChannel};
pub static CONFIG_ERR: &'static str = "Invalid config file";
lazy_static! {
pub static ref CONFIG: Value = {
let config_path =
std::env::var("CONFIG_PATH").unwrap_or_else(|_| "config.toml".to_string());
let config = fs::read_to_string(config_path)
.expect("Config file not found. Add 'config.toml' to this directory");
config.parse::<Value>().expect(CONFIG_ERR)
};
pub static ref REACTION_EMOTES: HashMap<String, EmojiId> = {
let mut m = HashMap::new();
let emotes = CONFIG.get("emotes").expect(CONFIG_ERR);
for v in emotes.as_array().expect(CONFIG_ERR) {
let name = v[0].as_str().expect(CONFIG_ERR).to_string();
let id = EmojiId::new(v[1].as_integer().expect(CONFIG_ERR).clone() as u64);
m.insert(name, id);
}
m
};
static ref RESPONSES: HashMap<String, String> = {
let mut m = HashMap::new();
let emotes = CONFIG.get("responses").expect(CONFIG_ERR);
for v in emotes.as_array().expect(CONFIG_ERR) {
let trigger = v[0].as_str().expect(CONFIG_ERR).to_string();
let response = v[1].as_str().expect(CONFIG_ERR).to_string();
m.insert(trigger, response);
}
m
};
}
#[hook] #[hook]
pub async fn normal_message(ctx: &Context, msg: &Message) { pub async fn normal_message(ctx: &Context, msg: &Message) {
@ -52,11 +13,12 @@ pub async fn normal_message(ctx: &Context, msg: &Message) {
let map = data let map = data
.get_mut::<LastMessageInChannel>() .get_mut::<LastMessageInChannel>()
.expect("LastMessageInChannel not found"); .expect("LastMessageInChannel not found");
map.insert(msg.channel_id.clone(), msg.content.clone()); map.insert(msg.channel_id, msg.content.clone());
lazy_static! { let config = data.get::<Config>().unwrap();
static ref TOM_REGEX: Regex = Regex::new(r"(?<=^|\D)(\d{6})(?=\D|$)").unwrap();
} static TOM_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?<=^|\D)(\d{6})(?=\D|$)").unwrap());
let is_nsfw = msg let is_nsfw = msg
.channel_id .channel_id
@ -71,17 +33,17 @@ pub async fn normal_message(ctx: &Context, msg: &Message) {
.as_str() .as_str()
.parse::<u32>() .parse::<u32>()
.expect("matched regex, so it is valid"); .expect("matched regex, so it is valid");
reply(&*format!("<https://nhentai.net/g/{}/>", number), &msg, &ctx).await; reply(&format!("<https://nhentai.net/g/{}/>", number), msg, ctx).await;
} }
} }
for (trigger, answer) in RESPONSES.iter() { for (trigger, answer) in config.responses.iter() {
if msg.content.to_lowercase() == *trigger { if msg.content.to_lowercase() == *trigger {
reply(answer, &msg, &ctx).await; reply(answer, msg, ctx).await;
} }
} }
for (name, id) in REACTION_EMOTES.iter() { for (name, id) in config.emotes.iter() {
if msg.content.to_lowercase().contains(name) { if msg.content.to_lowercase().contains(name) {
if let Err(why) = msg if let Err(why) = msg
.react( .react(

View file

@ -5,24 +5,42 @@ use std::collections::{HashMap, HashSet};
use std::fs; use std::fs;
use serenity::all::standard::Configuration; use serenity::all::standard::Configuration;
use serenity::all::EmojiId;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::StandardFramework; use serenity::framework::StandardFramework;
use serenity::http::Http; use serenity::http::Http;
use serenity::model::id::{ChannelId, UserId}; use serenity::model::id::{ChannelId, UserId};
use serenity::{async_trait, model::gateway::Ready, prelude::*}; use serenity::{async_trait, model::gateway::Ready, prelude::*};
use crate::commands::{ADMIN_GROUP, GENERAL_GROUP, MEME_GROUP, MY_HELP}; use crate::commands::{GENERAL_GROUP, MEME_GROUP, MY_HELP};
use crate::general::normal_message; use crate::general::normal_message;
mod commands; mod commands;
mod general; mod general;
pub struct LastMessageInChannel; #[derive(serde::Deserialize)]
struct ConfigFile {
emotes: Vec<(String, EmojiId)>,
responses: Vec<(String, String)>,
xp: Vec<String>,
}
struct Config {
emotes: HashMap<String, EmojiId>,
responses: HashMap<String, String>,
xp: Vec<String>,
}
struct LastMessageInChannel;
impl TypeMapKey for LastMessageInChannel { impl TypeMapKey for LastMessageInChannel {
type Value = HashMap<ChannelId, String>; type Value = HashMap<ChannelId, String>;
} }
impl TypeMapKey for Config {
type Value = Self;
}
struct Handler; struct Handler;
#[async_trait] #[async_trait]
@ -34,11 +52,22 @@ impl EventHandler for Handler {
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let config_path = std::env::var("CONFIG_PATH").unwrap_or_else(|_| "config.toml".to_string());
let config = fs::read_to_string(config_path)
.expect("Config file not found. Add 'config.toml' to this directory");
let config = toml::from_str::<ConfigFile>(&config).expect("invalid config file");
let config = Config {
emotes: config.emotes.into_iter().collect(),
responses: config.responses.into_iter().collect(),
xp: config.xp,
};
let token_path = std::env::var("BOT_TOKEN_PATH").unwrap_or_else(|_| "bot_token".to_string()); let token_path = std::env::var("BOT_TOKEN_PATH").unwrap_or_else(|_| "bot_token".to_string());
let token = fs::read_to_string(token_path).expect("Expected bot token in file 'bot_token'"); let token = fs::read_to_string(token_path).expect("Expected bot token in file 'bot_token'");
let token = token.trim(); let token = token.trim();
let http = Http::new(&token); let http = Http::new(token);
http.get_current_application_info() http.get_current_application_info()
.await .await
@ -58,8 +87,7 @@ async fn main() {
.normal_message(normal_message) .normal_message(normal_message)
.help(&MY_HELP) .help(&MY_HELP)
.group(&GENERAL_GROUP) .group(&GENERAL_GROUP)
.group(&MEME_GROUP) .group(&MEME_GROUP);
.group(&ADMIN_GROUP);
framework.configure( framework.configure(
Configuration::new() Configuration::new()
.with_whitespace(false) .with_whitespace(false)
@ -71,7 +99,7 @@ async fn main() {
// We don't really need all intents, but this is a small bot so we don't care. // We don't really need all intents, but this is a small bot so we don't care.
let intents = GatewayIntents::all(); let intents = GatewayIntents::all();
let mut client = Client::builder(&token, intents) let mut client = Client::builder(token, intents)
.event_handler(Handler) .event_handler(Handler)
.framework(framework) .framework(framework)
.await .await
@ -80,6 +108,7 @@ async fn main() {
{ {
let mut data = client.data.write().await; let mut data = client.data.write().await;
data.insert::<LastMessageInChannel>(HashMap::default()); data.insert::<LastMessageInChannel>(HashMap::default());
data.insert::<Config>(config);
} }
if let Err(why) = client.start().await { if let Err(why) = client.start().await {