Initial commit
This commit is contained in:
commit
fbc6270a0a
23 changed files with 293 additions and 0 deletions
12
.editorconfig
Normal file
12
.editorconfig
Normal file
|
@ -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
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"phoebe",
|
||||
"phoebe-main",
|
||||
"mid-chat",
|
||||
"services/*"
|
||||
]
|
11
README.md
Normal file
11
README.md
Normal file
|
@ -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
|
2
data/.gitignore
vendored
Normal file
2
data/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!/.gitignore
|
6
mid-chat/Cargo.toml
Normal file
6
mid-chat/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "mid-chat"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
30
mid-chat/src/content.rs
Normal file
30
mid-chat/src/content.rs
Normal file
|
@ -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),
|
||||
}
|
6
mid-chat/src/event.rs
Normal file
6
mid-chat/src/event.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use crate::ChatMessage;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ChatEvent {
|
||||
NewMessage(ChatMessage),
|
||||
}
|
22
mid-chat/src/lib.rs
Normal file
22
mid-chat/src/lib.rs
Normal file
|
@ -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;
|
5
mid-chat/src/reference.rs
Normal file
5
mid-chat/src/reference.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct ChatReference {
|
||||
pub service: &'static str,
|
||||
pub id: String,
|
||||
}
|
11
phoebe-main/Cargo.toml
Normal file
11
phoebe-main/Cargo.toml
Normal file
|
@ -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" }
|
23
phoebe-main/src/main.rs
Normal file
23
phoebe-main/src/main.rs
Normal file
|
@ -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(())
|
||||
}
|
1
phoebe/.env
Normal file
1
phoebe/.env
Normal file
|
@ -0,0 +1 @@
|
|||
DATABASE_URL="sqlite://PHOEBE_DB_ROOT/main.db"
|
13
phoebe/Cargo.toml
Normal file
13
phoebe/Cargo.toml
Normal file
|
@ -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"
|
26
phoebe/src/db.rs
Normal file
26
phoebe/src/db.rs
Normal file
|
@ -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
|
||||
}
|
14
phoebe/src/lib.rs
Normal file
14
phoebe/src/lib.rs
Normal file
|
@ -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)
|
||||
}
|
6
phoebe/src/prelude.rs
Normal file
6
phoebe/src/prelude.rs
Normal file
|
@ -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;
|
7
phoebe/src/service.rs
Normal file
7
phoebe/src/service.rs
Normal file
|
@ -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);
|
||||
}
|
1
services/phoebe-discord/.env
Normal file
1
services/phoebe-discord/.env
Normal file
|
@ -0,0 +1 @@
|
|||
DATABASE_URL="sqlite://PHOEBE_DB_ROOT/discord_media.db"
|
11
services/phoebe-discord/Cargo.toml
Normal file
11
services/phoebe-discord/Cargo.toml
Normal file
|
@ -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"] }
|
70
services/phoebe-discord/src/lib.rs
Normal file
70
services/phoebe-discord/src/lib.rs
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
7
services/phoebe-matrix/Cargo.toml
Normal file
7
services/phoebe-matrix/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "phoebe-matrix"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
phoebe = { path = "../../phoebe" }
|
0
services/phoebe-matrix/src/lib.rs
Normal file
0
services/phoebe-matrix/src/lib.rs
Normal file
Loading…
Reference in a new issue