projbotv3/src/main.rs

286 lines
9.6 KiB
Rust
Raw Normal View History

#![allow(clippy::expect_fun_call)] // I don't see a reason for this warn
mod convert;
2022-10-15 13:42:55 +02:00
mod frame;
2022-10-09 19:46:18 +02:00
use crate::convert::*;
use crate::frame::*;
2022-10-09 19:46:18 +02:00
use serenity::{
async_trait,
framework::StandardFramework,
2022-10-09 19:47:57 +02:00
futures::StreamExt,
model::prelude::{ChannelId, ChannelType, Message},
2022-10-09 19:46:18 +02:00
prelude::*,
2022-10-09 19:47:57 +02:00
Client,
2022-10-09 19:46:18 +02:00
};
use songbird::SerenityInit;
2022-10-15 13:42:55 +02:00
use std::{
env,
fs::{self, OpenOptions},
2022-10-15 13:42:55 +02:00
io::Read,
path::Path,
sync::Arc,
2022-10-15 13:42:55 +02:00
time::{Duration, SystemTime},
};
use tokio::sync::Mutex;
2022-10-09 19:46:18 +02:00
2022-10-15 13:42:55 +02:00
async fn send_video(message: Message, ctx: Context) {
// Read all frames from vid_encoded
2022-10-09 19:46:18 +02:00
let mut v: Vec<Frame> = Vec::new();
2022-10-11 17:06:17 +02:00
let dir = fs::read_dir("vid_encoded")
.expect("unable to read dir")
.count();
2022-10-10 21:30:58 +02:00
for i in 0..dir {
2022-10-09 19:46:18 +02:00
let mut file = OpenOptions::new()
.read(true)
.write(false)
.create(false)
.open(format!("vid_encoded/{i}"))
.expect("readvid: invalid vid_encoded");
let mut buf = Vec::new();
file.read_to_end(&mut buf)
.expect("readvid: unable to read file");
2022-10-15 13:42:55 +02:00
v.push(Frame::new(buf, message.channel_id.0));
2022-10-09 19:46:18 +02:00
}
2022-10-15 13:42:55 +02:00
// Get serenity and songbird items
2022-10-09 19:46:18 +02:00
let guild_id = message.guild_id.unwrap();
let http = ctx.http.clone();
let songbird = songbird::get(&ctx)
.await
.expect("voice: unable to initialize songbird");
let c0: Arc<Mutex<Option<ChannelId>>> = Arc::new(Mutex::new(None));
let c1 = c0.clone();
2022-10-15 13:42:55 +02:00
// Spawn task to send video
2022-10-09 19:46:18 +02:00
tokio::spawn(async move {
let message = message;
let ctx = ctx;
let args = env::args().collect::<Vec<String>>();
2022-10-11 17:06:05 +02:00
let token = args.get(1).unwrap().to_owned();
2022-10-09 19:46:18 +02:00
let mut v = v.into_iter();
let n = message
.channel_id
.say(
&ctx.http,
"<ProjBotV3 by TudbuT#2624> Image will appear below",
)
.await
.expect("discord: unable to send");
2022-10-15 13:42:55 +02:00
// Spawn task to send audio - This has to be here, because this is also where the timer is
// started
2022-10-09 19:46:18 +02:00
tokio::spawn(async move {
let sa = unix_millis();
println!("voice: init");
2022-10-09 19:47:57 +02:00
let channel = guild_id
.create_channel(http, |c| c.name("ProjBotV3-Sound").kind(ChannelType::Voice))
.await
.expect("voice: unable to create channel");
2022-10-10 17:58:44 +02:00
let api_time = unix_millis() - sa;
2022-10-09 19:46:18 +02:00
*c0.lock().await = Some(channel.id);
println!("voice: joining");
let (handler, err) = songbird.join(guild_id, channel.id).await;
if let Err(e) = err {
panic!("voice: error {e}");
}
println!("voice: loading");
2022-10-10 17:58:44 +02:00
let handle = handler.lock().await.play_only_source(
2022-10-09 19:47:57 +02:00
songbird::ffmpeg("aud_encoded")
.await
.expect("voice: unable to load"),
);
2022-10-09 19:46:18 +02:00
handle.make_playable().unwrap();
handle.pause().expect("voice: unable to pause");
handle.set_volume(1.0).unwrap();
2022-10-10 17:58:44 +02:00
println!("voice: waiting for video [api_time={api_time}]");
2022-10-10 17:58:56 +02:00
tokio::time::sleep(Duration::from_millis(
2022-10-11 17:06:17 +02:00
5000 - (unix_millis() - sa)
+ (api_time as i64
* str::parse::<i64>(
2022-10-11 17:06:17 +02:00
env::var("PROJBOTV3_API_TIME_FACTOR")
2022-10-27 09:32:39 +02:00
.unwrap_or_else(|_| "5".into())
.as_str(),
2022-10-11 17:06:17 +02:00
)
.unwrap()) as u64,
2022-10-10 17:58:56 +02:00
))
.await;
2022-10-09 19:46:18 +02:00
println!("voice: playing");
handle.play().expect("voice: unable to play");
2022-10-10 17:58:44 +02:00
});
2022-10-15 13:42:55 +02:00
// Initialize and start timing
2022-10-09 19:46:18 +02:00
let mut sa = unix_millis();
let mut to_compensate_for = 0;
2022-10-12 13:22:43 +02:00
let mut free_time = 0;
2022-10-15 13:42:55 +02:00
// Send frames (5 second long gifs)
2022-10-10 21:30:58 +02:00
for mut frame in v.by_ref() {
2022-10-15 13:42:55 +02:00
// Upload the frame to the API, but don't finish off the request.
2022-10-09 19:46:18 +02:00
println!("vid: caching");
2022-10-11 17:06:05 +02:00
let token = token.clone();
let mut frame = tokio::task::spawn_blocking(move || {
frame.cache_frame(
n.id.0,
2022-10-12 13:22:43 +02:00
format!("<ProjBotV3 by TudbuT#2624> Image will appear below [to_compensate_for={to_compensate_for}, free_time={free_time}]").as_str(),
2022-10-11 17:06:05 +02:00
token.as_str(),
);
frame
}).await.unwrap();
2022-10-15 13:42:55 +02:00
// Get recent messages
2022-10-09 19:47:57 +02:00
let msgs = n
.channel_id
.messages_iter(&ctx.http)
.take(30)
.collect::<Vec<_>>()
.await;
2022-10-15 13:42:55 +02:00
// Do timing for good synchronization and commands
2022-10-09 19:46:18 +02:00
println!("vid: waiting");
let mut to_sleep = 5000 - ((unix_millis() - sa) as i128);
2022-10-15 13:42:55 +02:00
// Check for commands (timing this is required because there are a few IO
// operations being awaited)
2022-10-09 19:46:18 +02:00
sa = unix_millis();
2022-10-11 17:06:17 +02:00
if let Some(Ok(msg)) = msgs.iter().find(|x| x.as_ref().unwrap().content == "!stop") {
2022-10-09 19:47:57 +02:00
msg.delete(&ctx.http)
.await
.expect("discord: unable to delete command");
2022-10-09 19:46:18 +02:00
break;
}
2022-10-09 19:47:57 +02:00
if let Some(Ok(msg)) = msgs
.iter()
2022-10-10 21:30:58 +02:00
.find(|x| x.as_ref().unwrap().content == "!sync vid")
2022-10-09 19:47:57 +02:00
{
msg.delete(&ctx.http)
.await
.expect("discord: unable to delete command");
2022-10-09 19:46:18 +02:00
to_compensate_for += 100;
2022-10-09 19:47:57 +02:00
msg.channel_id
.say(
&ctx.http,
"<ProjBotV3 by TudbuT#2624> Skipped 100ms of video :+1:",
)
.await
.expect("discord: unable to send commannd response");
2022-10-09 19:46:18 +02:00
}
2022-10-09 19:47:57 +02:00
if let Some(Ok(msg)) = msgs
.iter()
2022-10-10 21:30:58 +02:00
.find(|x| x.as_ref().unwrap().content == "!sync aud")
2022-10-09 19:47:57 +02:00
{
msg.delete(&ctx.http)
.await
.expect("discord: unable to delete command");
2022-10-09 19:46:18 +02:00
to_sleep += 100;
2022-10-09 19:47:57 +02:00
msg.channel_id
.say(
&ctx.http,
2022-10-11 07:11:24 +02:00
"<ProjBotV3 by TudbuT#2624> Stretching 100ms of video :+1:",
2022-10-09 19:47:57 +02:00
)
.await
.expect("discord: unable to send commannd response");
2022-10-09 19:46:18 +02:00
}
to_sleep -= (unix_millis() - sa) as i128;
2022-10-15 13:42:55 +02:00
// Now factor in to_compensate_for
// Clippy doesn't like this, but it's the only way to do it in stable
#[allow(clippy::never_loop)]
2022-10-09 19:46:18 +02:00
'calc: loop {
if to_sleep < 0 {
to_compensate_for += -to_sleep;
break 'calc;
}
if to_compensate_for > 0 {
if to_sleep - to_compensate_for >= 0 {
to_sleep -= to_compensate_for;
to_compensate_for = 0;
} else {
to_compensate_for -= to_sleep;
to_sleep = 0;
}
break 'calc;
}
break 'calc;
}
2022-10-15 13:42:55 +02:00
// Set free_time to display
2022-10-12 13:22:43 +02:00
free_time = to_sleep;
2022-10-09 19:46:18 +02:00
tokio::time::sleep(Duration::from_millis(to_sleep as u64)).await;
sa = unix_millis();
2022-10-15 13:42:55 +02:00
// Now complete the request. This allows each request to take O(1) time
2022-10-09 19:46:18 +02:00
println!("vid: completing");
2022-10-11 17:06:05 +02:00
tokio::task::spawn_blocking(move || {
frame.complete_send();
2022-10-11 17:06:17 +02:00
})
.await
.unwrap();
2022-10-09 19:46:18 +02:00
}
2022-10-15 13:42:55 +02:00
// The last frame would immediately be deleted if we didn't wait here.
2022-10-10 17:58:44 +02:00
tokio::time::sleep(Duration::from_millis(5000)).await;
2022-10-15 13:42:55 +02:00
// Now clean up
2022-10-09 19:46:18 +02:00
n.delete(&ctx.http)
.await
.expect("discord: unable to delete message");
if let Some(c) = *c1.lock().await {
2022-10-09 19:47:57 +02:00
c.delete(&ctx.http)
.await
.expect("discord: unable to delete voice channel");
2022-10-09 19:46:18 +02:00
}
2022-10-10 17:58:44 +02:00
});
2022-10-09 19:46:18 +02:00
}
// Unit struct used as event handler
2022-10-09 19:46:18 +02:00
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn message(&self, ctx: Context, message: Message) {
if message.guild_id == None {
return;
}
if message.content == "!play" {
2022-10-15 13:42:55 +02:00
send_video(message, ctx).await;
2022-10-09 19:46:18 +02:00
}
}
}
2022-10-11 17:06:05 +02:00
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
2022-10-09 19:46:18 +02:00
async fn main() {
2022-10-15 13:42:55 +02:00
// If vid_encoded doesn't exist, convert vid.mp4 into vid_encoded
2022-10-10 17:58:44 +02:00
if !Path::new("vid_encoded/").is_dir() {
convert().await;
2022-10-10 17:58:44 +02:00
}
2022-10-15 13:42:55 +02:00
// Start the discord bot
2022-10-09 19:46:18 +02:00
let framework = StandardFramework::new().configure(|c| c.prefix("!"));
let mut client = Client::builder(
env::args()
.collect::<Vec<String>>()
.get(1)
.expect("discord: no token provided"),
2022-10-09 19:47:57 +02:00
GatewayIntents::non_privileged()
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILD_VOICE_STATES,
2022-10-09 19:46:18 +02:00
)
.framework(framework)
.event_handler(Handler)
.register_songbird()
.await
.expect("discord: init failed");
if let Err(why) = client.start().await {
println!("An error occurred while running the client: {:?}", why);
}
}
2022-10-15 13:42:55 +02:00
// Helper function to get millis from unix epoch as a u64
2022-10-09 19:46:18 +02:00
fn unix_millis() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}