1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Move chat system into core, merge configs, save to database

This commit is contained in:
NG (Graham)
2025-05-23 16:46:12 -04:00
parent b83a23c115
commit 4f0bcde7d8
29 changed files with 362 additions and 331 deletions

View File

@@ -1,85 +0,0 @@
use polariton::operation::{Typed, Arr};
pub struct ChatChannelInfo {
pub channel_name: String,
pub members: Vec<ChatChannelMember>,
pub channel_ty: ChatChannelType,
}
impl ChatChannelInfo {
pub fn as_transmissible(&self) -> Typed {
Typed::HashMap(vec![
(Typed::Str("channelName".into()), Typed::Str(self.channel_name.clone().into())),
(Typed::Str("members".into()), Typed::Arr(Arr {
ty: polariton::serdes::TypePrefix::HashMap, // hashtable
items: self.members.iter().map(|x| x.as_transmissible()).collect(),
})),
(Typed::Str("channelType".into()), Typed::Int(self.channel_ty as _)),
].into())
}
}
pub struct ChatChannelMember {
pub name: String,
pub use_custom_avatar: bool,
pub state: ChatPlayerState,
pub custom_avatar: Vec<u8>, // always PNG?
pub avatar_id: i32,
}
impl ChatChannelMember {
pub fn as_transmissible(&self) -> Typed {
Typed::HashMap(vec![
(Typed::Str("name".into()), Typed::Str(self.name.clone().into())),
(Typed::Str("useCustomAvatar".into()), Typed::Bool(self.use_custom_avatar.into())),
(Typed::Str("state".into()), Typed::Int(self.state as _)),
if self.use_custom_avatar {
(Typed::Str("customAvatar".into()), Typed::Bytes(self.custom_avatar.clone().into()))
} else {
(Typed::Str("avatarId".into()), Typed::Int(self.avatar_id))
},
].into())
}
}
#[allow(dead_code)]
#[repr(u8)]
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
pub enum ChatChannelType {
None = 0,
Public = 1,
Battle = 2,
BattleTeam = 3,
Platoon = 4,
Custom = 5,
Clan = 6,
Private = 7,
CustomGame = 8,
}
impl ChatChannelType {
pub fn from_u8(num: u8) -> Result<Self, i16> {
match num {
0 => Ok(Self::None),
1 => Ok(Self::Public),
2 => Ok(Self::Battle),
3 => Ok(Self::BattleTeam),
4 => Ok(Self::Platoon),
5 => Ok(Self::Custom),
6 => Ok(Self::Clan),
7 => Ok(Self::Private),
8 => Ok(Self::CustomGame),
_ => Err(rc_core::data::error_codes::ChatErrorCodes::UnexpectedError as i16)
}
}
}
#[allow(dead_code)]
#[repr(u8)]
#[derive(Copy, Clone)]
pub enum ChatPlayerState {
Idk0 = 0,
Idk1 = 1,
Idk2 = 2,
// FIXME
}

View File

@@ -1 +1 @@
pub mod channel;
pub use rc_core::data::channel;

View File

@@ -1,7 +1,6 @@
#![forbid(unsafe_code)]
mod cli;
mod state;
mod persist;
mod op_handler;
pub use op_handler::SimpleChatFunc;
@@ -10,6 +9,7 @@ mod operations;
mod events;
use polariton_auth::Handshake;
use rc_core::ConfigProvider;
use tokio::net;
use polariton::packet::{Data, Message, Packet, StandardMessage};
@@ -26,9 +26,9 @@ async fn main() -> std::io::Result<()> {
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
let chat_system = state::chat::ChatImpl::new(&args.assets, &args.data).expect("Bad chat config data");
let chat_system = state::chat::ChatImpl::new(<rc_core::ConfigImpl as ConfigProvider<()>>::chat_system_config(&cubes)).expect("Bad chat config data");
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(chat_system, &args.data, &cubes), polariton_server::events::EventsHandler::new()));
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(chat_system, &cubes), polariton_server::events::EventsHandler::new()));
let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address");

View File

