Initial commit
commit
fbc6270a0a
|
@ -0,0 +1,12 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_size = 4
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
|
@ -0,0 +1,7 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"phoebe",
|
||||||
|
"phoebe-main",
|
||||||
|
"mid-chat",
|
||||||
|
"services/*"
|
||||||
|
]
|
|
@ -0,0 +1,11 @@
|
||||||
|
# phoebe
|
||||||
|
|
||||||
|
bridgers [primarily, discord ↔ matrix]
|
||||||
|
|
||||||
|
A [Charlotte Som](https://som.codes/) project.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- `mid-chat` - An intermediate representation for chat messages. Best-effort common denomination
|
||||||
|
- `services/*` - Handling for individual chat services & conversion to and from the common-denominator chat message IR
|
||||||
|
- `phoebe` - Main: Database, message dispatch, service orchestration, etc
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!/.gitignore
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "mid-chat"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,30 @@
|
||||||
|
pub type ChatMessageContent = Vec<ChatContentComponent>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ChatContentComponent {
|
||||||
|
Plain(String),
|
||||||
|
Link {
|
||||||
|
target: String,
|
||||||
|
text: ChatMessageContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
Italic(ChatMessageContent),
|
||||||
|
Bold(ChatMessageContent),
|
||||||
|
Strikethrough(ChatMessageContent),
|
||||||
|
Underline(ChatMessageContent),
|
||||||
|
|
||||||
|
Code(String),
|
||||||
|
CodeBlock {
|
||||||
|
lang: Option<String>,
|
||||||
|
source: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
Spoiler {
|
||||||
|
reason: Option<String>,
|
||||||
|
content: ChatMessageContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
HardBreak,
|
||||||
|
|
||||||
|
BlockQuote(ChatMessageContent),
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
use crate::ChatMessage;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ChatEvent {
|
||||||
|
NewMessage(ChatMessage),
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
pub mod reference;
|
||||||
|
pub use reference::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChatAuthor {
|
||||||
|
pub reference: ChatReference,
|
||||||
|
pub display_name: String,
|
||||||
|
pub display_color: Option<[u8; 3]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod content;
|
||||||
|
pub use content::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChatMessage {
|
||||||
|
pub origin: ChatReference,
|
||||||
|
pub author: ChatAuthor,
|
||||||
|
pub content: ChatMessageContent,
|
||||||
|
// TODO: Attachments
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod event;
|
|
@ -0,0 +1,5 @@
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub struct ChatReference {
|
||||||
|
pub service: &'static str,
|
||||||
|
pub id: String,
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "phoebe-main"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
tracing-subscriber = { version = "0.3.10", features = ["env-filter"] }
|
||||||
|
color-eyre = "0.6.1"
|
||||||
|
phoebe = { path = "../phoebe" }
|
||||||
|
phoebe-discord = { path = "../services/phoebe-discord" }
|
|
@ -0,0 +1,23 @@
|
||||||
|
use color_eyre::Result;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
use phoebe::service::Service;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_target(true)
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let (tx, rx) = tokio::sync::broadcast::channel(512);
|
||||||
|
|
||||||
|
let db = phoebe::open_core_db().await?;
|
||||||
|
let services: Vec<Box<dyn Service>> = vec![Box::new(
|
||||||
|
phoebe_discord::setup(db.clone(), tx.clone()).await?,
|
||||||
|
)];
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL="sqlite://PHOEBE_DB_ROOT/main.db"
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "phoebe"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["videogame hacker <half-kh-hacker@hackery.site>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mid-chat = { path = "../mid-chat" }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
sqlx = { version = "0.5", features = ["runtime-tokio-native-tls", "sqlite"] }
|
||||||
|
tracing = "0.1"
|
||||||
|
async-trait = "0.1.53"
|
||||||
|
eyre = "0.6.8"
|
|
@ -0,0 +1,26 @@
|
||||||
|
use sqlx::{sqlite::SqliteConnectOptions, SqlitePool};
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
static PHOEBE_DB_ROOT: OnceCell<String> = OnceCell::const_new();
|
||||||
|
|
||||||
|
async fn get_db_root() -> &'static str {
|
||||||
|
PHOEBE_DB_ROOT
|
||||||
|
.get_or_init(|| async {
|
||||||
|
std::env::var("PHOEBE_DB_ROOT")
|
||||||
|
.expect("PHOEBE_DB_ROOT environment variable was not set!")
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_or_open_sqlite(url: &str) -> sqlx::Result<SqlitePool> {
|
||||||
|
let options = url.parse::<SqliteConnectOptions>()?.create_if_missing(true);
|
||||||
|
SqlitePool::connect_with(options).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn open(name: &str) -> sqlx::Result<SqlitePool> {
|
||||||
|
debug!("Opening {name} database…");
|
||||||
|
let db_root = get_db_root().await;
|
||||||
|
let db_url = format!("sqlite://{}/{}.db", db_root, name);
|
||||||
|
create_or_open_sqlite(&db_url).await
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
use mid_chat::event::ChatEvent;
|
||||||
|
|
||||||
|
pub mod db;
|
||||||
|
pub mod prelude;
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
|
pub type ChatEventSender = tokio::sync::broadcast::Sender<ChatEvent>;
|
||||||
|
pub type ChatEventReceiver = tokio::sync::broadcast::Receiver<ChatEvent>;
|
||||||
|
|
||||||
|
pub async fn open_core_db() -> sqlx::Result<sqlx::SqlitePool> {
|
||||||
|
let db = db::open("main").await?;
|
||||||
|
sqlx::migrate!().run(&db).await?;
|
||||||
|
Ok(db)
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
pub use crate::{service::Service, ChatEventReceiver, ChatEventSender};
|
||||||
|
|
||||||
|
pub use async_trait::async_trait;
|
||||||
|
pub use eyre::Result;
|
||||||
|
pub use mid_chat::event::ChatEvent;
|
||||||
|
pub use sqlx::SqlitePool;
|
|
@ -0,0 +1,7 @@
|
||||||
|
use mid_chat::event::ChatEvent;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait Service {
|
||||||
|
fn get_service_tag(&self) -> &'static str;
|
||||||
|
async fn handle_chat_event(&mut self, event: &ChatEvent);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL="sqlite://PHOEBE_DB_ROOT/discord_media.db"
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "phoebe-discord"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
phoebe = { path = "../../phoebe" }
|
||||||
|
serenity = "0.10.10"
|
||||||
|
sqlx = { version = "0.5", features = ["runtime-tokio-native-tls", "sqlite"] }
|
||||||
|
tracing = "0.1"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
|
@ -0,0 +1,70 @@
|
||||||
|
use phoebe::prelude::*;
|
||||||
|
use serenity::{
|
||||||
|
client::{Context, EventHandler},
|
||||||
|
model::prelude::*,
|
||||||
|
prelude::*,
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
pub struct DiscordService {
|
||||||
|
discord_client: Client,
|
||||||
|
discord_ctx: Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DiscordHandler {
|
||||||
|
core_db: SqlitePool,
|
||||||
|
discord_media_db: SqlitePool,
|
||||||
|
chat_event_tx: ChatEventSender,
|
||||||
|
ctx_tx: tokio::sync::mpsc::Sender<Context>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for DiscordHandler {
|
||||||
|
async fn ready(&self, ctx: Context, _data_about_bot: Ready) {
|
||||||
|
let _ = self.ctx_tx.send(ctx).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn setup(core_db: SqlitePool, tx: ChatEventSender) -> Result<DiscordService> {
|
||||||
|
info!("Setting up Discord service…");
|
||||||
|
|
||||||
|
let discord_media_db = phoebe::db::open("discord_media").await?;
|
||||||
|
sqlx::migrate!().run(&discord_media_db).await?;
|
||||||
|
|
||||||
|
let (ctx_tx, mut ctx_rx) = tokio::sync::mpsc::channel::<Context>(1);
|
||||||
|
|
||||||
|
let discord_handler = DiscordHandler {
|
||||||
|
core_db,
|
||||||
|
discord_media_db,
|
||||||
|
chat_event_tx: tx,
|
||||||
|
ctx_tx,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Create a discord client
|
||||||
|
debug!("Logging in…");
|
||||||
|
let discord_token = std::env::var("PHOEBE_DISCORD_TOKEN")
|
||||||
|
.expect("PHOEBE_DISCORD_TOKEN environment variable was not set!");
|
||||||
|
let client = Client::builder(&discord_token)
|
||||||
|
.event_handler(discord_handler)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let discord_ctx = ctx_rx.recv().await.expect("Couldn't get Discord context");
|
||||||
|
debug!("Logged in!");
|
||||||
|
|
||||||
|
Ok(DiscordService {
|
||||||
|
discord_client: client,
|
||||||
|
discord_ctx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Service for DiscordService {
|
||||||
|
async fn handle_chat_event(&mut self, event: &ChatEvent) {
|
||||||
|
dbg!(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_service_tag(&self) -> &'static str {
|
||||||
|
"discord"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "phoebe-matrix"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
phoebe = { path = "../../phoebe" }
|
Loading…
Reference in New Issue