Compare commits

..

3 Commits

Author SHA1 Message Date
d8d2526782 feat: set the bot nickname and status 2026-04-14 00:15:12 -04:00
0dd335334d feat: add appreciation to the pledges 2026-04-14 00:14:17 -04:00
666d13f25b chore: make pledges about how this bot works 2026-04-13 21:19:54 -04:00
2 changed files with 86 additions and 14 deletions

View File

@@ -2,8 +2,8 @@ use crate::{VCs, command::State};
use async_trait::async_trait; use async_trait::async_trait;
use snafu::{OptionExt, Snafu}; use snafu::{OptionExt, Snafu};
use songbird::{CoreEvent, Event, EventContext, EventHandler}; use songbird::{CoreEvent, Event, EventContext, EventHandler};
use time::UtcDateTime;
use std::{sync::LazyLock, time::Instant}; use std::{sync::LazyLock, time::Instant};
use time::UtcDateTime;
use twilight_model::{ use twilight_model::{
application::{ application::{
command::{Command, CommandType}, command::{Command, CommandType},
@@ -17,7 +17,9 @@ use twilight_model::{
}, },
}; };
use twilight_util::builder::{ use twilight_util::builder::{
InteractionResponseDataBuilder, command::CommandBuilder, embed::EmbedBuilder, InteractionResponseDataBuilder,
command::CommandBuilder,
embed::{EmbedBuilder, EmbedFieldBuilder, EmbedFooterBuilder},
}; };
const NAME: &str = "join"; const NAME: &str = "join";
@@ -82,7 +84,6 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Handler { struct Handler {
start_instant: Instant, start_instant: Instant,
@@ -176,21 +177,42 @@ pub async fn handle(state: State, interaction: Interaction) {
let start_instant = Instant::now(); let start_instant = Instant::now();
let start_utc = UtcDateTime::now(); let start_utc = UtcDateTime::now();
let handler = Handler { start_instant, start_utc }; let handler = Handler {
call.lock().await.add_global_event( start_instant,
CoreEvent::RtpPacket.into(), start_utc,
handler, };
); call.lock()
.await
.add_global_event(CoreEvent::RtpPacket.into(), handler);
let channel_mention = format!("<#{voice_channel_id}>"); let channel_mention = format!("<#{voice_channel_id}>");
let bot_owner_mention = format!("<@{}>", state.discord_bot_owner_user_id);
state state
.discord_client .discord_client
.interaction(state.discord_application_id) .interaction(state.discord_application_id)
.update_response( .update_response(
&interaction.token, &interaction.token,
).embeds(Some(&[ ).embeds(Some(&[
EmbedBuilder::new().title("Joined VC with intent to record").description(format!("This bot joined {channel_mention} and intends to start recording after a responsible disclosure and consent.")).validate().unwrap().build() EmbedBuilder::new()
.title("Joined VC to record")
.description(format!("This bot joined {channel_mention} and intends to record. Here are some pledges backed by faith (because there is no way to verify them yourself) in {bot_owner_mention}:"))
.field(
EmbedFieldBuilder::new("Recordings are never shared", "Audio recordings are only stored on my home server and desktop computer and will never be uploaded to services or hardware that is owned by another person: not even curated clips, and not even to people who were in the recording. When transcription to text is implemented, this will only be run on my personally owned devices and not on any internet or cloud offering.").build()
)
.field(
EmbedFieldBuilder::new("You won't be \"audited\"", "I will not reference things said in past recordings with the goal of \"making a point\", nor pull them up on the spot (even by the request of the person who said it). Ideally, these are just peace of mind for me that I'm not missing out by not being in a Discord call all the time and can take my life back, so using them in an unhealthy way isn't in my interest.").build()
)
.field(
EmbedFieldBuilder::new("Code is publically available", "The latest source code is at https://gitea.katniss.top/jacob/fomo-reducer so that I don't have to write guarantees about the technology here (e.g. what data is acquired, how it's used or stored) and you can just check it yourself").build()
)
.footer(
EmbedFooterBuilder::new("Thanks for your patience and understanding as I have bad and unusual mental health and it's crazy that I need this. This - especially if I learn if I can record streams or webcams so I don't miss out on those experiences either - should be the end of abrasion and force about how we spend our time. Again, thank you, I appreciate it.")
)
.validate()
.unwrap()
.build()
])) ]))
.await .await
.expect("TODO"); .expect("TODO");

View File

@@ -10,7 +10,10 @@ use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};
use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, StreamExt}; use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, StreamExt};
use twilight_model::{ use twilight_model::{
application::interaction::InteractionData, application::interaction::InteractionData,
gateway::payload::incoming::InteractionCreate, gateway::{
payload::{incoming::InteractionCreate, outgoing::UpdatePresence},
presence::{ActivityType, MinimalActivity, Status},
},
id::{Id, marker::UserMarker}, id::{Id, marker::UserMarker},
}; };
@@ -20,7 +23,13 @@ struct AppArgs {
discord_token: SecretString, discord_token: SecretString,
#[arg(long, env)] #[arg(long, env)]
bot_owner: Id<UserMarker>, discord_bot_owner_user_id: Id<UserMarker>,
#[arg(long, env)]
discord_nickname: Option<Arc<str>>,
#[arg(long, env)]
discord_status: Option<Arc<str>>,
#[arg(long, env)] #[arg(long, env)]
bot_data: Storage, bot_data: Storage,
@@ -77,7 +86,9 @@ async fn main() -> Result<(), MainError> {
let AppArgs { let AppArgs {
discord_token, discord_token,
bot_owner, discord_bot_owner_user_id,
discord_nickname,
discord_status,
bot_data, bot_data,
user_data, user_data,
recording_data, recording_data,
@@ -91,6 +102,24 @@ async fn main() -> Result<(), MainError> {
let discord_client = twilight_http::Client::new(discord_token.expose_secret().to_owned()); let discord_client = twilight_http::Client::new(discord_token.expose_secret().to_owned());
let guilds = discord_client
.current_user_guilds()
.limit(200)
.await
.expect("TODO")
.model()
.await
.expect("TODO");
JoinSet::from_iter(guilds.into_iter().map(|guild| {
discord_client
.update_current_member(guild.id)
.nick(discord_nickname.as_deref())
.into_future()
}))
.join_all()
.await;
let discord_user = discord_client let discord_user = discord_client
.current_user() .current_user()
.await .await
@@ -170,7 +199,7 @@ async fn main() -> Result<(), MainError> {
bot_data, bot_data,
cancellation_token: cancellation_token.clone(), cancellation_token: cancellation_token.clone(),
discord_application_id, discord_application_id,
discord_bot_owner_user_id: bot_owner, discord_bot_owner_user_id,
discord_client, discord_client,
discord_user_id, discord_user_id,
recording_data, recording_data,
@@ -179,6 +208,27 @@ async fn main() -> Result<(), MainError> {
vcs, vcs,
}; };
if let Some(discord_status) = discord_status {
shards.iter().for_each(|shard| {
shard.command(
&UpdatePresence::new(
vec![
MinimalActivity {
kind: ActivityType::Listening,
name: (*discord_status).to_owned(),
url: None,
}
.into(),
],
false,
None,
Status::Idle,
)
.expect("TODO"),
)
});
}
let run_shards = JoinSet::from_iter( let run_shards = JoinSet::from_iter(
shards shards
.into_iter() .into_iter()
@@ -210,7 +260,7 @@ async fn main() -> Result<(), MainError> {
} }
} }
#[tracing::instrument(skip(command_router, state))] #[tracing::instrument(skip(command_router, shard, state))]
async fn handle_events(command_router: Arc<CommandRouter>, state: State, mut shard: Shard) { async fn handle_events(command_router: Arc<CommandRouter>, state: State, mut shard: Shard) {
let event_types = EventTypeFlags::GUILD_VOICE_STATES let event_types = EventTypeFlags::GUILD_VOICE_STATES
| EventTypeFlags::INTERACTION_CREATE | EventTypeFlags::INTERACTION_CREATE