@@ -1,61 +1,35 @@
use crate::SimpleChatFunc;
use crate::persist::chat_user::ChatUser;
use polariton::operation::ParameterTable;
//use rc_core::persist::user::ChatUser;
use polariton::operation::{ParameterTable, OperationResponse};
const CODE: u8 = 11;
const PARAM_KEY: u8 = 18;
pub(super) fn all_channels_provider(chat_system: crate::state::ChatImpl) -> SimpleChatFunc<11, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy, &crate::state::ChatImpl) -> Result<ParameterTable, i16>) + Sync + Sync> {
SimpleChatFunc::new(|params, user: &crate::UserTy, _chat_system: &crate::state::ChatImpl| {
let mut params = params.to_dict();
let user_trait = user.user()?;
let chat_user = super::get_chat_user(user_trait.as_ref().as_ref());
//let chat_user: &ChatUserImpl = user_trait.ext(std::any::TypeId::of::<ChatUserImpl>()).unwrap().downcast_ref().unwrap();
params.insert(PARAM_KEY, chat_user.subscribed_channels());
/*params.insert(PARAM_KEY, Typed::Arr(Arr {
ty: polariton::serdes::TypePrefix::HashMap, // hashtable
items: vec![
ChatChannelInfo {
channel_name: "RE_public_channel0".to_owned(),
members: vec![
ChatChannelMember {
name: "RE_chat0_username0".to_owned(),
use_custom_avatar: false,
state: ChatPlayerState::Idk1,
custom_avatar: Vec::default(),
avatar_id: 2,
},
ChatChannelMember {
name: "RE_chat0_username1".to_owned(),
use_custom_avatar: false,
state: ChatPlayerState::Idk2,
custom_avatar: Vec::default(),
avatar_id: 3,
},
],
channel_ty: ChatChannelType::Public,
}.as_transmissible(),
ChatChannelInfo {
channel_name: "RE_custom_channel1".to_owned(),
members: vec![
ChatChannelMember {
name: "RE_chat1_username0".to_owned(),
use_custom_avatar: false,
state: ChatPlayerState::Idk0,
custom_avatar: Vec::default(),
avatar_id: 2,
},
ChatChannelMember {
name: "RE_chat1_username1".to_owned(),
use_custom_avatar: false,
state: ChatPlayerState::Idk1,
custom_avatar: Vec::default(),
avatar_id: 3,
},
],
channel_ty: ChatChannelType::Custom,
}.as_transmissible(),
],
}));*/
Ok(params.into())
}, chat_system)
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
let mut params = params.to_dict();
let user_info = user.user()?;
params.insert(PARAM_KEY, user_info.subscribed_channels().await?);
Ok(params.into())
}
pub struct JoinedChannelsProvider;
#[async_trait::async_trait]
impl polariton_server::operations::Operation<()> for JoinedChannelsProvider {
type User = crate::UserTy;
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user).await)
}
}
impl polariton_server::operations::OperationCode for JoinedChannelsProvider {
fn op_code() -> u8 {
CODE
}
}
pub(super) fn all_channels_provider() -> JoinedChannelsProvider {
JoinedChannelsProvider
}

View File

