dircord/src/main.rs

202 lines
5.7 KiB
Rust
Raw Permalink Normal View History

#![warn(clippy::pedantic)]
mod discord_irc;
mod irc_discord;
use std::{borrow::Cow, collections::HashMap, env, fs::File, io::Read, sync::Arc};
2021-12-26 15:13:09 -06:00
2021-12-26 16:05:53 -06:00
use serenity::{
2022-06-17 01:15:39 -05:00
http::Http,
2021-12-26 16:05:53 -06:00
model::{
2022-07-24 11:38:19 -05:00
gateway::GatewayIntents,
2022-06-17 01:15:39 -05:00
guild::Member,
id::{ChannelId, UserId},
2021-12-27 17:33:09 -06:00
webhook::Webhook,
2021-12-26 16:05:53 -06:00
},
2021-12-27 11:24:31 -06:00
Client as DiscordClient,
};
2022-01-24 11:37:53 -06:00
use tokio::{select, sync::Mutex};
2022-06-17 01:15:39 -05:00
use irc::client::{data::Config, Client as IrcClient, Sender};
2022-06-17 01:15:39 -05:00
use crate::discord_irc::Handler;
use crate::irc_discord::irc_loop;
2022-01-18 16:55:05 -06:00
2022-06-17 01:15:39 -05:00
use fancy_regex::{Captures, Replacer};
use serde::Deserialize;
#[derive(Deserialize)]
struct DircordConfig {
token: String,
nickname: Option<String>,
server: String,
port: Option<u16>,
mode: Option<String>,
tls: Option<bool>,
2022-04-04 11:37:55 -05:00
raw_prefix: Option<String>,
channels: HashMap<String, u64>,
webhooks: Option<HashMap<String, String>>,
2022-08-02 18:57:43 -05:00
ref_content_limit: Option<u16>,
2022-08-05 11:50:10 -05:00
cache_ttl: Option<u64>,
}
2021-12-27 13:49:54 -06:00
macro_rules! type_map_key {
($($name:ident => $value:ty),* $(,)?) => {
$(
struct $name;
impl ::serenity::prelude::TypeMapKey for $name {
type Value = $value;
}
)*
};
2022-04-04 11:35:26 -05:00
}
type_map_key!(
HttpKey => Arc<Http>,
ChannelIdKey => ChannelId,
UserIdKey => UserId,
SenderKey => Sender,
MembersKey => Arc<Mutex<Vec<Member>>>,
StringKey => String,
OptionStringKey => Option<String>,
ChannelMappingKey => HashMap<String, u64>,
2022-08-02 18:57:43 -05:00
RefContentLimitKey => Option<u16>,
);
2022-01-24 11:37:53 -06:00
#[cfg(unix)]
async fn terminate_signal() {
use tokio::signal::unix::{signal, SignalKind};
let mut sigterm = signal(SignalKind::terminate()).unwrap();
let mut sigint = signal(SignalKind::interrupt()).unwrap();
select! {
_ = sigterm.recv() => {},
_ = sigint.recv() => {},
2022-01-24 11:37:53 -06:00
}
}
#[cfg(windows)]
async fn terminate_signal() {
use tokio::signal::windows::ctrl_c;
let mut ctrlc = ctrl_c().unwrap();
let _ = ctrlc.recv().await;
}
2021-12-26 15:13:09 -06:00
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let filename = env::args()
.nth(1)
.map_or(Cow::Borrowed("config.toml"), Cow::Owned);
2021-12-27 13:49:54 -06:00
let mut data = String::new();
File::open(&*filename)?.read_to_string(&mut data)?;
2021-12-27 13:49:54 -06:00
let conf: DircordConfig = toml::from_str(&data)?;
2021-12-26 15:13:09 -06:00
2022-07-24 11:38:19 -05:00
let intents = GatewayIntents::non_privileged()
| GatewayIntents::GUILD_MEMBERS
| GatewayIntents::MESSAGE_CONTENT;
2022-07-23 21:37:10 -05:00
2022-07-24 11:38:19 -05:00
let mut discord_client = DiscordClient::builder(&conf.token, intents)
2021-12-27 11:24:31 -06:00
.event_handler(Handler)
.await?;
2021-12-26 15:13:09 -06:00
2021-12-27 11:24:31 -06:00
let config = Config {
nickname: conf.nickname,
server: Some(conf.server),
port: conf.port,
channels: conf.channels.keys().map(Clone::clone).collect(),
use_tls: conf.tls,
umodes: conf.mode,
2021-12-27 11:24:31 -06:00
..Config::default()
};
2021-12-27 13:28:35 -06:00
let irc_client = IrcClient::from_config(config).await?;
2021-12-27 11:24:31 -06:00
let http = discord_client.cache_and_http.http.clone();
2022-07-16 15:20:50 -05:00
let cache = discord_client.cache_and_http.cache.clone();
2021-12-27 11:24:31 -06:00
let members = Arc::new(Mutex::new({
let channel_id = ChannelId::from(*conf.channels.iter().next().unwrap().1);
2022-01-15 12:32:47 -06:00
channel_id
.to_channel(discord_client.cache_and_http.clone())
.await?
.guild()
.unwrap() // we can panic here because if it's not a guild channel then the bot shouldn't even work
.guild_id
.members(&http, None, None)
.await?
}));
let channels = Arc::new(conf.channels);
2021-12-27 17:33:09 -06:00
2021-12-26 15:35:49 -06:00
{
2021-12-27 11:24:31 -06:00
let mut data = discord_client.data.write().await;
data.insert::<SenderKey>(irc_client.sender());
data.insert::<MembersKey>(members.clone());
2022-04-04 11:35:26 -05:00
data.insert::<OptionStringKey>(conf.raw_prefix);
data.insert::<ChannelMappingKey>((*channels).clone());
2022-08-02 18:57:43 -05:00
data.insert::<RefContentLimitKey>(conf.ref_content_limit);
2021-12-26 15:35:49 -06:00
}
let mut webhooks_transformed: HashMap<String, Webhook> = HashMap::new();
if let Some(webhooks) = conf.webhooks {
for (channel, wh) in webhooks {
let parsed = parse_webhook_url(http.clone(), wh)
.await
.expect("Invalid webhook URL");
webhooks_transformed.insert(channel.clone(), parsed);
}
}
2021-12-27 14:46:12 -06:00
2022-01-24 11:37:53 -06:00
select! {
2022-08-05 11:50:10 -05:00
r = irc_loop(irc_client, http.clone(), cache.clone(), channels.clone(), webhooks_transformed, members, conf.cache_ttl) => r.unwrap(),
2022-07-07 01:22:39 -05:00
r = discord_client.start() => r.unwrap(),
_ = terminate_signal() => {
for (_, &v) in channels.iter() {
let channel_id = ChannelId::from(v);
2022-07-07 01:22:39 -05:00
channel_id.say(&http, format!("dircord shutting down! (dircord {}-{})", env!("VERGEN_GIT_BRANCH"), &env!("VERGEN_GIT_SHA")[..7])).await.unwrap();
}
},
2022-01-24 11:37:53 -06:00
}
2021-12-26 15:13:09 -06:00
Ok(())
}
struct OptionReplacer<F>(F);
impl<T: AsRef<str>, F: for<'r, 't> FnMut(&'r Captures<'t>) -> Option<T>> Replacer
for OptionReplacer<F>
{
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
match (self.0)(caps) {
Some(v) => dst.push_str(v.as_ref()),
None => dst.push_str(caps.get(0).unwrap().as_str()),
}
}
}
#[macro_export]
2022-06-16 12:11:04 -05:00
macro_rules! regex {
($(static $name:ident = $regex:literal;)*) => {
::lazy_static::lazy_static! {
$(
static ref $name: ::fancy_regex::Regex = ::fancy_regex::Regex::new($regex).unwrap();
2022-06-16 12:11:04 -05:00
)*
}
};
}
async fn parse_webhook_url(http: Arc<Http>, url: String) -> anyhow::Result<Webhook> {
let url = url.trim_start_matches("https://discord.com/api/webhooks/");
let split = url.split('/').collect::<Vec<&str>>();
let id = split[0].parse::<u64>()?;
let token = split[1].to_string();
let webhook = http.get_webhook_with_token(id, &token).await?;
Ok(webhook)
2021-12-27 17:33:09 -06:00
}