lamprey-bridge 0.1.0

yet another chat thing?
use std::sync::Arc;

use anyhow::{anyhow, Result};
use serenity::all::{ChannelId as DcChannelId, GuildId as DcGuildId};
use tokio::sync::mpsc;
use tracing::{error, info};

use crate::{
    common::{Globals, PortalConfig},
    data::Data,
    discord,
    portal::Portal,
};

pub struct Bridge {
    globals: Arc<Globals>,
    recv: mpsc::UnboundedReceiver<BridgeMessage>,
}

#[derive(Debug, Clone)]
pub enum BridgeMessage {
    LampreyThreadCreate {
        thread: common::v1::types::Channel,
        discord_guild_id: DcGuildId,
    },
    DiscordChannelCreate {
        guild_id: DcGuildId,
        channel_id: DcChannelId,
        channel_name: String,
        channel_type: serenity::all::ChannelType,
        parent_id: Option<DcChannelId>,
    },
}

impl Bridge {
    pub fn spawn(globals: Arc<Globals>, recv: mpsc::UnboundedReceiver<BridgeMessage>) {
        let bridge = Self { globals, recv };
        tokio::spawn(bridge.activate());
    }

    async fn activate(mut self) {
        while let Some(msg) = self.recv.recv().await {
            if let Err(err) = self.handle(msg).await {
                error!("{err}")
            }
        }
    }

    #[tracing::instrument(skip(self))]
    async fn handle(&mut self, msg: BridgeMessage) -> Result<()> {
        match msg {
            BridgeMessage::LampreyThreadCreate {
                thread,
                discord_guild_id,
            } => {
                if self
                    .globals
                    .get_portal_by_thread_id(thread.id)
                    .await
                    .is_ok_and(|a| a.is_some())
                {
                    info!("portal already exists");
                    return Ok(());
                }

                info!("autobridging thread {}", thread.id);

                let discord_parent_id = if let Some(lamprey_parent_id) = thread.parent_id {
                    if let Ok(Some(parent_portal)) = self
                        .globals
                        .get_portal_by_thread_id(lamprey_parent_id)
                        .await
                    {
                        Some(parent_portal.discord_channel_id)
                    } else {
                        None
                    }
                } else {
                    None
                };

                let name = if thread.name.is_empty() {
                    "thread".to_string()
                } else {
                    thread.name.clone()
                };
                let channel_id = discord::discord_create_channel(
                    self.globals.clone(),
                    discord_guild_id,
                    name.clone(),
                    thread.ty,
                    discord_parent_id,
                )
                .await?;

                let webhook_url = if thread.ty != common::v1::types::ChannelType::Category {
                    let webhook = discord::discord_create_webhook(
                        self.globals.clone(),
                        channel_id,
                        "bridge".to_string(),
                    )
                    .await?;
                    webhook
                        .url()
                        .map_err(|_| anyhow!("created webhook has no url"))?
                } else {
                    "".to_string()
                };

                let portal = PortalConfig {
                    lamprey_thread_id: thread.id,
                    lamprey_room_id: thread
                        .room_id
                        .ok_or_else(|| anyhow!("lamprey thread {} has no room id", thread.id))?,
                    discord_guild_id,
                    discord_channel_id: channel_id,
                    discord_thread_id: None,
                    discord_webhook: webhook_url,
                };
                self.globals.insert_portal(portal.clone()).await?;
                self.globals
                    .portals
                    .entry(portal.lamprey_thread_id)
                    .or_insert_with(|| Portal::summon(self.globals.clone(), portal));
            }
            BridgeMessage::DiscordChannelCreate {
                guild_id,
                channel_id,
                channel_name,
                channel_type,
                parent_id,
            } => {
                let Ok(realms) = self.globals.get_realms().await else {
                    return Ok(());
                };

                let Some(realm_config) = realms.iter().find(|c| c.discord_guild_id == guild_id)
                else {
                    return Ok(());
                };

                if !realm_config.continuous {
                    return Ok(());
                }

                if self
                    .globals
                    .get_portal_by_discord_channel(channel_id)
                    .await
                    .is_ok_and(|a| a.is_some())
                {
                    info!("already exists");
                    return Ok(());
                }

                info!("autobridging discord channel {}", channel_id);
                let ly = self.globals.lamprey_handle().await?;

                let thread_name = if channel_name.is_empty() {
                    "thread".to_string()
                } else {
                    channel_name.clone()
                };

                let thread_type = if channel_type == serenity::all::ChannelType::Category {
                    common::v1::types::ChannelType::Category
                } else {
                    common::v1::types::ChannelType::Text
                };

                let lamprey_parent_id = if let Some(discord_parent_id) = parent_id {
                    if let Ok(Some(parent_portal)) = self
                        .globals
                        .get_portal_by_discord_channel(discord_parent_id)
                        .await
                    {
                        Some(parent_portal.lamprey_thread_id)
                    } else {
                        None
                    }
                } else {
                    None
                };

                let thread = ly
                    .create_thread(
                        realm_config.lamprey_room_id,
                        thread_name.clone(),
                        None,
                        thread_type,
                        lamprey_parent_id,
                    )
                    .await?;

                let webhook_url = if channel_type != serenity::all::ChannelType::Category {
                    let webhook = discord::discord_create_webhook(
                        self.globals.clone(),
                        channel_id,
                        "bridge".to_string(),
                    )
                    .await?;
                    webhook
                        .url()
                        .map_err(|_| anyhow!("created webhook has no url"))?
                } else {
                    "".to_string()
                };

                let portal_config = PortalConfig {
                    lamprey_thread_id: thread.id,
                    lamprey_room_id: realm_config.lamprey_room_id,
                    discord_guild_id: guild_id,
                    discord_channel_id: channel_id,
                    discord_thread_id: None,
                    discord_webhook: webhook_url,
                };

                self.globals.insert_portal(portal_config.clone()).await?;

                self.globals
                    .portals
                    .entry(portal_config.lamprey_thread_id)
                    .or_insert_with(|| Portal::summon(self.globals.clone(), portal_config));
            }
        }

        Ok(())
    }
}