@@ -1,41 +1,85 @@
use crate::{persist::chat_user::ChatUser, SimpleChatFunc};
use polariton::operation::{ParameterTable, Typed};
use polariton::operation::{ParameterTable, Typed, OperationResponse};
const JOIN_CODE: u8 = 1;
const LEAVE_CODE: u8 = 5;
const CHANNEL_NAME_PARAM_KEY: u8 = 3; // str; in
const CHANNEL_TYPE_PARAM_KEY: u8 = 1; // int; in
const CHANNEL_PASSWORD_PARAM_KEY: u8 = 16; // str; in
const CHANNEL_INFO_PARAM_KEY: u8 = 17; // hashtable; out
pub(super) fn join_channel_provider(chat_system: crate::state::ChatImpl) -> SimpleChatFunc<1, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy, &crate::state::ChatImpl) -> Result<ParameterTable, i16>) + Sync + Sync> {
SimpleChatFunc::new(|params, user: &crate::UserTy, chat_system| {
let mut params = params.to_dict();
if let Some(Typed::Str(chann_name)) = params.remove(&CHANNEL_NAME_PARAM_KEY) {
if let Some(Typed::Int(chann_ty)) = params.remove(&CHANNEL_TYPE_PARAM_KEY) {
if let Some(Typed::Str(channel_pwd)) = params.remove(&CHANNEL_PASSWORD_PARAM_KEY) {
log::warn!("Received channel password {} which is unsupported", channel_pwd.string);
}
let user_info = user.user()?;
let chat_user = super::get_chat_user(user_info.as_ref().as_ref());
chat_system.system_mut().join_channel(user_info.token().uuid.clone(), chann_name.string.clone());
let response = chat_user.add_subscribed_channel(chann_name.string, crate::data::channel::ChatChannelType::from_u8(chann_ty as _)?);
params.insert(CHANNEL_INFO_PARAM_KEY, response);
}
}
Ok(params.into())
}, chat_system)
pub struct JoinChannelProvider {
chat_system: crate::state::ChatImpl,
}
pub(super) fn leave_channel_provider(chat_system: crate::state::ChatImpl) -> SimpleChatFunc<5, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy, &crate::state::ChatImpl) -> Result<ParameterTable, i16>) + Sync + Sync> {
SimpleChatFunc::new(|params, user: &crate::UserTy, chat_system| {
let mut params = params.to_dict();
if let Some(Typed::Str(chann_name)) = params.remove(&CHANNEL_NAME_PARAM_KEY) {
if let Some(Typed::Int(chann_ty)) = params.remove(&CHANNEL_TYPE_PARAM_KEY) {
let user_info = user.user()?;
let chat_user = super::get_chat_user(user_info.as_ref().as_ref());
chat_system.system_mut().leave_channel(user_info.token().uuid.clone(), chann_name.string.clone());
chat_user.remove_subscribed_channel(chann_name.string, crate::data::channel::ChatChannelType::from_u8(chann_ty as _)?);
pub(super) fn join_channel_provider(chat_system: crate::state::ChatImpl) -> JoinChannelProvider {
JoinChannelProvider { chat_system }
}
async fn do_join_handling(params: ParameterTable<()>, user: &crate::UserTy, chat_system: &crate::state::ChatImpl) -> Result<ParameterTable, i16> {
let mut params = params.to_dict();
if let Some(Typed::Str(chann_name)) = params.remove(&CHANNEL_NAME_PARAM_KEY) {
if let Some(Typed::Int(chann_ty)) = params.remove(&CHANNEL_TYPE_PARAM_KEY) {
if let Some(Typed::Str(channel_pwd)) = params.remove(&CHANNEL_PASSWORD_PARAM_KEY) {
log::warn!("Received channel password {} which is unsupported", channel_pwd.string);
}
let user_info = user.user()?;
//let chat_user = super::get_chat_user(user_info.as_ref().as_ref());
chat_system.system_mut().join_channel(user_info.token().uuid.clone(), chann_name.string.clone());
let response = user_info.add_subscribed_channel(chann_name.string, crate::data::channel::ChatChannelType::from_u8(chann_ty as _)?).await?;
params.insert(CHANNEL_INFO_PARAM_KEY, response);
}
Ok(params.into())
}, chat_system)
}
Ok(params.into())
}
#[async_trait::async_trait]
impl polariton_server::operations::Operation<()> for JoinChannelProvider {
type User = crate::UserTy;
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
polariton_server::operations::result_to_op_resp::<JOIN_CODE, ()>(do_join_handling(params, user, &self.chat_system).await)
}
}
impl polariton_server::operations::OperationCode for JoinChannelProvider {
fn op_code() -> u8 {
JOIN_CODE
}
}
pub struct LeaveChannelProvider {
chat_system: crate::state::ChatImpl,
}
pub(super) fn leave_channel_provider(chat_system: crate::state::ChatImpl) -> LeaveChannelProvider {
LeaveChannelProvider { chat_system }
}
async fn do_leave_handling(params: ParameterTable<()>, user: &crate::UserTy, chat_system: &crate::state::ChatImpl) -> Result<ParameterTable, i16> {
let mut params = params.to_dict();
if let Some(Typed::Str(chann_name)) = params.remove(&CHANNEL_NAME_PARAM_KEY) {
if let Some(Typed::Int(chann_ty)) = params.remove(&CHANNEL_TYPE_PARAM_KEY) {
let user_info = user.user()?;
//let chat_user = super::get_chat_user(user_info.as_ref().as_ref());
chat_system.system_mut().leave_channel(user_info.token().uuid.clone(), chann_name.string.clone());
user_info.remove_subscribed_channel(chann_name.string, crate::data::channel::ChatChannelType::from_u8(chann_ty as _)?).await?;
}
}
Ok(params.into())
}
#[async_trait::async_trait]
impl polariton_server::operations::Operation<()> for LeaveChannelProvider {
type User = crate::UserTy;
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
polariton_server::operations::result_to_op_resp::<LEAVE_CODE, ()>(do_leave_handling(params, user, &self.chat_system).await)
}
}
impl polariton_server::operations::OperationCode for LeaveChannelProvider {
fn op_code() -> u8 {
LEAVE_CODE
}
}

View File

@@ -6,26 +6,24 @@ mod send_message;
mod public_channels;
mod join_channel;
mod user_online;
mod subscribed_channels;
use polariton_server::operations::OperationsHandler;
pub fn handler(chat_system: crate::state::chat::ChatImpl, data_root: impl AsRef<std::path::Path>, conf: &rc_core::persist::config::ConfigImpl) -> OperationsHandler<crate::UserTy> {
pub fn handler(chat_system: crate::state::chat::ChatImpl, conf: &rc_core::persist::config::ConfigImpl) -> OperationsHandler<crate::UserTy> {
OperationsHandler::new()
.modify(rc_core::polariton::OpIdCopy)
.add(more_auth::MoreLobbyAuth::new(chat_system.clone(), data_root))
.add(more_auth::MoreLobbyAuth::new(chat_system.clone()))
.add(chat_ignores::ignores_provider())
.add(pending_sanctions::pending_sanctions_checker())
.add(all_joined_channels::all_channels_provider(chat_system.clone()))
.add(polariton_server::operations::Ack::<12, _>::default())
.add(all_joined_channels::all_channels_provider())
//.add(polariton_server::operations::Ack::<12, _>::default())
.add(send_message::send_public_message_handler(chat_system.clone()))
.add(public_channels::public_channels_provider(conf))
.add(join_channel::join_channel_provider(chat_system.clone()))
.add(user_online::is_online_provider(chat_system.clone()))
.add(send_message::send_private_message_handler(chat_system.clone()))
.add(join_channel::leave_channel_provider(chat_system.clone()))
.add(subscribed_channels::all_subbed_channels_provider())
//.add(polariton_server::operations::Ack::<00000, _>::default())
}
pub(self) fn get_chat_user<'a, C>(user: &'a dyn rc_core::persist::user::User<C>) -> &'a crate::persist::chat_user::ChatUserImpl {
user.ext(std::any::TypeId::of::<crate::persist::chat_user::ChatUserImpl>()).unwrap().downcast_ref().unwrap()
}

