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

@@ -14447,6 +14447,22 @@
"main", "main",
"sys", "sys",
"jam_club" "jam_club"
],
"commands": [
{
"regex": "\\?online",
"op": {
"type": "BuiltIn",
"built_in": "OnlineUsers"
}
},
{
"regex": "\\?users",
"op": {
"type": "BuiltIn",
"built_in": "TotalUsers"
}
}
] ]
}, },
"factory": { "factory": {
@@ -14515,4 +14531,4 @@
} }
] ]
} }
} }

View File

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

View File

@@ -1,7 +1,6 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
mod cli; mod cli;
mod state; mod state;
mod persist;
mod op_handler; mod op_handler;
pub use op_handler::SimpleChatFunc; pub use op_handler::SimpleChatFunc;
@@ -10,6 +9,7 @@ mod operations;
mod events; mod events;
use polariton_auth::Handshake; use polariton_auth::Handshake;
use rc_core::ConfigProvider;
use tokio::net; use tokio::net;
use polariton::packet::{Data, Message, Packet, StandardMessage}; 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 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 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"); let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address");

View File

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

View File

@@ -1,41 +1,85 @@
use crate::{persist::chat_user::ChatUser, SimpleChatFunc}; use polariton::operation::{ParameterTable, Typed, OperationResponse};
use polariton::operation::{ParameterTable, Typed};
const JOIN_CODE: u8 = 1;
const LEAVE_CODE: u8 = 5;
const CHANNEL_NAME_PARAM_KEY: u8 = 3; // str; in const CHANNEL_NAME_PARAM_KEY: u8 = 3; // str; in
const CHANNEL_TYPE_PARAM_KEY: u8 = 1; // int; in const CHANNEL_TYPE_PARAM_KEY: u8 = 1; // int; in
const CHANNEL_PASSWORD_PARAM_KEY: u8 = 16; // str; in const CHANNEL_PASSWORD_PARAM_KEY: u8 = 16; // str; in
const CHANNEL_INFO_PARAM_KEY: u8 = 17; // hashtable; out 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> { pub struct JoinChannelProvider {
SimpleChatFunc::new(|params, user: &crate::UserTy, chat_system| { chat_system: crate::state::ChatImpl,
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(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> { pub(super) fn join_channel_provider(chat_system: crate::state::ChatImpl) -> JoinChannelProvider {
SimpleChatFunc::new(|params, user: &crate::UserTy, chat_system| { JoinChannelProvider { 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) { async fn do_join_handling(params: ParameterTable<()>, user: &crate::UserTy, chat_system: &crate::state::ChatImpl) -> Result<ParameterTable, i16> {
let user_info = user.user()?; let mut params = params.to_dict();
let chat_user = super::get_chat_user(user_info.as_ref().as_ref()); if let Some(Typed::Str(chann_name)) = params.remove(&CHANNEL_NAME_PARAM_KEY) {
chat_system.system_mut().leave_channel(user_info.token().uuid.clone(), chann_name.string.clone()); if let Some(Typed::Int(chann_ty)) = params.remove(&CHANNEL_TYPE_PARAM_KEY) {
chat_user.remove_subscribed_channel(chann_name.string, crate::data::channel::ChatChannelType::from_u8(chann_ty as _)?); 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 public_channels;
mod join_channel; mod join_channel;
mod user_online; mod user_online;
mod subscribed_channels;
use polariton_server::operations::OperationsHandler; 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() OperationsHandler::new()
.modify(rc_core::polariton::OpIdCopy) .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(chat_ignores::ignores_provider())
.add(pending_sanctions::pending_sanctions_checker()) .add(pending_sanctions::pending_sanctions_checker())
.add(all_joined_channels::all_channels_provider(chat_system.clone())) .add(all_joined_channels::all_channels_provider())
.add(polariton_server::operations::Ack::<12, _>::default()) //.add(polariton_server::operations::Ack::<12, _>::default())
.add(send_message::send_public_message_handler(chat_system.clone())) .add(send_message::send_public_message_handler(chat_system.clone()))
.add(public_channels::public_channels_provider(conf)) .add(public_channels::public_channels_provider(conf))
.add(join_channel::join_channel_provider(chat_system.clone())) .add(join_channel::join_channel_provider(chat_system.clone()))
.add(user_online::is_online_provider(chat_system.clone())) .add(user_online::is_online_provider(chat_system.clone()))
.add(send_message::send_private_message_handler(chat_system.clone())) .add(send_message::send_private_message_handler(chat_system.clone()))
.add(join_channel::leave_channel_provider(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()) //.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::operation::Typed;
use polariton_server::operations::{Operation, OperationCode}; 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 { pub struct MoreLobbyAuth {
chat_system: crate::state::chat::ChatImpl, chat_system: crate::state::chat::ChatImpl,
root: std::path::PathBuf,
} }
impl MoreLobbyAuth { impl MoreLobbyAuth {
const AUTH_PAYLOAD_KEY: u8 = 245; 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 { Self {
chat_system, 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 user_dir = self.root.join(rc_core::persist::user::USERS_DIR).join(&token.uuid);
let data = if let Ok(data) = ChatUserImpl::load(&user_dir) { let data = if let Ok(data) = ChatUserImpl::load(&user_dir) {
data data
@@ -29,15 +28,15 @@ impl MoreLobbyAuth {
let mut map = std::collections::HashMap::with_capacity(1); let mut map = std::collections::HashMap::with_capacity(1);
map.insert(std::any::TypeId::of::<ChatUserImpl>(), Box::new(data) as _); map.insert(std::any::TypeId::of::<ChatUserImpl>(), Box::new(data) as _);
Some(map) Some(map)
} }*/
async fn do_auth<C>(&self, params: std::collections::HashMap<u8, Typed<C>>, user: &crate::UserTy) -> Result<polariton::operation::ParameterTable<C>, i16> { 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 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 user_impl = user.user()?;
let name = user_impl.token().uuid.clone(); let name = user_impl.token().uuid.clone();
let chat_user = super::get_chat_user(user_impl.as_ref().as_ref()); //let chat_user = super::get_chat_user(user_impl.as_ref().as_ref());
let channels = chat_user.subscribed_channels_strings(); let channels = user_impl.subscribed_channels_strings().await?;
let event_tx = user.event_sender(); let event_tx = user.event_sender();
self.chat_system.system_mut().connect_user(name, channels, event_tx); self.chat_system.system_mut().connect_user(name, channels, event_tx);
let mut resp_params = std::collections::HashMap::new(); 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 { 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 { 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); 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> { pub fn new(config: rc_core::persist::config::ChatSystemConfig) -> std::io::Result<Self> {
let config_persist = crate::persist::config::ChatSystemConfig::load(&asset_root)?;
Ok(Self { Ok(Self {
chats: HashMap::new(), chats: HashMap::new(),
online_users: 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 { pub struct ChatSystemConfig {
command_channel: String, command_channel: String,
commands: Vec<ChatCommand>, commands: Vec<ChatCommand>,
asset_root: std::path::PathBuf,
data_root: std::path::PathBuf,
} }
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct CommandContext<'a, 'b, 'c> { struct CommandContext<'a, 'b> {
chat_system: &'a super::ChatSystem, chat_system: &'a super::ChatSystem,
user: &'b dyn rc_core::persist::user::User<()>, user: &'b dyn rc_core::persist::user::User<()>,
asset_root: &'c std::path::PathBuf,
data_root: &'c std::path::PathBuf,
} }
impl ChatSystemConfig { 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()); let mut compiled_commands = Vec::with_capacity(config.commands.len());
for (i, cmd) in config.commands.into_iter().enumerate() { for (i, cmd) in config.commands.into_iter().enumerate() {
let compiled_command = ChatCommand::compile_command(cmd).map_err(|e| { let compiled_command = ChatCommand::compile_command(cmd).map_err(|e| {
@@ -27,8 +23,6 @@ impl ChatSystemConfig {
Ok(Self { Ok(Self {
command_channel: config.command_channel, command_channel: config.command_channel,
commands: compiled_commands, commands: compiled_commands,
asset_root,
data_root,
}) })
} }
@@ -36,8 +30,6 @@ impl ChatSystemConfig {
let ctx = CommandContext { let ctx = CommandContext {
chat_system, chat_system,
user, user,
asset_root: &self.asset_root,
data_root: &self.data_root,
}; };
for cmd in self.commands.iter() { for cmd in self.commands.iter() {
if let Some(result) = cmd.perform_if_match(text, ctx) { if let Some(result) = cmd.perform_if_match(text, ctx) {
@@ -66,7 +58,7 @@ pub struct ChatCommand {
} }
impl 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 { Ok(Self {
regex: regex::RegexBuilder::new(&command.regex).build()?, regex: regex::RegexBuilder::new(&command.regex).build()?,
op: ChatOperation::from_persist(command.op) op: ChatOperation::from_persist(command.op)
@@ -89,11 +81,11 @@ enum ChatOperation {
} }
impl ChatOperation { impl ChatOperation {
fn from_persist(op: crate::persist::config::ChatOperation) -> Self { fn from_persist(op: rc_core::persist::ChatOperation) -> Self {
match op { match op {
crate::persist::config::ChatOperation::BuiltIn(b_in) => Self::BuiltIn(BuiltIn::from_persist(b_in)), rc_core::persist::ChatOperation::BuiltIn(b_in) => Self::BuiltIn(BuiltIn::from_persist(b_in)),
crate::persist::config::ChatOperation::Custom => Self::Custom, rc_core::persist::ChatOperation::Custom => Self::Custom,
crate::persist::config::ChatOperation::Nop => Self::Nop, rc_core::persist::ChatOperation::Nop => Self::Nop,
} }
} }
@@ -112,10 +104,10 @@ enum BuiltIn {
} }
impl 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 { match b_in {
crate::persist::config::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers, rc_core::persist::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers,
crate::persist::config::BuiltInChatOperation::TotalUsers => Self::TotalUsers, rc_core::persist::BuiltInChatOperation::TotalUsers => Self::TotalUsers,
} }
} }
@@ -130,14 +122,7 @@ impl BuiltIn {
} }
}, },
Self::TotalUsers => { Self::TotalUsers => {
let user_path = ctx.data_root.join(rc_core::persist::user::USERS_DIR); format!("User count is not supported")
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)
}
}, },
} }
} }

View File

@@ -69,7 +69,7 @@ impl ChatChannelType {
6 => Ok(Self::Clan), 6 => Ok(Self::Clan),
7 => Ok(Self::Private), 7 => Ok(Self::Private),
8 => Ok(Self::CustomGame), 8 => Ok(Self::CustomGame),
_ => Err(rc_core::data::error_codes::ChatErrorCodes::UnexpectedError as i16) _ => Err(crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16)
} }
} }
} }

View File

@@ -11,6 +11,7 @@ pub mod voting;
pub mod weapon_list; pub mod weapon_list;
pub mod weapon_upgrade; pub mod weapon_upgrade;
pub mod crf; pub mod crf;
pub mod channel;
pub mod error_codes; pub mod error_codes;

View File

@@ -4,8 +4,34 @@ use serde::{Serialize, Deserialize};
pub struct ChatConfig { pub struct ChatConfig {
#[serde(default = "default_pub_channs")] #[serde(default = "default_pub_channs")]
pub public_channels: Vec<String>, pub public_channels: Vec<String>,
#[serde(default = "default_command_chann")]
pub command_channel: String,
pub commands: Vec<ChatCommand>,
} }
#[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,
}
fn default_pub_channs() -> Vec<String> { fn default_pub_channs() -> Vec<String> {
vec![ vec![
"main".to_owned(), "main".to_owned(),
@@ -13,3 +39,8 @@ fn default_pub_channs() -> Vec<String> {
"openjam_worship".to_owned(), "openjam_worship".to_owned(),
] ]
} }
fn default_command_chann() -> String {
"sys".to_owned()
}

View File

@@ -281,4 +281,11 @@ impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
fn cubes(&self) -> &'_ std::collections::HashMap<String, crate::persist::Cube> { fn cubes(&self) -> &'_ std::collections::HashMap<String, crate::persist::Cube> {
&self.cubes &self.cubes
} }
fn chat_system_config(&self) -> super::ChatSystemConfig {
super::ChatSystemConfig {
command_channel: self.chat.command_channel.clone(),
commands: self.chat.commands.clone(),
}
}
} }

View File

@@ -2,7 +2,7 @@ mod cubes_json;
pub use cubes_json::CubeConfig; pub use cubes_json::CubeConfig;
mod traits; mod traits;
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement}; pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig};
pub type ConfigImpl = CubeConfig; pub type ConfigImpl = CubeConfig;

View File

@@ -23,6 +23,7 @@ pub trait ConfigProvider<C: Clone> {
fn garage_upgrades(&self) -> GarageUpgrades; fn garage_upgrades(&self) -> GarageUpgrades;
async fn factory(&self) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>>; async fn factory(&self) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>>;
fn cubes(&self) -> &'_ std::collections::HashMap<String, crate::persist::Cube>; fn cubes(&self) -> &'_ std::collections::HashMap<String, crate::persist::Cube>;
fn chat_system_config(&self) -> ChatSystemConfig;
} }
pub struct CompleteCampaignProvider { pub struct CompleteCampaignProvider {
@@ -121,3 +122,9 @@ impl GarageUpgrades {
].into()) ].into())
} }
} }
#[derive(Clone, Debug)]
pub struct ChatSystemConfig {
pub command_channel: String,
pub commands: Vec<crate::persist::ChatCommand>,
}

View File

@@ -30,7 +30,7 @@ mod settings;
pub use settings::Settings; pub use settings::Settings;
mod chat; mod chat;
pub use chat::ChatConfig; pub use chat::{ChatConfig, ChatCommand, ChatOperation, BuiltInChatOperation};
mod vehicle_factory; mod vehicle_factory;
pub use vehicle_factory::{FactoryConfig, AdapterSettings, ArcFactorySettings}; pub use vehicle_factory::{FactoryConfig, AdapterSettings, ArcFactorySettings};

View File

@@ -527,3 +527,78 @@ impl <C: Clone> super::User<C> for UserData {
} }
} }
} }
#[async_trait::async_trait]
impl super::ChatUser for UserData {
async fn subscribed_channels(&self) -> Result<polariton::operation::Typed<()>, i16> {
let channels = self.subscribed_channels_strings().await?;
Ok(polariton::operation::Typed::Arr(polariton::operation::Arr {
ty: polariton::serdes::TypePrefix::HashMap, // hashtable
items: channels.iter().map(|name| crate::data::channel::ChatChannelInfo {
channel_name: name.to_owned(),
members: Vec::default(),
channel_ty: crate::data::channel::ChatChannelType::Public,
}.as_transmissible()).collect()
}))
}
async fn subscribed_channels_strings(&self) -> Result<Vec<String>, i16> {
let channels = self.db.user_aux_by_user_id_and_descriptor(self.account.id, rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| {
log::error!("Failed to retrieve SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e);
crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16
})?.ok_or_else(|| {
log::error!("Failed to find SubscribedChannels (user_aux) for user_id {}", self.account.id);
crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16
})?;
let channels = serde_json::from_str::<Vec<String>>(&channels.data).map_err(|e| {
log::error!("Failed to parse SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e);
crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16
})?;
Ok(channels)
}
async fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<polariton::operation::Typed<()>, i16> {
if let crate::data::channel::ChatChannelType::Public = channel_ty {
let mut sub_channels = self.subscribed_channels_strings().await?;
sub_channels.push(channel.clone());
let new_data = serde_json::to_string(&sub_channels).map_err(|e| {
log::error!("Failed to convert to JSON SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e);
crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16
})?;
self.db.update_user_aux_by_user_id_and_descriptor(rc_database::schema::user_aux::ActiveModel {
data: rc_database::sea_orm::ActiveValue::Set(new_data),
..Default::default()
}, self.account.id, rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| {
log::error!("Failed to update SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e);
crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16
})?;
}
Ok(crate::data::channel::ChatChannelInfo {
channel_name: channel,
members: Vec::default(),
channel_ty,
}.as_transmissible())
}
async fn remove_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<(), i16> {
if let crate::data::channel::ChatChannelType::Public = channel_ty {
let mut sub_channels = self.subscribed_channels_strings().await?;
if let Some(index) = sub_channels.iter().position(|chann| chann == &channel) {
sub_channels.swap_remove(index);
let new_data = serde_json::to_string(&sub_channels).map_err(|e| {
log::error!("Failed to convert to JSON SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e);
crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16
})?;
self.db.update_user_aux_by_user_id_and_descriptor(rc_database::schema::user_aux::ActiveModel {
data: rc_database::sea_orm::ActiveValue::Set(new_data),
..Default::default()
}, self.account.id, rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| {
log::error!("Failed to update SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e);
crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16
})?;
}
}
Ok(())
}
}

View File

@@ -69,25 +69,26 @@ fn default_user_data(info: &super::RegistrationInfo) -> rc_database::schema::use
} }
fn default_user_aux_data(user_id: u32) -> Vec<rc_database::schema::user_aux::ActiveModel> { fn default_user_aux_data(user_id: u32) -> Vec<rc_database::schema::user_aux::ActiveModel> {
let current_time = current_unix_time();
vec![ vec![
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserXP), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserXP),
data: rc_database::sea_orm::ActiveValue::Set("0".to_owned()), data: rc_database::sea_orm::ActiveValue::Set("0".to_owned()),
}, },
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::PremiumExpiry), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::PremiumExpiry),
data: rc_database::sea_orm::ActiveValue::Set(current_unix_time().to_string()), data: rc_database::sea_orm::ActiveValue::Set(current_time.to_string()),
}, },
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UnlockedParts), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UnlockedParts),
data: rc_database::sea_orm::ActiveValue::Set( data: rc_database::sea_orm::ActiveValue::Set(
r#"{ r#"{
@@ -98,37 +99,44 @@ r#"{
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::TechPoints), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::TechPoints),
data: rc_database::sea_orm::ActiveValue::Set("1337".to_owned()), data: rc_database::sea_orm::ActiveValue::Set("1337".to_owned()),
}, },
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserRank), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserRank),
data: rc_database::sea_orm::ActiveValue::Set("1".to_owned()), data: rc_database::sea_orm::ActiveValue::Set("1".to_owned()),
}, },
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserFreeCurrency), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserFreeCurrency),
data: rc_database::sea_orm::ActiveValue::Set("10000".to_owned()), data: rc_database::sea_orm::ActiveValue::Set("10000".to_owned()),
}, },
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserPaidCurrency), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserPaidCurrency),
data: rc_database::sea_orm::ActiveValue::Set("1000".to_owned()), data: rc_database::sea_orm::ActiveValue::Set("1000".to_owned()),
}, },
rc_database::schema::user_aux::ActiveModel { rc_database::schema::user_aux::ActiveModel {
id: Default::default(), id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id), user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()), creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::GarageSlotOrder), descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::GarageSlotOrder),
data: rc_database::sea_orm::ActiveValue::Set("[0]".to_owned()), data: rc_database::sea_orm::ActiveValue::Set("[0]".to_owned()),
},
rc_database::schema::user_aux::ActiveModel {
id: Default::default(),
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::SubscribedChannels),
data: rc_database::sea_orm::ActiveValue::Set("[\"sys\"]".to_owned()),
} }
] ]
} }

