restructure and manage own deps
This commit is contained in:
30
src/CMakeLists.txt
Normal file
30
src/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
add_library(solanaceae_message3
|
||||
./solanaceae/message3/file.hpp
|
||||
./solanaceae/message3/file_r_mem.hpp
|
||||
./solanaceae/message3/file_r_file.hpp
|
||||
./solanaceae/message3/file_w_file.hpp
|
||||
./solanaceae/message3/file_rw_file.hpp
|
||||
|
||||
./solanaceae/message3/components.hpp
|
||||
./solanaceae/message3/components_id.inl
|
||||
|
||||
./solanaceae/message3/message_model3.hpp
|
||||
|
||||
./solanaceae/message3/registry_message_model.hpp
|
||||
./solanaceae/message3/registry_message_model.cpp
|
||||
|
||||
./solanaceae/message3/message_time_sort.hpp
|
||||
./solanaceae/message3/message_time_sort.cpp
|
||||
|
||||
./solanaceae/message3/message_command_dispatcher.hpp
|
||||
./solanaceae/message3/message_command_dispatcher.cpp
|
||||
)
|
||||
|
||||
target_include_directories(solanaceae_message3 PUBLIC .)
|
||||
target_compile_features(solanaceae_message3 PUBLIC cxx_std_17)
|
||||
target_link_libraries(solanaceae_message3 PUBLIC
|
||||
solanaceae_util
|
||||
solanaceae_contact
|
||||
EnTT::EnTT
|
||||
)
|
||||
|
156
src/solanaceae/message3/components.hpp
Normal file
156
src/solanaceae/message3/components.hpp
Normal file
@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/contact/contact_model3.hpp>
|
||||
|
||||
//fwd
|
||||
struct FileI;
|
||||
|
||||
#include <entt/container/dense_map.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Message::Components {
|
||||
|
||||
struct ContactFrom {
|
||||
Contact3 c;
|
||||
};
|
||||
|
||||
struct ContactTo {
|
||||
Contact3 c;
|
||||
};
|
||||
|
||||
// best guess as to how this should be displayed in the global order
|
||||
struct Timestamp {
|
||||
uint64_t ts {0};
|
||||
};
|
||||
|
||||
struct TimestampWritten {
|
||||
uint64_t ts {0};
|
||||
};
|
||||
|
||||
// local start TODO: namespace ?
|
||||
struct TimestampProcessed {
|
||||
uint64_t ts {0};
|
||||
};
|
||||
|
||||
struct TagUnread {};
|
||||
|
||||
struct Read {
|
||||
// TODO: too much?
|
||||
uint64_t ts {0};
|
||||
};
|
||||
// local end
|
||||
|
||||
namespace Remote {
|
||||
// TODO: milliseconds sound excessive
|
||||
|
||||
struct TimestampReceived {
|
||||
// Due to a lack of info with most protocols,
|
||||
// this is often the timestamp we heard they already have the message.
|
||||
entt::dense_map<Contact3, uint64_t> ts;
|
||||
};
|
||||
|
||||
struct TimestampRead {
|
||||
// Due to a lack of info with most protocols,
|
||||
// this is often the timestamp we heard they have read it the message.
|
||||
entt::dense_map<Contact3, uint64_t> ts;
|
||||
};
|
||||
|
||||
} // Remote
|
||||
|
||||
struct SyncedBy {
|
||||
// ts is not updated once set
|
||||
entt::dense_map<Contact3, uint64_t> ts;
|
||||
};
|
||||
|
||||
struct MessageText {
|
||||
std::string text;
|
||||
MessageText(const std::string_view& view) : text(view) {}
|
||||
};
|
||||
|
||||
struct TagMessageIsAction {};
|
||||
|
||||
namespace Transfer {
|
||||
|
||||
//struct TransferState {
|
||||
//enum State {
|
||||
//running,
|
||||
//paused,
|
||||
//finished,
|
||||
//failed,
|
||||
//} state = paused;
|
||||
//};
|
||||
struct TagHaveAll {};
|
||||
|
||||
struct BytesSent {
|
||||
uint64_t total {0u};
|
||||
};
|
||||
|
||||
struct BytesReceived {
|
||||
uint64_t total {0u};
|
||||
};
|
||||
|
||||
// TODO: rename to stream?
|
||||
using File = std::unique_ptr<FileI>;
|
||||
|
||||
struct TagReceiving {};
|
||||
struct TagSending {};
|
||||
// TODO: add both?
|
||||
|
||||
// convert to enum?
|
||||
// TODO: local/remote
|
||||
// TODO: invert?
|
||||
struct TagPaused {};
|
||||
|
||||
struct StateCanceled {
|
||||
enum Reason {
|
||||
disconnected,
|
||||
remote_canceld,
|
||||
local_canceld,
|
||||
} reason = disconnected;
|
||||
};
|
||||
|
||||
#if 0
|
||||
struct FileID {
|
||||
// persistent ID
|
||||
// sometimes called file_id or hash
|
||||
ToxKey id;
|
||||
// TODO: variable length
|
||||
};
|
||||
|
||||
struct FileKind {
|
||||
// TODO: use tox file kind
|
||||
uint64_t kind {0};
|
||||
};
|
||||
#endif
|
||||
|
||||
struct FileInfo {
|
||||
struct FileDirEntry {
|
||||
std::string file_name; // full path relative to base
|
||||
uint64_t file_size {0};
|
||||
};
|
||||
std::vector<FileDirEntry> file_list;
|
||||
uint64_t total_size {0};
|
||||
};
|
||||
|
||||
// describes the files locally
|
||||
// filename might be different to non local FileInfo
|
||||
// order is the same
|
||||
struct FileInfoLocal {
|
||||
std::vector<std::string> file_list;
|
||||
};
|
||||
|
||||
// TODO: rename to start? or set or ...
|
||||
struct ActionAccept {
|
||||
std::string save_to_path;
|
||||
bool path_is_file = false; // if the path is not the folder to place the file into, overwrites the name
|
||||
};
|
||||
|
||||
} // Transfer
|
||||
|
||||
} // Message::Components
|
||||
|
||||
#include "./components_id.inl"
|
||||
|
41
src/solanaceae/message3/components_id.inl
Normal file
41
src/solanaceae/message3/components_id.inl
Normal file
@ -0,0 +1,41 @@
|
||||
#include "./components.hpp"
|
||||
|
||||
#include <entt/core/type_info.hpp>
|
||||
|
||||
// TODO: move more central
|
||||
#define DEFINE_COMP_ID(x) \
|
||||
template<> \
|
||||
constexpr entt::id_type entt::type_hash<x>::value() noexcept { \
|
||||
using namespace entt::literals; \
|
||||
return #x##_hs; \
|
||||
}
|
||||
|
||||
// cross compile(r) stable ids
|
||||
|
||||
DEFINE_COMP_ID(Message::Components::ContactFrom)
|
||||
DEFINE_COMP_ID(Message::Components::ContactTo)
|
||||
DEFINE_COMP_ID(Message::Components::Timestamp)
|
||||
DEFINE_COMP_ID(Message::Components::TimestampProcessed)
|
||||
DEFINE_COMP_ID(Message::Components::TimestampWritten)
|
||||
DEFINE_COMP_ID(Message::Components::TagUnread)
|
||||
DEFINE_COMP_ID(Message::Components::Read)
|
||||
DEFINE_COMP_ID(Message::Components::Remote::TimestampReceived)
|
||||
DEFINE_COMP_ID(Message::Components::Remote::TimestampRead)
|
||||
DEFINE_COMP_ID(Message::Components::SyncedBy)
|
||||
DEFINE_COMP_ID(Message::Components::MessageText)
|
||||
DEFINE_COMP_ID(Message::Components::TagMessageIsAction)
|
||||
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::TagHaveAll)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::BytesSent)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::BytesReceived)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::File)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::TagReceiving)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::TagSending)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::TagPaused)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::StateCanceled)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::FileInfo)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::FileInfoLocal)
|
||||
DEFINE_COMP_ID(Message::Components::Transfer::ActionAccept)
|
||||
|
||||
#undef DEFINE_COMP_ID
|
||||
|
20
src/solanaceae/message3/file.hpp
Normal file
20
src/solanaceae/message3/file.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
struct FileI {
|
||||
uint64_t _file_size {0};
|
||||
// TODO: remove?
|
||||
uint64_t _bytes_read {0};
|
||||
uint64_t _bytes_written {0};
|
||||
|
||||
virtual ~FileI(void) {}
|
||||
|
||||
virtual bool isGood(void) = 0;
|
||||
|
||||
// TODO: move to owning/nonowning pointers
|
||||
virtual std::vector<uint8_t> read(uint64_t pos, uint64_t size) = 0;
|
||||
virtual bool write(uint64_t pos, const std::vector<uint8_t>& data) = 0;
|
||||
};
|
||||
|
64
src/solanaceae/message3/file_r_file.hpp
Normal file
64
src/solanaceae/message3/file_r_file.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "./file.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <limits>
|
||||
|
||||
struct FileRFile : public FileI {
|
||||
std::ifstream _file;
|
||||
|
||||
FileRFile(std::string_view file_path) : _file(static_cast<std::string>(file_path), std::ios::binary) {
|
||||
if (!_file.is_open()) {
|
||||
return; // TODO: error
|
||||
}
|
||||
|
||||
// figure out size
|
||||
_file.seekg(0, _file.end);
|
||||
_file_size = _file.tellg();
|
||||
_file.seekg(0, _file.beg);
|
||||
}
|
||||
|
||||
//FileRFile(std::ifstream&& other_file) : _file(std::move(other_file)) {}
|
||||
//FileRFile(std::ifstream&& other_file, size_t file_size) : _file(std::move(other_file)), _file_size(file_size) {}
|
||||
|
||||
virtual ~FileRFile(void) {}
|
||||
|
||||
bool isGood(void) override {
|
||||
return _file.is_open() && _file.good();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read(uint64_t pos, uint64_t size) override {
|
||||
if (_file_size > 0 && pos >= _file_size) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: error check
|
||||
_file.seekg(pos);
|
||||
|
||||
#if 0
|
||||
std::vector<uint8_t> chunk;
|
||||
int read_char;
|
||||
for (size_t i = 0; i < size && (_file_size == 0 || i+pos < _file_size) && (read_char = _file.get()) != std::ifstream::traits_type::eof(); i++) {
|
||||
chunk.push_back(read_char);
|
||||
}
|
||||
#else
|
||||
std::vector<uint8_t> chunk(size);
|
||||
const auto nread = _file.read(reinterpret_cast<char*>(chunk.data()), chunk.size()).gcount();
|
||||
if (nread != std::numeric_limits<std::streamsize>::max()) {
|
||||
chunk.resize(nread); // usually a noop
|
||||
} else {
|
||||
chunk.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
_bytes_read += chunk.size();
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// read only
|
||||
bool write(uint64_t, const std::vector<uint8_t>&) override { return false; }
|
||||
};
|
||||
|
38
src/solanaceae/message3/file_r_mem.hpp
Normal file
38
src/solanaceae/message3/file_r_mem.hpp
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "./file.hpp"
|
||||
|
||||
struct FileRMem : public FileI {
|
||||
std::vector<uint8_t> _data;
|
||||
|
||||
FileRMem(void) = delete;
|
||||
FileRMem(const std::vector<uint8_t>& data) : _data(data) {
|
||||
_file_size = _data.size();
|
||||
}
|
||||
virtual ~FileRMem(void) {}
|
||||
|
||||
bool isGood(void) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read(uint64_t pos, uint64_t size) override {
|
||||
if (_file_size > 0 && pos >= _data.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: optimize
|
||||
std::vector<uint8_t> chunk;
|
||||
for (size_t i = 0; i < size && i+pos < _data.size(); i++) {
|
||||
chunk.push_back(_data[pos+i]);
|
||||
}
|
||||
//chunk.insert(chunk.begin(), _data.cbegin()+pos, _data.cbegin()+pos+size);
|
||||
|
||||
_bytes_read += chunk.size();
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// read only
|
||||
bool write(uint64_t, const std::vector<uint8_t>&) override { return false; }
|
||||
};
|
||||
|
71
src/solanaceae/message3/file_rw_file.hpp
Normal file
71
src/solanaceae/message3/file_rw_file.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "./file.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
|
||||
struct FileRWFile : public FileI {
|
||||
std::fstream _file;
|
||||
|
||||
// dont truncate by default
|
||||
FileRWFile(std::string_view file_path, uint64_t file_size, bool trunc = false)
|
||||
: _file(
|
||||
static_cast<std::string>(file_path),
|
||||
std::ios::in |
|
||||
std::ios::out |
|
||||
(trunc ? std::ios::trunc | std::ios::binary : std::ios::binary) // hacky but type safe
|
||||
) {
|
||||
_file_size = file_size;
|
||||
|
||||
if (!_file.is_open()) {
|
||||
return; // TODO: error
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~FileRWFile(void) {}
|
||||
|
||||
bool isGood(void) override {
|
||||
return _file.is_open() && _file.good();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read(uint64_t pos, uint64_t size) override {
|
||||
if (pos >= _file_size) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: error check
|
||||
_file.seekg(pos, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> chunk(size);
|
||||
const auto nread = _file.read(reinterpret_cast<char*>(chunk.data()), chunk.size()).gcount();
|
||||
if (nread != std::numeric_limits<std::streamsize>::max()) {
|
||||
chunk.resize(nread); // usually a noop
|
||||
} else {
|
||||
chunk.clear();
|
||||
}
|
||||
|
||||
_bytes_read += chunk.size();
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
bool write(uint64_t pos, const std::vector<uint8_t>& data) override {
|
||||
if (pos >= _file_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if out-of-order, seek
|
||||
if (_file.tellp() != int64_t(pos)) {
|
||||
// TODO: error check
|
||||
if (_file.seekp(pos, std::ios::beg).fail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
return _file.good();
|
||||
}
|
||||
};
|
||||
|
46
src/solanaceae/message3/file_w_file.hpp
Normal file
46
src/solanaceae/message3/file_w_file.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "./file.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <fstream>
|
||||
|
||||
struct FileWFile : public FileI {
|
||||
std::ofstream _file;
|
||||
|
||||
FileWFile(std::string_view file_path, uint64_t file_size) : _file(static_cast<std::string>(file_path), std::ios::binary) {
|
||||
_file_size = file_size;
|
||||
|
||||
if (!_file.is_open()) {
|
||||
return; // TODO: error
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~FileWFile(void) {}
|
||||
|
||||
bool isGood(void) override {
|
||||
return _file.is_open() && _file.good();
|
||||
}
|
||||
|
||||
// write only
|
||||
std::vector<uint8_t> read(uint64_t, uint64_t) override { return {}; }
|
||||
|
||||
bool write(uint64_t pos, const std::vector<uint8_t>& data) override {
|
||||
if (_file_size > 0 && pos >= _file_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if out-of-order, seek
|
||||
if (_file.tellp() != int64_t(pos)) {
|
||||
// TODO: error check
|
||||
_file.seekp(pos);
|
||||
}
|
||||
|
||||
_file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
_bytes_written += data.size();
|
||||
|
||||
return _file.good();
|
||||
}
|
||||
};
|
||||
|
377
src/solanaceae/message3/message_command_dispatcher.cpp
Normal file
377
src/solanaceae/message3/message_command_dispatcher.cpp
Normal file
@ -0,0 +1,377 @@
|
||||
#include "./message_command_dispatcher.hpp"
|
||||
|
||||
#include <solanaceae/util/config_model.hpp>
|
||||
#include <solanaceae/util/utils.hpp>
|
||||
#include <solanaceae/message3/components.hpp>
|
||||
#include <solanaceae/contact/components.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cassert>
|
||||
|
||||
MessageCommandDispatcher::MessageCommandDispatcher(
|
||||
Contact3Registry& cr,
|
||||
RegistryMessageModel& rmm,
|
||||
ConfigModelI& conf
|
||||
) :
|
||||
_cr(cr), _rmm(rmm), _conf(conf), _program_started_at(Message::getTimeMS())
|
||||
{
|
||||
// overwrite default admin and moderator to false
|
||||
_conf.set("MessageCommandDispatcher", "admin", false);
|
||||
_conf.set("MessageCommandDispatcher", "moderator", false);
|
||||
|
||||
_rmm.subscribe(this, RegistryMessageModel_Event::message_construct);
|
||||
|
||||
{ // setup basic commands for bot
|
||||
registerCommand(
|
||||
"host", "",
|
||||
"help",
|
||||
[this](std::string_view params, Message3Handle m) -> bool {
|
||||
return helpCommand(params, m);
|
||||
},
|
||||
"Get help",
|
||||
Perms::EVERYONE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MessageCommandDispatcher::~MessageCommandDispatcher(void) {
|
||||
}
|
||||
|
||||
float MessageCommandDispatcher::iterate(float) {
|
||||
if (!_message_queue.empty()) {
|
||||
_rmm.sendText(
|
||||
_message_queue.front().to,
|
||||
_message_queue.front().message
|
||||
);
|
||||
_message_queue.pop_front();
|
||||
|
||||
return 0.05f;
|
||||
}
|
||||
|
||||
return 1.f*60.f*60.f; // 1h
|
||||
}
|
||||
|
||||
static std::string_view get_first_word(std::string_view text, std::string_view::size_type& out_next) {
|
||||
if (text.empty()) {
|
||||
out_next = std::string_view::npos;
|
||||
return text;
|
||||
}
|
||||
|
||||
// trim
|
||||
const auto pos_first_non_space = text.find_first_not_of(' ');
|
||||
if (pos_first_non_space == std::string_view::npos) {
|
||||
// only contains spaces o.o
|
||||
out_next = std::string_view::npos;
|
||||
return "";
|
||||
}
|
||||
|
||||
text = text.substr(pos_first_non_space);
|
||||
out_next += pos_first_non_space;
|
||||
|
||||
const auto pos_first_space = text.find_first_of(' ');
|
||||
if (pos_first_space == 0 || pos_first_space == std::string_view::npos) {
|
||||
// does not contain spaces
|
||||
// command is whole message
|
||||
out_next = std::string_view::npos;
|
||||
return text;
|
||||
} else {
|
||||
out_next += pos_first_space;
|
||||
return text.substr(0, pos_first_space);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageCommandDispatcher::registerCommand(
|
||||
std::string_view m, // module
|
||||
std::string_view m_prefix, // module prefix (if any)
|
||||
std::string_view command, // command
|
||||
std::function<bool(std::string_view params, Message3Handle m)>&& fn,
|
||||
std::string_view help_text,
|
||||
Perms perms
|
||||
) {
|
||||
std::string full_command_string = (m_prefix.empty() ? "" : std::string{m_prefix} + " ") + std::string{command};
|
||||
|
||||
if (_command_map.count(full_command_string)) {
|
||||
std::cout << "MCD warning: overwriting existing '" << full_command_string << "'\n";
|
||||
}
|
||||
|
||||
assert(
|
||||
// needs atleast one "group"
|
||||
(perms & (
|
||||
Perms::EVERYONE |
|
||||
Perms::ADMIN |
|
||||
Perms::MODERATOR
|
||||
)) != 0u
|
||||
);
|
||||
|
||||
assert(
|
||||
// at most one "group"
|
||||
(((perms & Perms::EVERYONE) != 0) +
|
||||
((perms & Perms::ADMIN) != 0) +
|
||||
((perms & Perms::MODERATOR) != 0))
|
||||
== 1
|
||||
);
|
||||
|
||||
_command_map[full_command_string] = Command{
|
||||
std::string{m},
|
||||
std::string{m_prefix},
|
||||
std::string{command},
|
||||
std::move(fn),
|
||||
std::string{help_text},
|
||||
perms
|
||||
};
|
||||
}
|
||||
|
||||
bool MessageCommandDispatcher::helpCommand(std::string_view params, Message3Handle m) {
|
||||
std::cout << "MCD: help got called '" << params << "'\n";
|
||||
|
||||
std::map<std::string, std::vector<decltype(_command_map.cbegin())>> module_command_list;
|
||||
for (auto it = _command_map.cbegin(); it != _command_map.cend(); it++) {
|
||||
if (true) { // have permission
|
||||
module_command_list[it->second.m].push_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
const auto contact_from = m.get<Message::Components::ContactFrom>().c;
|
||||
|
||||
for (const auto& [module_name, command_list] : module_command_list) {
|
||||
_message_queue.push_back({
|
||||
contact_from,
|
||||
"=== " + module_name + " ==="
|
||||
});
|
||||
|
||||
bool module_empty = true;
|
||||
for (const auto& it : command_list) {
|
||||
if (!hasPermission(it->second, contact_from)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module_empty = false;
|
||||
|
||||
std::string help_line {" !"};
|
||||
if (!it->second.m_prefix.empty()) {
|
||||
help_line += it->second.m_prefix + " ";
|
||||
}
|
||||
|
||||
help_line += it->second.command;
|
||||
|
||||
help_line += " - ";
|
||||
help_line += it->second.help_text;
|
||||
|
||||
_message_queue.push_back({
|
||||
contact_from,
|
||||
help_line
|
||||
});
|
||||
}
|
||||
|
||||
if (module_empty) {
|
||||
// unsend module cat title
|
||||
_message_queue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MessageCommandDispatcher::hasPermission(const Command& cmd, const Contact3 contact) {
|
||||
if (!_cr.all_of<Contact::Components::ID>(contact)) {
|
||||
std::cerr << "MCD error: contact without ID\n";
|
||||
return false; // default to false
|
||||
}
|
||||
|
||||
const auto id_str = bin2hex(_cr.get<Contact::Components::ID>(contact).data);
|
||||
std::cout << "MCD: perm check for id '" << id_str << "'\n";
|
||||
|
||||
// TODO: blacklist here
|
||||
// TODO: whitelist here
|
||||
|
||||
if ((cmd.perms & Perms::EVERYONE) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((cmd.perms & Perms::ADMIN) != 0) {
|
||||
auto is_admin_opt = _conf.get_bool("MessageCommandDispatcher", "admin", id_str);
|
||||
assert(is_admin_opt.has_value);
|
||||
|
||||
return is_admin_opt.value();
|
||||
}
|
||||
|
||||
if ((cmd.perms & Perms::MODERATOR) != 0) {
|
||||
auto is_mod_opt = _conf.get_bool("MessageCommandDispatcher", "moderator", id_str);
|
||||
assert(is_mod_opt.has_value);
|
||||
|
||||
return is_mod_opt.value();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MessageCommandDispatcher::onEvent(const Message::Events::MessageConstruct& e) {
|
||||
if (!e.e.all_of<Message::Components::ContactFrom, Message::Components::MessageText, Message::Components::TagUnread>()) {
|
||||
std::cout << "MCD: got message that is not";
|
||||
|
||||
if (!e.e.all_of<Message::Components::ContactFrom>()) {
|
||||
std::cout << " contact_from";
|
||||
}
|
||||
if (!e.e.all_of<Message::Components::MessageText>()) {
|
||||
std::cout << " text";
|
||||
}
|
||||
if (!e.e.all_of<Message::Components::TagUnread>()) {
|
||||
std::cout << " unread";
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.e.any_of<Message::Components::TagMessageIsAction>()) {
|
||||
std::cout << "MCD: got message that is";
|
||||
if (e.e.all_of<Message::Components::TagMessageIsAction>()) {
|
||||
std::cout << " action";
|
||||
}
|
||||
std::cout << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.e.any_of<Message::Components::Timestamp>()) {
|
||||
// test if message was written before program was started (-1s)
|
||||
if (e.e.get<Message::Components::Timestamp>().ts + 1'000 < _program_started_at) {
|
||||
// message too old
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view message_text = e.e.get<Message::Components::MessageText>().text;
|
||||
|
||||
if (message_text.empty()) {
|
||||
std::cout << "MCD warning: empty message\n";
|
||||
// empty message?
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip unrelyable synced
|
||||
if (e.e.all_of<Message::Components::SyncedBy>()) {
|
||||
const auto& list = e.e.get<Message::Components::SyncedBy>().ts;
|
||||
if (
|
||||
std::find_if(
|
||||
list.cbegin(), list.cend(),
|
||||
[this](const auto&& it) {
|
||||
return _cr.any_of<
|
||||
Contact::Components::TagSelfStrong,
|
||||
Contact::Components::TagSelfWeak // trust weak self
|
||||
>(it.first);
|
||||
}
|
||||
) == list.cend()
|
||||
) {
|
||||
// self not found
|
||||
// TODO: config for self only
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool is_private = _cr.any_of<Contact::Components::TagSelfWeak, Contact::Components::TagSelfStrong>(e.e.get<Message::Components::ContactTo>().c);
|
||||
|
||||
if (is_private) {
|
||||
// check for command prefix
|
||||
if (
|
||||
message_text.at(0) == '!' ||
|
||||
message_text.at(0) == '/'
|
||||
) {
|
||||
// starts with command prefix
|
||||
// remove c prefix
|
||||
message_text = message_text.substr(1);
|
||||
}
|
||||
} else {
|
||||
// check for command prefix
|
||||
if (
|
||||
message_text.at(0) != '!' &&
|
||||
message_text.at(0) != '/'
|
||||
) {
|
||||
// does not start with command prefix, not for us
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove c prefix
|
||||
message_text = message_text.substr(1);
|
||||
}
|
||||
|
||||
if (message_text.empty()) {
|
||||
// empty message?
|
||||
std::cout << "MCD: got empty command\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "MCD: got command '" << message_text << "'\n";
|
||||
|
||||
std::string_view first_word;
|
||||
std::string_view second_word;
|
||||
std::string_view::size_type pos_next = 0;
|
||||
|
||||
first_word = get_first_word(message_text, pos_next);
|
||||
std::cout << "------- first_word:'" << first_word << "' pos_next:" << pos_next << "\n";
|
||||
if (first_word.size() != message_text.size()) {
|
||||
second_word = get_first_word(
|
||||
message_text.substr(pos_next),
|
||||
pos_next
|
||||
);
|
||||
}
|
||||
|
||||
std::cout << "------- second_word:'" << second_word << "' empty:" << second_word.empty() << " pos_next:" << pos_next << "\n";
|
||||
|
||||
std::string params;
|
||||
if (pos_next != std::string_view::npos && message_text.size() > pos_next+1) {
|
||||
auto tmp_params = message_text.substr(pos_next);
|
||||
|
||||
const auto params_pos_first_non_space = tmp_params.find_first_not_of(' ');
|
||||
if (params_pos_first_non_space == std::string_view::npos) {
|
||||
tmp_params = {};
|
||||
} else if (params_pos_first_non_space != 0) {
|
||||
// trim leading whitespace
|
||||
tmp_params = tmp_params.substr(params_pos_first_non_space);
|
||||
}
|
||||
|
||||
params = tmp_params;
|
||||
|
||||
std::cout << "------- params:'" << params << "'\n";
|
||||
}
|
||||
|
||||
const auto contact_from = e.e.get<Message::Components::ContactFrom>().c;
|
||||
|
||||
// first search first + space + second word
|
||||
if (!second_word.empty()) {
|
||||
std::string query {first_word};
|
||||
query += " ";
|
||||
query += second_word;
|
||||
|
||||
const auto command_it = _command_map.find(query);
|
||||
if (command_it != _command_map.cend()) {
|
||||
if (!hasPermission(command_it->second, contact_from)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return command_it->second.fn(params, e.e);
|
||||
}
|
||||
}
|
||||
|
||||
// then seach first word only
|
||||
const auto command_it = _command_map.find(std::string{first_word});
|
||||
if (command_it != _command_map.cend()) {
|
||||
if (!hasPermission(command_it->second, contact_from)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
params = std::string{second_word} + " " + params;
|
||||
return command_it->second.fn(params, e.e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MessageCommandDispatcher::onEvent(const Message::Events::MessageUpdated&) {
|
||||
// do i need this?
|
||||
return false;
|
||||
}
|
||||
|
87
src/solanaceae/message3/message_command_dispatcher.hpp
Normal file
87
src/solanaceae/message3/message_command_dispatcher.hpp
Normal file
@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/message3/registry_message_model.hpp>
|
||||
#include <solanaceae/contact/contact_model3.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
// fwd
|
||||
struct ConfigModelI;
|
||||
|
||||
class MessageCommandDispatcher : public RegistryMessageModelEventI {
|
||||
Contact3Registry& _cr;
|
||||
RegistryMessageModel& _rmm;
|
||||
ConfigModelI& _conf;
|
||||
|
||||
public:
|
||||
static constexpr const char* version {"1"};
|
||||
|
||||
enum Perms {
|
||||
BLACKLIST = 1 << 0,
|
||||
WHITELIST = 1 << 1,
|
||||
|
||||
// can only be trumped by blacklist
|
||||
EVERYONE = 1 << 2,
|
||||
|
||||
// can only be trumped by blacklist
|
||||
// TODO: replace with groups?
|
||||
ADMIN = 1 << 3,
|
||||
MODERATOR = 1 << 4,
|
||||
};
|
||||
|
||||
private:
|
||||
struct Command {
|
||||
std::string m; // module
|
||||
std::string m_prefix; // module prefix (if any)
|
||||
std::string command; // command
|
||||
std::function<bool(std::string_view params, Message3Handle m)> fn;
|
||||
std::string help_text;
|
||||
|
||||
Perms perms = Perms::ADMIN; // default to highest
|
||||
|
||||
//Command(const Command&) = delete;
|
||||
};
|
||||
std::unordered_map<std::string, Command> _command_map;
|
||||
|
||||
struct QueuedMessage {
|
||||
Contact3 to;
|
||||
std::string message;
|
||||
};
|
||||
std::deque<QueuedMessage> _message_queue;
|
||||
|
||||
uint64_t _program_started_at {0};
|
||||
|
||||
public:
|
||||
MessageCommandDispatcher(Contact3Registry& cr, RegistryMessageModel& rmm, ConfigModelI& conf);
|
||||
~MessageCommandDispatcher(void);
|
||||
|
||||
float iterate(float time_delta);
|
||||
|
||||
// TODO: think more about permissions?
|
||||
// - user(s)
|
||||
// - group(s)
|
||||
// - everyone else?
|
||||
|
||||
void registerCommand(
|
||||
std::string_view m, // module
|
||||
std::string_view m_prefix, // module prefix (if any)
|
||||
std::string_view command, // command
|
||||
std::function<bool(std::string_view params, Message3Handle m)>&& fn,
|
||||
std::string_view help_text,
|
||||
Perms perms = Perms::ADMIN
|
||||
);
|
||||
|
||||
// generates a help
|
||||
bool helpCommand(std::string_view params, Message3Handle m);
|
||||
|
||||
bool hasPermission(const Command& cmd, const Contact3 contact);
|
||||
|
||||
protected: // mm
|
||||
bool onEvent(const Message::Events::MessageConstruct& e) override;
|
||||
bool onEvent(const Message::Events::MessageUpdated& e) override;
|
||||
};
|
||||
|
27
src/solanaceae/message3/message_model3.hpp
Normal file
27
src/solanaceae/message3/message_model3.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/contact/contact_model3.hpp>
|
||||
|
||||
//#include "./file.hpp"
|
||||
|
||||
// TODO: move, rename, do something?, change in favor of tox?
|
||||
//enum class FileKind : uint32_t {
|
||||
//file = 0u,
|
||||
//avatar = 1u,
|
||||
//};
|
||||
|
||||
// interface to send messages
|
||||
struct MessageModel3I {
|
||||
virtual ~MessageModel3I(void) {}
|
||||
|
||||
// return true if a handler was found for the contact
|
||||
|
||||
virtual bool sendText(const Contact3 c, std::string_view message, bool action = false) { (void)c,(void)message,(void)action; return false; }
|
||||
|
||||
//virtual bool sendFile(const Contact& c, std::string_view file_name, std::unique_ptr<FileI> file) { (void)c,(void)message,(void)action; return false; }
|
||||
virtual bool sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) { (void)c,(void)file_name,(void)file_path; return false; }
|
||||
|
||||
|
||||
//virtual bool sendFileMem(const Contact& c, std::string_view file_name, const std::vector<uint8_t>& file) = 0;
|
||||
};
|
||||
|
34
src/solanaceae/message3/message_time_sort.cpp
Normal file
34
src/solanaceae/message3/message_time_sort.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include "./message_time_sort.hpp"
|
||||
|
||||
#include "./components.hpp"
|
||||
|
||||
MessageTimeSort::MessageTimeSort(RegistryMessageModel& rmm) : _rmm(rmm) {
|
||||
_rmm.subscribe(this, RegistryMessageModel_Event::message_construct);
|
||||
_rmm.subscribe(this, RegistryMessageModel_Event::message_updated);
|
||||
_rmm.subscribe(this, RegistryMessageModel_Event::message_destroy);
|
||||
}
|
||||
|
||||
void MessageTimeSort::iterate(void) {
|
||||
// TODO: maybe only every x for updated
|
||||
for (auto* reg : _to_sort) {
|
||||
reg->sort<Message::Components::Timestamp>([](const auto& lhs, const auto& rhs) -> bool {
|
||||
return lhs.ts > rhs.ts;
|
||||
}, entt::insertion_sort{});
|
||||
}
|
||||
_to_sort.clear();
|
||||
}
|
||||
|
||||
bool MessageTimeSort::onEvent(const Message::Events::MessageConstruct& e) {
|
||||
_to_sort.emplace(e.e.registry());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MessageTimeSort::onEvent(const Message::Events::MessageUpdated& e) {
|
||||
_to_sort.emplace(e.e.registry());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MessageTimeSort::onEvent(const Message::Events::MessageDestory& e) {
|
||||
_to_sort.emplace(e.e.registry());
|
||||
return false;
|
||||
}
|
24
src/solanaceae/message3/message_time_sort.hpp
Normal file
24
src/solanaceae/message3/message_time_sort.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "./registry_message_model.hpp"
|
||||
|
||||
#include <entt/container/dense_set.hpp>
|
||||
|
||||
class MessageTimeSort : public RegistryMessageModelEventI {
|
||||
RegistryMessageModel& _rmm;
|
||||
|
||||
// TODO: use contact instead
|
||||
entt::dense_set<Message3Registry*> _to_sort;
|
||||
|
||||
public:
|
||||
MessageTimeSort(RegistryMessageModel& rmm);
|
||||
|
||||
// do the sorting
|
||||
void iterate(void);
|
||||
|
||||
protected: // mm
|
||||
bool onEvent(const Message::Events::MessageConstruct& e) override;
|
||||
bool onEvent(const Message::Events::MessageUpdated& e) override;
|
||||
bool onEvent(const Message::Events::MessageDestory& e) override;
|
||||
};
|
||||
|
142
src/solanaceae/message3/registry_message_model.cpp
Normal file
142
src/solanaceae/message3/registry_message_model.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
#include "./registry_message_model.hpp"
|
||||
|
||||
#include <solanaceae/contact/components.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
uint64_t Message::getTimeMS(void) {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
Message3Registry* RegistryMessageModel::get(Contact3 c) {
|
||||
if (_cr.valid(c) && !_cr.all_of<Contact::Components::TagBig>(c)) {
|
||||
// TODO: loop upwards
|
||||
if (!_cr.all_of<Contact::Components::Parent>(c)) {
|
||||
return nullptr;
|
||||
}
|
||||
c = _cr.get<Contact::Components::Parent>(c).parent;
|
||||
}
|
||||
|
||||
if (!_cr.valid(c)) {
|
||||
// TODO: throw error
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto it = _contact_messages.find(c);
|
||||
if (it != _contact_messages.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
auto& reg_sh = _contact_messages[c] = std::make_unique<Message3Registry>();
|
||||
return reg_sh.get();
|
||||
}
|
||||
|
||||
Message3Registry* RegistryMessageModel::get(Contact3 c) const {
|
||||
if (_cr.valid(c) && !_cr.all_of<Contact::Components::TagBig>(c)) {
|
||||
// TODO: loop upwards
|
||||
if (!_cr.all_of<Contact::Components::Parent>(c)) {
|
||||
return nullptr;
|
||||
}
|
||||
c = _cr.get<Contact::Components::Parent>(c).parent;
|
||||
}
|
||||
|
||||
if (!_cr.valid(c)) {
|
||||
// TODO: throw error
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto it = _contact_messages.find(c);
|
||||
if (it != _contact_messages.cend()) {
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RegistryMessageModel::throwEventConstruct(Message3Registry& reg, Message3 e) {
|
||||
std::cout << "RMM debug: event construct " << entt::to_integral(e) << "\n";
|
||||
dispatch(
|
||||
RegistryMessageModel_Event::message_construct,
|
||||
Message::Events::MessageConstruct{
|
||||
Message3Handle{reg, e}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void RegistryMessageModel::throwEventUpdate(Message3Registry& reg, Message3 e) {
|
||||
// the update while update lock is hacky
|
||||
_update_queue.push_back({reg, e});
|
||||
if (!_update_in_progess) {
|
||||
_update_in_progess = true;
|
||||
for (size_t i = 0; i < _update_queue.size(); i++) {
|
||||
std::cout << "RMM debug: event update " << entt::to_integral(e) << "\n";
|
||||
dispatch(
|
||||
RegistryMessageModel_Event::message_updated,
|
||||
Message::Events::MessageUpdated{
|
||||
_update_queue.at(i)
|
||||
}
|
||||
);
|
||||
}
|
||||
_update_queue.clear();
|
||||
_update_in_progess = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RegistryMessageModel::throwEventDestroy(Message3Registry& reg, Message3 e) {
|
||||
std::cout << "RMM debug: event destroy " << entt::to_integral(e) << "\n";
|
||||
dispatch(
|
||||
RegistryMessageModel_Event::message_destroy,
|
||||
Message::Events::MessageDestory{
|
||||
Message3Handle{reg, e}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void RegistryMessageModel::throwEventConstruct(const Contact3 c, Message3 e) {
|
||||
if (auto* reg_ptr = get(c); reg_ptr) {
|
||||
throwEventConstruct(*reg_ptr, e);
|
||||
}
|
||||
}
|
||||
|
||||
void RegistryMessageModel::throwEventUpdate(const Contact3 c, Message3 e) {
|
||||
if (auto* reg_ptr = get(c); reg_ptr) {
|
||||
throwEventUpdate(*reg_ptr, e);
|
||||
}
|
||||
}
|
||||
|
||||
void RegistryMessageModel::throwEventDestroy(const Contact3 c, Message3 e) {
|
||||
if (auto* reg_ptr = get(c); reg_ptr) {
|
||||
throwEventDestroy(*reg_ptr, e);
|
||||
}
|
||||
}
|
||||
|
||||
bool RegistryMessageModel::sendText(const Contact3 c, std::string_view message, bool action) {
|
||||
std::cout << "RMM debug: event send text\n";
|
||||
|
||||
// manual, bc its not an "event"
|
||||
for (auto* zei : _subscribers.at(size_t(RegistryMessageModel_Event::send_text))) {
|
||||
if (zei->sendText(c, message, action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "RMM error: event send text unhandled\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegistryMessageModel::sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) {
|
||||
std::cout << "RMM debug: event send file path\n";
|
||||
|
||||
// manual, bc its not an "event"
|
||||
for (auto* zei : _subscribers.at(size_t(RegistryMessageModel_Event::send_file_path))) {
|
||||
if (zei->sendFilePath(c, file_name, file_path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "RMM error: event send file path unhandled\n";
|
||||
|
||||
return false;
|
||||
}
|
111
src/solanaceae/message3/registry_message_model.hpp
Normal file
111
src/solanaceae/message3/registry_message_model.hpp
Normal file
@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/util/event_provider.hpp>
|
||||
|
||||
#include "./message_model3.hpp"
|
||||
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/handle.hpp>
|
||||
#include <entt/container/dense_map.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
// strong typing
|
||||
enum class Message3 : uint32_t {};
|
||||
using Message3Registry = entt::basic_registry<Message3>;
|
||||
using Message3Handle = entt::basic_handle<Message3Registry>;
|
||||
|
||||
namespace Message {
|
||||
|
||||
namespace Events {
|
||||
|
||||
struct MessageConstruct {
|
||||
const Message3Handle e;
|
||||
};
|
||||
|
||||
struct MessageUpdated {
|
||||
const Message3Handle e;
|
||||
// hint?
|
||||
// like component list?
|
||||
};
|
||||
|
||||
struct MessageDestory {
|
||||
const Message3Handle e;
|
||||
};
|
||||
|
||||
} // Events
|
||||
|
||||
// get unix time in milliseconds
|
||||
// TODO: move to util
|
||||
uint64_t getTimeMS(void);
|
||||
|
||||
} // Message
|
||||
|
||||
enum class RegistryMessageModel_Event : uint32_t {
|
||||
message_construct,
|
||||
message_updated,
|
||||
message_destroy,
|
||||
|
||||
send_text,
|
||||
send_file_path,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
// 2 in 1
|
||||
struct RegistryMessageModelEventI : public MessageModel3I {
|
||||
using enumType = RegistryMessageModel_Event;
|
||||
|
||||
virtual ~RegistryMessageModelEventI(void) {}
|
||||
|
||||
virtual bool onEvent(const Message::Events::MessageConstruct&) { return false; }
|
||||
virtual bool onEvent(const Message::Events::MessageUpdated&) { return false; }
|
||||
virtual bool onEvent(const Message::Events::MessageDestory&) { return false; }
|
||||
|
||||
// mm3
|
||||
// send text
|
||||
// send file path
|
||||
};
|
||||
using RegistryMessageModelEventProviderI = EventProviderI<RegistryMessageModelEventI>;
|
||||
|
||||
class RegistryMessageModel : public RegistryMessageModelEventProviderI, public MessageModel3I {
|
||||
public:
|
||||
static constexpr const char* version {"1"};
|
||||
|
||||
protected:
|
||||
Contact3Registry& _cr;
|
||||
|
||||
entt::dense_map<Contact3, std::unique_ptr<Message3Registry>> _contact_messages;
|
||||
|
||||
bool _update_in_progess {false};
|
||||
std::vector<Message3Handle> _update_queue {};
|
||||
|
||||
public:
|
||||
RegistryMessageModel(Contact3Registry& cr) : _cr(cr) {}
|
||||
virtual ~RegistryMessageModel(void) {}
|
||||
|
||||
// TODO: iterate?
|
||||
|
||||
public:
|
||||
Message3Registry* get(Contact3 c);
|
||||
Message3Registry* get(Contact3 c) const;
|
||||
|
||||
public: // dispatcher
|
||||
// !!! remember to manually throw these externally
|
||||
void throwEventConstruct(Message3Registry& reg, Message3 e);
|
||||
void throwEventUpdate(Message3Registry& reg, Message3 e);
|
||||
void throwEventDestroy(Message3Registry& reg, Message3 e);
|
||||
|
||||
void throwEventConstruct(Message3Handle h) { throwEventConstruct(*h.registry(), h.entity()); }
|
||||
void throwEventUpdate(Message3Handle h) { throwEventUpdate(*h.registry(), h.entity()); }
|
||||
void throwEventDestroy(Message3Handle h) { throwEventDestroy(*h.registry(), h.entity()); }
|
||||
|
||||
void throwEventConstruct(const Contact3 c, Message3 e);
|
||||
void throwEventUpdate(const Contact3 c, Message3 e);
|
||||
void throwEventDestroy(const Contact3 c, Message3 e);
|
||||
|
||||
public: // mm3
|
||||
bool sendText(const Contact3 c, std::string_view message, bool action = false) override;
|
||||
bool sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) override;
|
||||
};
|
||||
|
Reference in New Issue
Block a user