View File

@@ -1,24 +1,23 @@
use polariton::operation::Typed;
use polariton_server::operations::{Operation, OperationCode};
use crate::persist::chat_user::{ChatUser, ChatUserImpl};
//use crate::persist::chat_user::{ChatUser, ChatUserImpl};
//use rc_core::persist::user::ChatUser;
pub struct MoreLobbyAuth {
chat_system: crate::state::chat::ChatImpl,
root: std::path::PathBuf,
}
impl MoreLobbyAuth {
const AUTH_PAYLOAD_KEY: u8 = 245;
pub fn new(chat_system: crate::state::chat::ChatImpl, root: impl AsRef<std::path::Path>) -> Self {
pub fn new(chat_system: crate::state::chat::ChatImpl) -> Self {
Self {
chat_system,
root: root.as_ref().to_path_buf(),
}
}
fn build_ext_map(&self, token: &rc_core::persist::user::UserToken) -> Option<std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>> {
/*fn build_ext_map(&self, token: &rc_core::persist::user::UserToken) -> Option<std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>> {
let user_dir = self.root.join(rc_core::persist::user::USERS_DIR).join(&token.uuid);
let data = if let Ok(data) = ChatUserImpl::load(&user_dir) {
data
@@ -29,15 +28,15 @@ impl MoreLobbyAuth {
let mut map = std::collections::HashMap::with_capacity(1);
map.insert(std::any::TypeId::of::<ChatUserImpl>(), Box::new(data) as _);
Some(map)
}
}*/
async fn do_auth<C>(&self, params: std::collections::HashMap<u8, Typed<C>>, user: &crate::UserTy) -> Result<polariton::operation::ParameterTable<C>, i16> {
if let Some(Typed::Str(auth_payload)) = params.get(&Self::AUTH_PAYLOAD_KEY) {
if user.update_with_auth_ext(&auth_payload.string, |t| self.build_ext_map(t)).await {
if user.update_with_auth(&auth_payload.string).await {
let user_impl = user.user()?;
let name = user_impl.token().uuid.clone();
let chat_user = super::get_chat_user(user_impl.as_ref().as_ref());
let channels = chat_user.subscribed_channels_strings();
//let chat_user = super::get_chat_user(user_impl.as_ref().as_ref());
let channels = user_impl.subscribed_channels_strings().await?;
let event_tx = user.event_sender();
self.chat_system.system_mut().connect_user(name, channels, event_tx);
let mut resp_params = std::collections::HashMap::new();

View File

@@ -0,0 +1,35 @@
//use rc_core::persist::user::ChatUser;
use polariton::operation::{ParameterTable, OperationResponse};
const CODE: u8 = 12;
const PARAM_KEY: u8 = 18;
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
let mut params = params.to_dict();
let user_info = user.user()?;
params.insert(PARAM_KEY, user_info.subscribed_channels().await?);
Ok(params.into())
}
pub struct SubscribedChannelsProvider;
#[async_trait::async_trait]
impl polariton_server::operations::Operation<()> for SubscribedChannelsProvider {
type User = crate::UserTy;
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user).await)
}
}
impl polariton_server::operations::OperationCode for SubscribedChannelsProvider {
fn op_code() -> u8 {
CODE
}
}
pub(super) fn all_subbed_channels_provider() -> SubscribedChannelsProvider {
SubscribedChannelsProvider
}

View File

@@ -1,111 +0,0 @@
use serde::{Serialize, Deserialize};
use polariton::operation::{Typed, Arr};
use crate::data::channel::{ChatChannelInfo, ChatChannelType};
pub struct ChatUserInfo {
data: std::sync::RwLock<ChatUserData>,
root: std::path::PathBuf,
}
impl ChatUserInfo {
pub fn load(root: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
let data = ChatUserData::load(root.as_ref())?;
Ok(Self {
data: std::sync::RwLock::new(data),
root: root.as_ref().to_path_buf(),
})
}
/*pub fn save(&self) -> std::io::Result<()> {
self.data.read().unwrap().save(&self.root)
}*/
pub fn default_load(root: impl AsRef<std::path::Path>) -> Self {
let data = ChatUserData::default_load();
if let Err(e) = data.save(root.as_ref()) {
log::error!("Failed to save default chat data to {}: {}", root.as_ref().display(), e);
}
Self {
data: std::sync::RwLock::new(data),
root: root.as_ref().to_path_buf(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ChatUserData {
subscribed_channels: Vec<String>,
}
impl ChatUserData {
fn load(root: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
let file = std::fs::File::open(root.as_ref().join(super::CHAT_USER_FILE))?;
let buffered = std::io::BufReader::new(file);
let result = serde_json::from_reader(buffered)?;
Ok(result)
}
fn save(&self, root: impl AsRef<std::path::Path>) -> std::io::Result<()> {
let file = std::fs::File::create(root.as_ref().join(super::CHAT_USER_FILE))?;
let buffered = std::io::BufWriter::new(file);
serde_json::to_writer_pretty(buffered, self)?;
Ok(())
}
fn default_load() -> Self {
Self {
subscribed_channels: vec![
"main".to_owned(),
"sys".to_owned(),
]
}
}
}
impl super::ChatUser for ChatUserInfo {
fn subscribed_channels(&self) -> polariton::operation::Typed<()> {
Typed::Arr(Arr {
ty: polariton::serdes::TypePrefix::HashMap, // hashtable
items: self.data.read().unwrap().subscribed_channels.iter().map(|name| ChatChannelInfo {
channel_name: name.to_owned(),
members: Vec::default(),
channel_ty: ChatChannelType::Public,
}.as_transmissible()).collect()
})
}
fn subscribed_channels_strings(&self) -> Vec<String> {
self.data.read().unwrap().subscribed_channels.clone()
}
fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Typed<()> {
if let crate::data::channel::ChatChannelType::Public = channel_ty {
let mut lock = self.data.write().unwrap();
lock.subscribed_channels.push(channel.clone());
if let Err(e) = lock.save(&self.root) {
log::error!("Failed to save chat data to {}: {}", self.root.display(), e);
}
}
crate::data::channel::ChatChannelInfo {
channel_name: channel,
members: Vec::default(),
channel_ty,
}.as_transmissible()
}
fn remove_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> bool {
if let crate::data::channel::ChatChannelType::Public = channel_ty {
let mut lock = self.data.write().unwrap();
if let Some(index) = lock.subscribed_channels.iter().position(|chann| chann == &channel) {
lock.subscribed_channels.swap_remove(index);
if let Err(e) = lock.save(&self.root) {
log::error!("Failed to save chat data to {}: {}", self.root.display(), e);
} else {
return true;
}
}
}
false
}
}

View File

@@ -1,8 +0,0 @@
mod chat_json;
pub use chat_json::ChatUserInfo;
mod traits;
pub use traits::ChatUser;
pub const CHAT_USER_FILE: &str = "chat.json";
pub type ChatUserImpl = ChatUserInfo;

View File

@@ -1,8 +0,0 @@
use polariton::operation::Typed;
pub trait ChatUser {
fn subscribed_channels(&self) -> Typed<()>;
fn subscribed_channels_strings(&self) -> Vec<String>;
fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Typed<()>;
fn remove_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> bool;
}

View File

@@ -1,43 +0,0 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ChatSystemConfig {
#[serde(default = "default_command_chann")]
pub command_channel: String,
pub commands: Vec<ChatCommand>,
}
fn default_command_chann() -> String {
"sys".to_owned()
}
impl ChatSystemConfig {
pub fn load(asset_root: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
let file = std::fs::File::open(asset_root.as_ref().join(super::CHAT_CONFIG_FILE))?;
let buffered = std::io::BufReader::new(file);
let config = serde_json::from_reader(buffered)?;
Ok(config)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ChatCommand {
pub regex: String,
pub op: ChatOperation,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "type")]
pub enum ChatOperation {
BuiltIn(BuiltInChatOperation),
Custom,
#[serde(alias = "No-op")]
Nop,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "built_in")]
pub enum BuiltInChatOperation {
OnlineUsers,
TotalUsers,
}

View File

@@ -1,4 +0,0 @@
mod chat;
pub use chat::{ChatSystemConfig, ChatCommand, ChatOperation, BuiltInChatOperation};
pub const CHAT_CONFIG_FILE: &str = "chat.json";

View File

@@ -1,2 +0,0 @@
pub mod chat_user;
pub mod config;

View File

@@ -6,9 +6,9 @@ pub struct ChatProvider {
}
impl ChatProvider {
pub fn new(asset_root: impl AsRef<std::path::Path>, data_root: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
pub fn new(conf: rc_core::persist::config::ChatSystemConfig) -> std::io::Result<Self> {
Ok(Self {
chat_system: std::sync::Arc::new(std::sync::RwLock::new(crate::state::chat::ChatSystem::new(asset_root, data_root)?)),
chat_system: std::sync::Arc::new(std::sync::RwLock::new(crate::state::chat::ChatSystem::new(conf)?)),
})
}
@@ -161,12 +161,11 @@ impl ChatSystem {
handle.send_private_message(response);
}
pub fn new(asset_root: impl AsRef<std::path::Path>, data_root: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
let config_persist = crate::persist::config::ChatSystemConfig::load(&asset_root)?;
pub fn new(config: rc_core::persist::config::ChatSystemConfig) -> std::io::Result<Self> {
Ok(Self {
chats: HashMap::new(),
online_users: HashMap::new(),
config: super::ChatSystemConfig::from_persist(config_persist, asset_root.as_ref().to_path_buf(), data_root.as_ref().to_path_buf())?,
config: super::ChatSystemConfig::from_persist(config)?,
})
}

View File

@@ -1,21 +1,17 @@
pub struct ChatSystemConfig {
command_channel: String,
commands: Vec<ChatCommand>,
asset_root: std::path::PathBuf,
data_root: std::path::PathBuf,
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
struct CommandContext<'a, 'b, 'c> {
struct CommandContext<'a, 'b> {
chat_system: &'a super::ChatSystem,
user: &'b dyn rc_core::persist::user::User<()>,
asset_root: &'c std::path::PathBuf,
data_root: &'c std::path::PathBuf,
}
impl ChatSystemConfig {
pub fn from_persist(config: crate::persist::config::ChatSystemConfig, asset_root: std::path::PathBuf, data_root: std::path::PathBuf) -> std::io::Result<Self> {
pub fn from_persist(config: rc_core::persist::config::ChatSystemConfig) -> std::io::Result<Self> {
let mut compiled_commands = Vec::with_capacity(config.commands.len());
for (i, cmd) in config.commands.into_iter().enumerate() {
let compiled_command = ChatCommand::compile_command(cmd).map_err(|e| {
@@ -27,8 +23,6 @@ impl ChatSystemConfig {
Ok(Self {
command_channel: config.command_channel,
commands: compiled_commands,
asset_root,
data_root,
})
}
@@ -36,8 +30,6 @@ impl ChatSystemConfig {
let ctx = CommandContext {
chat_system,
user,
asset_root: &self.asset_root,
data_root: &self.data_root,
};
for cmd in self.commands.iter() {
if let Some(result) = cmd.perform_if_match(text, ctx) {
@@ -66,7 +58,7 @@ pub struct ChatCommand {
}
impl ChatCommand {
fn compile_command(command: crate::persist::config::ChatCommand) -> Result<Self, regex::Error> {
fn compile_command(command: rc_core::persist::ChatCommand) -> Result<Self, regex::Error> {
Ok(Self {
regex: regex::RegexBuilder::new(&command.regex).build()?,
op: ChatOperation::from_persist(command.op)
@@ -89,11 +81,11 @@ enum ChatOperation {
}
impl ChatOperation {
fn from_persist(op: crate::persist::config::ChatOperation) -> Self {
fn from_persist(op: rc_core::persist::ChatOperation) -> Self {
match op {
crate::persist::config::ChatOperation::BuiltIn(b_in) => Self::BuiltIn(BuiltIn::from_persist(b_in)),
crate::persist::config::ChatOperation::Custom => Self::Custom,
crate::persist::config::ChatOperation::Nop => Self::Nop,
rc_core::persist::ChatOperation::BuiltIn(b_in) => Self::BuiltIn(BuiltIn::from_persist(b_in)),
rc_core::persist::ChatOperation::Custom => Self::Custom,
rc_core::persist::ChatOperation::Nop => Self::Nop,
}
}
@@ -112,10 +104,10 @@ enum BuiltIn {
}
impl BuiltIn {
fn from_persist(b_in: crate::persist::config::BuiltInChatOperation) -> Self {
fn from_persist(b_in: rc_core::persist::BuiltInChatOperation) -> Self {
match b_in {
crate::persist::config::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers,
crate::persist::config::BuiltInChatOperation::TotalUsers => Self::TotalUsers,
rc_core::persist::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers,
rc_core::persist::BuiltInChatOperation::TotalUsers => Self::TotalUsers,
}
}
@@ -130,14 +122,7 @@ impl BuiltIn {
}
},
Self::TotalUsers => {
let user_path = ctx.data_root.join(rc_core::persist::user::USERS_DIR);
let user_count = user_path.read_dir().map_or(0, |dir| dir.count()).clamp(1, usize::MAX) - 1;
if user_count == 1 {
"1 user exists".to_owned()
} else {
format!("{} users exist", user_count)
}
format!("User count is not supported")
},
}
}