View File

@@ -11,7 +11,7 @@ mod inventory;
pub use inventory::UnlockedParts; pub use inventory::UnlockedParts;
mod traits; mod traits;
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData}; pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser};
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key"; pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";

View File

@@ -54,7 +54,7 @@ pub trait UserAuthenticator {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait User<C> { pub trait User<C>: ChatUser {
fn ext(&self, ty: std::any::TypeId) -> Option<&'_ (dyn std::any::Any + Send + Sync + 'static)>; fn ext(&self, ty: std::any::TypeId) -> Option<&'_ (dyn std::any::Any + Send + Sync + 'static)>;
fn token(&self) -> &'_ super::UserToken; fn token(&self) -> &'_ super::UserToken;
fn is_mod(&self) -> bool; fn is_mod(&self) -> bool;
@@ -121,3 +121,14 @@ pub struct VehicleUploadData {
pub description: String, pub description: String,
pub thumbnail: Vec<u8>, pub thumbnail: Vec<u8>,
} }
use polariton::operation::Typed;
#[async_trait::async_trait]
pub trait ChatUser {
async fn subscribed_channels(&self) -> Result<Typed<()>, i16>;
async fn subscribed_channels_strings(&self) -> Result<Vec<String>, i16>;
async fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<Typed<()>, i16>;
async fn remove_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<(), i16>;
}

View File

@@ -39,6 +39,7 @@ pub enum Descriptor {
UserRank, // u32 UserRank, // u32
UserFreeCurrency, // u64 UserFreeCurrency, // u64
UserPaidCurrency, // u64 UserPaidCurrency, // u64
GarageSlotOrder, // Vec<u32>, GarageSlotOrder, // Vec<u32>, CSV
LastSeen, // u64, seconds since Unix epoch LastSeen, // u64, seconds since Unix epoch
SubscribedChannels, // Vec<String>, JSON
} }

View File

@@ -454,6 +454,22 @@ def main(asset_in, cubes=None, weapons=None, movement=None):
"sys", "sys",
"jam_club", "jam_club",
], ],
"commands": [
{
"regex": "\\?online",
"op": {
"type": "BuiltIn",
"built_in": "OnlineUsers"
}
},
{
"regex": "\\?users",
"op": {
"type": "BuiltIn",
"built_in": "TotalUsers"
}
}
]
}, },
"factory": { "factory": {
"adapter": { "adapter": {