// This file is generated automatically by amalgamate; // GitHub repo https://github.com/edlund/amalgamate // When contributing to this repository, please DO NOT edit this file. // Copyright (C) 2020-2021 Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #ifndef MIRAICP_PRO_PLUGINCONFIG_H #define MIRAICP_PRO_PLUGINCONFIG_H #include namespace MiraiCP { const std::string MiraiCPVersion = "v2.12.0-RC2"; struct PluginConfig { /// @brief 插件id, 要与别人不一样否则报错无法加载(建议用类包格式,如: io.github.nambers) const char *id; /// @brief 插件名称 const char *name; /// @brief 插件版本 const char *version; /// @brief 插件作者(及联系方式) const char *author; /// @brief [optional]插件描述 const char *description; /// @brief [optional]构建时间, 默认为__DATE__宏 const char *time = __DATE__; const char *mversion = MiraiCPVersion.c_str(); std::string getId() const { return {id}; } std::string getName() const { return {name}; } std::string getVersion() const { return {version}; } std::string getAuthor() const { return {author}; } std::string getDescription() const { return {description}; } std::string getTime() const { return {time}; } std::string getMVersion() const { return {mversion}; } nlohmann::json serialize(); std::string serialize2string(); }; } // namespace MiraiCP #endif //MIRAICP_PRO_PLUGINCONFIG_H // Copyright (c) 2022. Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_COMMONTOOLS_H #define MIRAICP_PRO_COMMONTOOLS_H #include #define MiraiCP_defer(code) \ auto __defered_statement_wrapper__ = [&]() { code }; \ CommonTools::MiraiCPDefer __defered_object__(__defered_statement_wrapper__) #define MiraiCP_defer_lambda(lambda) \ auto __defered_statement_wrapper__ = lambda; \ CommonTools::MiraiCPDefer __defered_object__(__defered_statement_wrapper__) namespace CommonTools { /// defer class /// @see MiraiCP_defer template class MiraiCPDefer { public: std::function defer_func; template MiraiCPDefer(F &&func) : defer_func(std::forward(func)) { } virtual ~MiraiCPDefer() { defer_func(); } }; } // namespace CommonTools #endif //MIRAICP_PRO_COMMONTOOLS_H // Copyright (c) 2022. Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_COMMONTYPES_H #define MIRAICP_PRO_COMMONTYPES_H // don't create cpp for this header // #include "PluginConfig.h" // #include "loaderApiInternal.h" // Copyright (c) 2022. Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_LOADERAPIINTERNAL_H #define MIRAICP_PRO_LOADERAPIINTERNAL_H // #include "miraicpString.h" // Copyright (c) 2022. Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_MIRAICPSTRING_H #define MIRAICP_PRO_MIRAICPSTRING_H #include #include namespace MiraiCP { // this class is used to ensure data consistency between dynamic libs // note: do not use this directly; // always convert to const char* or std::string before using. class MiraiCPString { using string = std::string; friend void swap(MiraiCPString &, MiraiCPString &) noexcept; private: static constexpr decltype(&::std::free) std_free_ptr = &::std::free; private: // to keep integration and safe for empty construction/deconstruction, always initialize here char *str = nullptr; size_t _size = 0; decltype(&::std::free) free_this = std_free_ptr; // specify which free() to use; ensure deconstruction is paired to construction public: bool isEmpty() const { return _size == 0; } MiraiCPString() : str(nullptr), _size(0), free_this(std_free_ptr) {} // call if _size is set to non-zero // allocate memory for str void construction(); ~MiraiCPString(); MiraiCPString(const MiraiCPString &other); MiraiCPString(MiraiCPString &&temp) noexcept; MiraiCPString(const char *char_str); MiraiCPString(const std::string &string_str); std::string toString() const { if (str == nullptr || _size == 0) return {}; return {str}; } operator std::string() const { return toString(); } // for safe destruction, DO NOT provide move convert to char* // the return value of this method can always be deleted by delete[] and is never nullptr const char *copyToCharPtr() const; bool operator==(const MiraiCPString &another) const; MiraiCPString &operator=(const MiraiCPString &another); MiraiCPString &operator=(MiraiCPString &&another) noexcept; }; static_assert(sizeof(char) == 1, "Please make sure the size of char type is 1"); static_assert(sizeof(MiraiCPString) == 3 * 8, "Please make sure MiraiCP is compiled under 64-bit mode."); } // namespace MiraiCP #endif //MIRAICP_PRO_MIRAICPSTRING_H #ifdef MIRAICP_LIB_LOADER constexpr int LOADERAPI_H_COUNTER_BASE = __COUNTER__ + 1; #define LOADERAPI_H_NOTHING(X) #define LOADERAPI_H_LOADER_API_INNER(X) LOADERAPI_H_NOTHING(X) #define LOADER_API_COUNT LOADERAPI_H_LOADER_API_INNER(__COUNTER__) #define LOADERAPI_H_GET_COUNTER (__COUNTER__ - LOADERAPI_H_COUNTER_BASE) #else #define LOADER_API_COUNT #endif // the API defs to be exposed namespace LibLoader::LoaderApi { using MiraiCP::MiraiCPString; LOADER_API_COUNT MiraiCPString pluginOperation(const MiraiCPString&); LOADER_API_COUNT void loggerInterface(const MiraiCPString& content, const MiraiCPString& name, long long id, int level); LOADER_API_COUNT MiraiCPString showAllPluginId(); LOADER_API_COUNT void enablePluginById(const MiraiCPString&); LOADER_API_COUNT void disablePluginById(const MiraiCPString&); LOADER_API_COUNT void enableAllPlugins(); LOADER_API_COUNT void disableAllPlugins(); LOADER_API_COUNT void loadNewPlugin(const MiraiCPString&, bool); LOADER_API_COUNT void unloadPluginById(const MiraiCPString&); LOADER_API_COUNT void reloadPluginById(const MiraiCPString&); struct interface_funcs { decltype(&pluginOperation) _pluginOperation; decltype(&loggerInterface) _loggerInterface; decltype(&showAllPluginId) _showAllPluginId; // function below can only be called by admin plugins decltype(&enablePluginById) _enablePluginById = nullptr; decltype(&disablePluginById) _disablePluginById = nullptr; decltype(&enableAllPlugins) _enableAllPlugins = nullptr; decltype(&disableAllPlugins) _disableAllPlugins = nullptr; decltype(&loadNewPlugin) _loadNewPlugin = nullptr; decltype(&unloadPluginById) _unloadPluginById = nullptr; decltype(&reloadPluginById) _reloadPluginById = nullptr; }; #ifdef MIRAICP_LIB_LOADER constexpr inline interface_funcs collect_interface_functions(bool admin) { constexpr int counter = LOADERAPI_H_GET_COUNTER; static_assert(sizeof(interface_funcs) == sizeof(void *) * counter); if (admin) { constexpr int line0 = __LINE__; interface_funcs t = { pluginOperation, loggerInterface, showAllPluginId, enablePluginById, disablePluginById, enableAllPlugins, disableAllPlugins, loadNewPlugin, unloadPluginById, reloadPluginById, }; constexpr int line1 = __LINE__; static_assert(line1 - line0 == counter + 3); return t; } else { interface_funcs t2 = { pluginOperation, loggerInterface, showAllPluginId, }; // no admin functions return t2; } } #endif } // namespace LibLoader::LoaderApi #endif //MIRAICP_PRO_LOADERAPIINTERNAL_H #define FUNC_ENTRANCE FUNC_ENTRANCE #define FUNC_EVENT FUNC_EVENT #define FUNC_EXIT FUNC_EXIT #define PLUGIN_INFO PLUGIN_INFO namespace LibLoader { typedef void *plugin_handle; /// @see @macro FUNC_ENTRANCE typedef void (*plugin_entrance_func_ptr)(const LoaderApi::interface_funcs &); /// @see @macro FUNC_EVENT typedef void (*plugin_event_func_ptr)(const char *); /// @see @macro FUNC_EXIT typedef void (*plugin_func_ptr)(); /// @see @macro PLUGIN_INFO typedef const MiraiCP::PluginConfig &(*plugin_info_func_ptr)(); } // namespace LibLoader #endif //MIRAICP_PRO_COMMONTYPES_H // Copyright (c) 2022. Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_LOADERAPIINTERNAL_H #define MIRAICP_PRO_LOADERAPIINTERNAL_H // #include "miraicpString.h" #ifdef MIRAICP_LIB_LOADER constexpr int LOADERAPI_H_COUNTER_BASE = __COUNTER__ + 1; #define LOADERAPI_H_NOTHING(X) #define LOADERAPI_H_LOADER_API_INNER(X) LOADERAPI_H_NOTHING(X) #define LOADER_API_COUNT LOADERAPI_H_LOADER_API_INNER(__COUNTER__) #define LOADERAPI_H_GET_COUNTER (__COUNTER__ - LOADERAPI_H_COUNTER_BASE) #else #define LOADER_API_COUNT #endif // the API defs to be exposed namespace LibLoader::LoaderApi { using MiraiCP::MiraiCPString; LOADER_API_COUNT MiraiCPString pluginOperation(const MiraiCPString&); LOADER_API_COUNT void loggerInterface(const MiraiCPString& content, const MiraiCPString& name, long long id, int level); LOADER_API_COUNT MiraiCPString showAllPluginId(); LOADER_API_COUNT void enablePluginById(const MiraiCPString&); LOADER_API_COUNT void disablePluginById(const MiraiCPString&); LOADER_API_COUNT void enableAllPlugins(); LOADER_API_COUNT void disableAllPlugins(); LOADER_API_COUNT void loadNewPlugin(const MiraiCPString&, bool); LOADER_API_COUNT void unloadPluginById(const MiraiCPString&); LOADER_API_COUNT void reloadPluginById(const MiraiCPString&); struct interface_funcs { decltype(&pluginOperation) _pluginOperation; decltype(&loggerInterface) _loggerInterface; decltype(&showAllPluginId) _showAllPluginId; // function below can only be called by admin plugins decltype(&enablePluginById) _enablePluginById = nullptr; decltype(&disablePluginById) _disablePluginById = nullptr; decltype(&enableAllPlugins) _enableAllPlugins = nullptr; decltype(&disableAllPlugins) _disableAllPlugins = nullptr; decltype(&loadNewPlugin) _loadNewPlugin = nullptr; decltype(&unloadPluginById) _unloadPluginById = nullptr; decltype(&reloadPluginById) _reloadPluginById = nullptr; }; #ifdef MIRAICP_LIB_LOADER constexpr inline interface_funcs collect_interface_functions(bool admin) { constexpr int counter = LOADERAPI_H_GET_COUNTER; static_assert(sizeof(interface_funcs) == sizeof(void *) * counter); if (admin) { constexpr int line0 = __LINE__; interface_funcs t = { pluginOperation, loggerInterface, showAllPluginId, enablePluginById, disablePluginById, enableAllPlugins, disableAllPlugins, loadNewPlugin, unloadPluginById, reloadPluginById, }; constexpr int line1 = __LINE__; static_assert(line1 - line0 == counter + 3); return t; } else { interface_funcs t2 = { pluginOperation, loggerInterface, showAllPluginId, }; // no admin functions return t2; } } #endif } // namespace LibLoader::LoaderApi #endif //MIRAICP_PRO_LOADERAPIINTERNAL_H // Copyright (c) 2022. Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_MIRAICPSTRING_H #define MIRAICP_PRO_MIRAICPSTRING_H #include #include namespace MiraiCP { // this class is used to ensure data consistency between dynamic libs // note: do not use this directly; // always convert to const char* or std::string before using. class MiraiCPString { using string = std::string; friend void swap(MiraiCPString &, MiraiCPString &) noexcept; private: static constexpr decltype(&::std::free) std_free_ptr = &::std::free; private: // to keep integration and safe for empty construction/deconstruction, always initialize here char *str = nullptr; size_t _size = 0; decltype(&::std::free) free_this = std_free_ptr; // specify which free() to use; ensure deconstruction is paired to construction public: bool isEmpty() const { return _size == 0; } MiraiCPString() : str(nullptr), _size(0), free_this(std_free_ptr) {} // call if _size is set to non-zero // allocate memory for str void construction(); ~MiraiCPString(); MiraiCPString(const MiraiCPString &other); MiraiCPString(MiraiCPString &&temp) noexcept; MiraiCPString(const char *char_str); MiraiCPString(const std::string &string_str); std::string toString() const { if (str == nullptr || _size == 0) return {}; return {str}; } operator std::string() const { return toString(); } // for safe destruction, DO NOT provide move convert to char* // the return value of this method can always be deleted by delete[] and is never nullptr const char *copyToCharPtr() const; bool operator==(const MiraiCPString &another) const; MiraiCPString &operator=(const MiraiCPString &another); MiraiCPString &operator=(MiraiCPString &&another) noexcept; }; static_assert(sizeof(char) == 1, "Please make sure the size of char type is 1"); static_assert(sizeof(MiraiCPString) == 3 * 8, "Please make sure MiraiCP is compiled under 64-bit mode."); } // namespace MiraiCP #endif //MIRAICP_PRO_MIRAICPSTRING_H #ifndef MIRAICP_PRO_BOT_H #define MIRAICP_PRO_BOT_H #include #include // #include "MiraiDefs.h" #ifndef MIRAICP_PRO_MIRAIDEFS_H #define MIRAICP_PRO_MIRAIDEFS_H // #define MiraiCPThrow(x) throw x.append(__FILE__, __LINE__) #define ErrorHandle(x, y) ErrorHandle0(__FILE__, __LINE__, (x), (y)) #define MIRAICP_EXCEPTION_WHERE __FILE__, __LINE__ #if defined(_MSC_VER) #define ShouldNotUse(msg) _Pragma("warning(error:4996)") [[deprecated(msg)]] _Pragma("warning(warning:4996)") #else // MSVC #if defined(__GNUC__) #define ShouldNotUse(msg) [[deprecated(msg)]] __attribute__((error(msg))) #else // GUNC #define ShouldNotUse(msg) #endif // ShouldNotUse #endif #include namespace MiraiCP { using QQID = unsigned long long; } // namespace MiraiCP #endif //MIRAICP_PRO_MIRAIDEFS_H namespace MiraiCP { class Friend; // forward declaration class Group; // forward declaration class Contact; // forward declaration /// 当前bot账号信息 class Bot { private: bool inited = false; std::string _nick; std::string _avatarUrl; public: /// 该botid QQID id; private: void check() { if (!this->inited) { refreshInfo(); this->inited = true; } } public: /*! * @brief 刷新bot信息 * @param env */ void refreshInfo(); /// 用id构建机器人 explicit Bot(QQID i) : id(i) {} /// 取好友 Friend getFriend(QQID i) const; /// 取群聊 Group getGroup(QQID groupid) const; /// 昵称 std::string nick() { check(); return this->_nick; } /// 头像下载链接 std::string avatarUrl() { check(); return this->_avatarUrl; } /// 取好友列表 std::vector getFriendList() const; /// 好友列表string形式返回,利于保存 std::string FriendListToString(); /// 取群列表 std::vector getGroupList() const; /// 群列表string形式返回,利于保存 std::string GroupListToString() const; bool operator==(const Contact &c) const; bool operator==(const Bot &b) const { return this->id == b.id; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_BOT_H #ifndef MIRAICP_PRO_CPPPLUGIN_H #define MIRAICP_PRO_CPPPLUGIN_H #include // #include "Logger.h" #ifndef MIRAICP_PRO_LOGGER_H #define MIRAICP_PRO_LOGGER_H // #include "MiraiCode.h" #ifndef MIRAICP_PRO_MIRAICODE_H #define MIRAICP_PRO_MIRAICODE_H #include namespace MiraiCP { /// MiraiCode父类, 指可以被转换成miraicode的类型 class MiraiCodeable { public: /// 返回MiraiCode virtual std::string toMiraiCode() const = 0; }; /// @brief miraicode字符串 /// @attention MiraiCode会把非miraicode组成部分(非[mirai:])转码, 输出转码前的文本用toString, 参考: https://github.com/mamoe/mirai/blob/dev/docs/Messages.md#%E8%BD%AC%E4%B9%89%E8%A7%84%E5%88%99 /// @detail 为了便捷使用,构造函数不以explicit注释 class MiraiCode : public MiraiCodeable { private: std::string content; public: /// 输出当前内容, 会自动转码 std::string toString(); /// 和toString作用一样, 不过不会自动转码 std::string toMiraiCode() const override { return content; } /// 从MiraiCodeable类型初始化一个miraicode字符串 MiraiCode(MiraiCodeable *a) { // NOLINT(google-explicit-constructor) content = a->toMiraiCode(); } /// 从文本初始化一个miraicode字符串, 根据第二个参数决定是否转码, 默认不转码 /// @attention 如果是传入文本MiraiCode,请勿转码,转码只是为了[mirai:xxx:<应该转码的部分>], 如果<应该转码>的部分里面含有'[]:,'内容,请调用Tools::escapeToMiraiCode转码 MiraiCode(const std::string &a, bool convert = false); MiraiCode operator+(MiraiCodeable *a) { return {content + a->toMiraiCode()}; } MiraiCode operator+(const std::string &a) { return {content + a}; } MiraiCode operator+(const MiraiCode &a) { return {content + a.content}; } MiraiCode operator+(MiraiCode *a) { return {content + a->content}; } MiraiCode &operator=(const std::string &a) { this->content = a; return *this; } MiraiCode plus(MiraiCodeable *a) { return {content + a->toMiraiCode()}; } MiraiCode plus(const std::string &a) { return MiraiCode(a) + this; } /// 不执行转义,适用于已经被MiraiCode转义过的字符串 static MiraiCode MiraiCodeWithoutEscape(const std::string &a) { return {a, false}; } /// 不执行转义,因为MiraiCodeable的toMiraiCode已经转义过了 static MiraiCode MiraiCodeWithoutEscape(MiraiCodeable *a) { return {a->toMiraiCode(), false}; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_MIRAICODE_H // #include "MiraiDefs.h" #include #include namespace MiraiCP { class MiraiCodeable; // forward declaration /*! * @class Logger * @brief 以MiraiCP的名义发送日志, 日志表现格式是: 2021-06-28 09:37:22 [log level]/MiraiCP: [log content], 为最底层的logger * 发送消息级日志 * @code Logger::logger.info(string) @endcode * 发送警告级日志 * @code Logger::logger.warning(string) @endcode * 发送错误级日志 * @code Logger::logger.error(string) @endcode * @doxygenEg{1011, logger.cpp, 自定义日志handle} */ class Logger_interface { using string = std::string; public: /// @brief 封装lambda类型 /// @param string 日志内容 /// @param 日志级别 /// - 0 info /// - 1 warning /// - 2 error typedef std::function Action; /// @brief loggerhandler会在每次log执行前执行一遍,可用于执行自定义的保存操作等 struct Handler { /// @brief 是否启用 bool enable = true; /// @brief 执行的操作,格式为lambda Action action = [](const string &content, int level) {}; }; Handler loggerhandler; private: static std::string constructString() { return ""; } template static std::string constructString(T val, T1... val1) { std::stringstream sstream; sstream << val; return sstream.str() + constructString(val1...); } template static std::string constructString(std::string a, T... val1) { return a + constructString(val1...); } template static std::string constructString(MiraiCodeable &val, T... val1) { return val.toMiraiCode() + constructString(val1...); } protected: /// @brief 日志底层实现封装 /// @param log 日志内容 /// @param level 日志等级 virtual void log_interface(const string &log, int level) = 0; public: ///发送普通(info级日志) template void info(T... val) { this->log_interface(constructString(val...), 0); } ///发送警告(warning级日志) template void warning(T... val) { this->log_interface(constructString(val...), 1); } ///发送错误(error级日志) template void error(T... val) { this->log_interface(constructString(val...), 2); } /// @brief 设置loggerhandler的action /// @param action 执行的操作 /// @see Logger::handler void registerHandle(Action action) { this->loggerhandler.action = std::move(action); } /// @brief 设置handler的启用状态 /// @param state 状态,启用或者关闭 /// @doxygenEg{1012, logger.cpp, 启用或关闭日志} void setHandleState(bool state) { this->loggerhandler.enable = state; } }; class Logger : public Logger_interface { private: Logger() = default; protected: /// @brief 日志底层实现封装 /// @param content 日志内容 /// @param level 日志等级 void log_interface(const std::string &content, int level) override; public: static Logger logger; }; /// 带id(一般为bot账号)的logger class IdLogger : public Logger_interface { public: QQID id; protected: void log_interface(const std::string &content, int level) override; public: IdLogger(QQID id, Logger *l) : id(id) { this->loggerhandler = l->loggerhandler; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_LOGGER_H // #include "PluginConfig.h" namespace MiraiCP { /// 插件父类 class CPPPlugin { public: // for api-compatible ShouldNotUse("请改为初始化静态常量 CPPPlugin::config") explicit CPPPlugin(const PluginConfig &c) { // 不可覆盖原本的config,这里什么都不做 } explicit CPPPlugin() = default; virtual ~CPPPlugin() = default; public: /// @brief 插件信息,一个插件中该内容不应变化 const static PluginConfig config; /// @brief 插件级logger /// @deprecated use Logger::logger instead [[deprecated("Use Logger::logger instead")]] static Logger *pluginLogger; static std::unique_ptr plugin; public: /// 插件启用时调用一次 virtual void onEnable() {} virtual void onDisable() {} }; } // namespace MiraiCP #endif //MIRAICP_PRO_CPPPLUGIN_H #ifndef MIRAICP_PRO_COMMAND_H #define MIRAICP_PRO_COMMAND_H // #include "CPPPlugin.h" // #include "Exception.h" #ifndef MIRAICP_PRO_EXCEPTION_H #define MIRAICP_PRO_EXCEPTION_H // #include "CPPPlugin.h" #include #include #include namespace MiraiCP { /// @brief 总异常抽象类,用于一般捕获,不要直接抛出该类,不知道抛出什么的时候请抛出 MiraiCPException /// @interface MiraiCPExceptionBase class MiraiCPExceptionBase : public ::std::exception { protected: using string = std::string; protected: /// @brief 异常内容 string re; public: /// @brief 发生异常的文件名 string filename; /// @brief 发生异常的行号 int lineNum = 0; protected: /// 受保护构造函数,供子类调用 MiraiCPExceptionBase(string info, string _filename, int _lineNum) : re(std::move(info)), filename(std::move(_filename)), lineNum(_lineNum) {} public: ~MiraiCPExceptionBase() override = default; public: /// 异常信息 const char *what() const noexcept override { return re.c_str(); } /// 返回std::string的异常信息 string getError() const { return re; } /// 实际抛出方法 void raise() const; public: // 暴露的接口 /// basicRaise 基本抛出方法,子类重写该方法 virtual void basicRaise() const; // CRTP实现一次,调用静态的exceptionType /// 获取异常类型,通用接口 virtual string getExceptionType() const = 0; // 每个子类需要单独实现该静态方法 /// 返回异常的类型,该静态方法无法正确实现多态,请使用 getExceptionType /// @see getExceptionType static string exceptionType() { return "MiraiCPException"; } }; /// @brief 总异常CRTP抽象类,不要直接抛出该类,不知道抛出什么的时候请抛出 MiraiCPException。 /// 该类是用于继承的基类,需要新的异常类型时,继承该类并以子类作为模板参数。 /// 子类需要实现的方法: /// 1. 构造函数,要求必须委托MiraiCPExceptionCRTP构造,其他成员需要在MiraiCPException构造前完成构造。 /// 2. `static std::string exceptionType()` 返回一个字符串表示异常类型。 /// 继承该类后异常类能正确实现多态。 /// @interface MiraiCPExceptionCRTP /// @note 请勿给该类增加新的属性。如果要增加属性应在 MiraiCPExceptionBase 中增加 template class MiraiCPExceptionCRTP : public MiraiCPExceptionBase { public: /// 委托构造函数 explicit MiraiCPExceptionCRTP(std::string _re, string _filename, int _lineNum) : MiraiCPExceptionBase(std::move(_re), std::move(_filename), _lineNum) { } public: // CRTP类型获取实现 string getExceptionType() const override { return T::exceptionType(); } }; /// @brief 通用MiraiCP异常 /// @param const string &description, string _filename, int _lineNum /// @see MiraiCPExceptionBase typedef MiraiCPExceptionCRTP MiraiCPException; /// 文件读取异常. /// @see MiraiCPExceptionBase class UploadException : public MiraiCPExceptionCRTP { public: explicit UploadException(const std::string &text, string _filename, int _lineNum) : MiraiCPExceptionCRTP("上传(图片/文件)异常" + text, std::move(_filename), _lineNum) {} static std::string exceptionType() { return "UploadException"; } }; /// 通常为Mirai返回 /// @see MiraiCPExceptionBase class IllegalStateException : public MiraiCPExceptionCRTP { public: explicit IllegalStateException(const std::string &text, string _filename, int _lineNum) : MiraiCPExceptionCRTP("状态异常:" + text, std::move(_filename), _lineNum) {} static std::string exceptionType() { return "IllegalStateException"; } }; /// 内部异常, 通常为json读写问题 /// @see MiraiCPExceptionBase class APIException : public MiraiCPExceptionCRTP { public: explicit APIException(const std::string &text, string _filename, int _lineNum) : MiraiCPExceptionCRTP("MiraiCP内部无法预料的错误:" + text, std::move(_filename), _lineNum) {} static string exceptionType() { return "APIException"; } }; /// 机器人操作异常 /// @see MiraiCPExceptionBase class BotException : public MiraiCPExceptionCRTP { public: explicit BotException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("没有权限执行该操作", std::move(_filename), _lineNum) {} explicit BotException(const string &d, string _filename, int _lineNum) : MiraiCPExceptionCRTP(d, std::move(_filename), _lineNum) {} static string exceptionType() { return "BotException"; } }; /// 被禁言异常, 通常发生于发送信息 class BotIsBeingMutedException : public MiraiCPExceptionCRTP { public: /// 剩余禁言时间, 单位秒 int timeRemain; public: explicit BotIsBeingMutedException(int t, string _filename, int _lineNum) : timeRemain(t), MiraiCPExceptionCRTP("发送信息失败, bot已被禁言, 剩余时间" + std::to_string(t), std::move(_filename), _lineNum) {} static string exceptionType() { return "BotIsBeingMutedException"; } }; /// 禁言异常 /// @see MiraiCPExceptionBase class MuteException : public MiraiCPExceptionCRTP { public: /* * 禁言时间超出0s~30d */ MuteException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("禁言时长不在0s~30d中间", std::move(_filename), _lineNum) {} static string exceptionType() { return "MuteException"; } }; /// 获取群成员错误 /// @see MiraiCPExceptionBase class MemberException : public MiraiCPExceptionCRTP { public: enum MemberExceptionType : int { OtherType, NoSuchGroup, NoSuchMember }; MemberExceptionType type = OtherType; /* * "1" - 找不到群 * "2" - 找不到群成员 */ explicit MemberException(int _type, string _filename, int _lineNum) : MiraiCPExceptionCRTP( [&]() -> string { type = MemberExceptionType(_type); switch (type) { case NoSuchGroup: return "找不到群"; case NoSuchMember: return "找不到群成员"; default: return ""; } }(), std::move(_filename), _lineNum) {} static string exceptionType() { return "MemberException"; } }; /// 获取群成员错误 /// @see MiraiCPExceptionBase class FriendException : public MiraiCPExceptionCRTP { public: /* * 找不到好友 */ FriendException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("找不到好友", std::move(_filename), _lineNum) {} static string exceptionType() { return "FriendException"; } }; /// 获取群错误 /// @see MiraiCPExceptionBase class GroupException : public MiraiCPExceptionCRTP { public: GroupException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("找不到群", std::move(_filename), _lineNum) {} static string exceptionType() { return "GroupException"; } }; /// 撤回异常 /// @see MiraiCPExceptionBase class RecallException : public MiraiCPExceptionCRTP { public: RecallException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("该消息已经被撤回", std::move(_filename), _lineNum) {} static string exceptionType() { return "RecallException"; } }; /// 远程资源出现问题 /// @see MiraiCPExceptionBase class RemoteAssetException : public MiraiCPExceptionCRTP { public: explicit RemoteAssetException(const string &e, string _filename, int _lineNum) : MiraiCPExceptionCRTP(e, std::move(_filename), _lineNum) {} static string exceptionType() { return "RemoteAssetException"; } }; /// 参数错误 /// @see MiraiCPExceptionBase class IllegalArgumentException : public MiraiCPExceptionCRTP { public: explicit IllegalArgumentException(const string &e, string _filename, int _lineNum) : MiraiCPExceptionCRTP(e, std::move(_filename), _lineNum) { } static string exceptionType() { return "IllegalArgumentException"; } }; /// 超时 /// @see MiraiCPExceptionBase class TimeOutException : public MiraiCPExceptionCRTP { public: explicit TimeOutException(const std::string &e, string _filename, int _lineNum) : MiraiCPExceptionCRTP(e, std::move(_filename), _lineNum) {} static string exceptionType() { return "TimeOutException"; } }; /// 事件被取消, 一般出现在发送消息时在preSendMessageEvent取消的时候抛出 /// @see MiraiCPExceptionBase class EventCancelledException : public MiraiCPExceptionCRTP { public: explicit EventCancelledException(const string &msg, string _filename, int _lineNum) : MiraiCPExceptionCRTP(msg, std::move(_filename), _lineNum) {} static string exceptionType() { return "EventCancelledException"; } }; /// 插件没有权限时抛出该异常 /// 该异常仅可能在插件尝试调用libLoader 高级权限的Api接口时抛出 /// 如插件尝试重载、加载、卸载插件等操作,但配置文件中并没有赋予该插件权限时 /// @see MiraiCPExceptionBase class PluginNotAuthorizedException : public MiraiCPExceptionCRTP { public: explicit PluginNotAuthorizedException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("插件" + CPPPlugin::config.getId() + "没有管理权限", std::move(_filename), _lineNum) {} static string exceptionType() { return "PluginNotAuthorizedException"; } }; /// 插件未加载抛出该异常 /// 在插件能正常运行时不会抛出,出现该异常事件时请不要再次尝试收发消息等Mirai操作, /// 否则可能导致异常处理时再次抛出异常 /// @see MiraiCPExceptionBase class PluginNotEnabledException : public MiraiCPExceptionCRTP { public: explicit PluginNotEnabledException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("插件" + CPPPlugin::config.getId() + "未加载", std::move(_filename), _lineNum) {} static string exceptionType() { return "PluginNotEnabledException"; } }; /// 如果在 MiraiCPNewThread 中捕获到了非 MiraiCP 之外的异常抛出 /// @see MiraiCPNewThread class MiraiCPThreadException : public MiraiCPExceptionCRTP { public: /// 抛出异常的线程 ID std::thread::id threadId; public: explicit MiraiCPThreadException(const std::string &exception_content, std::thread::id threadId, string _filename, int _lineNum) : MiraiCPExceptionCRTP(exception_content + " at threadId: " + getThreadIdStr(threadId), std::move(_filename), _lineNum), threadId(threadId) {} public: std::string getThreadIdStr() const { return getThreadIdStr(threadId); } public: static string exceptionType() { return "MiraiCPThreadException"; } private: static std::string getThreadIdStr(const std::thread::id &id) { std::stringstream ss; ss << id; return ss.str(); } }; inline void ErrorHandle0(const std::string &name, int line, const std::string &re, const std::string &ErrorMsg = "") { if (re == "EF") throw FriendException(name, line); if (re == "EG") throw GroupException(name, line); if (re == "EM") throw MemberException(1, name, line); if (re == "EMM") throw MemberException(2, name, line); if (re == "EB") throw BotException("找不到bot:" + re, name, line); if (re == "EA") throw APIException(ErrorMsg, name, line); if (re == "EC") throw EventCancelledException("发送信息被取消", name, line); if (re == "ET") throw TimeOutException("发送信息超时", name, line); if (re == "EP") throw BotException(name, line); // equal to Tools::start_with if (re.rfind("EBM", 0) == 0) throw BotIsBeingMutedException(std::stoi(re.substr(3)), name, line); } } // namespace MiraiCP #endif //MIRAICP_PRO_EXCEPTION_H // #include "KtOperation.h" #ifndef MIRAICP_PRO_KTOPERATION_H #define MIRAICP_PRO_KTOPERATION_H #include /// @brief 配置类声明, MiraiCP内部使用, 不需要更改或其他操作 /// @internal 一般为MiraiCP内部调用jni接口使用 /// @namespace KtOperation namespace MiraiCP::KtOperation { /// 操作id enum operation_set { /// 撤回信息 Recall, /// 发送信息 Send, /// 查询信息接口 RefreshInfo, /// 上传图片 UploadImg, /// 取好友列表 QueryBFL, /// 取群组列表 QueryBGL, /// 上传文件 SendFile, /// 查询文件信息 RemoteFileInfo, /// 查询图片下载地址 QueryImgInfo, /// 禁言 MuteM, /// 查询权限 QueryM, /// 踢出 KickM, /// 取群主 QueryOwner, /// 语音 Voice, /// 查询群成员列表 QueryML, /// 群设置 GroupSetting, /// 构建转发信息 Buildforward, /// 好友申请事件 Nfroperation, /// 群聊邀请事件 Gioperation, /// 回复(引用并发送) SendWithQuote, /// 群公告操作 Announcement, /// 定时任务 TimeOut, /// 发送戳一戳 SendNudge, /// 下一条信息 NextMsg, /// 更改权限 ModifyAdmin, /// 群成员申请入群 MemberJoinRequest, /// 图片是否已经上传 ImageUploaded, /// 注册指令 CommandReg, /// 改名称 ChangeNameCard, }; /** * @brief 调用mirai操作 * @param type 操作id * @param data 传入数据 * @return 返回数据 */ std::string ktOperation( operation_set type, nlohmann::json data, bool catchErr = true, const std::string &errorInfo = ""); } // namespace MiraiCP::KtOperation #endif //MIRAICP_PRO_KTOPERATION_H // #include "Logger.h" #include namespace MiraiCP { class MessageChain; class Bot; class Contact; /*! * @brief 指令 Interface * @doxygenEg{1001, command.cpp, 新建自定义命令} * @attention loader端的命令只支持从console传入, plugin端是对接mirai的RawCommand */ class IRawCommand { using string = std::string; public: struct Config { public: /// 指令名不能为空 string primaryName; /// 可以为空 std::vector secondNames; /// 用法 string usage = "null"; /// 描述 string description = "null"; /// 覆盖已有命令 bool overrideOrigin = false; /// 前缀`/`可省略 bool preFixOption = false; }; virtual IRawCommand::Config config() = 0; virtual void onCommand(std::optional, const Bot &, const MessageChain &) = 0; IRawCommand() = default; virtual ~IRawCommand() = default; }; class CommandManager { private: CommandManager() = default; std::vector> commandList; public: std::shared_ptr &operator[](const int &index) { return commandList[index]; } /*! * @brief 注册一条指令 * @param command 指令 * @return 是否注册成功 */ template bool registerCommand(T command) { static_assert(std::is_base_of_v, "只支持IRawCommand的派生类"); nlohmann::json j; j["pluginId"] = CPPPlugin::config.id; j["usage"] = command.config().usage; j["primaryName"] = command.config().primaryName; j["secondName"] = command.config().secondNames; j["description"] = command.config().description; j["override"] = command.config().overrideOrigin; j["preFixOption"] = command.config().preFixOption; size_t before = commandList.size(); std::shared_ptr c; c.reset(new T(command)); commandList.push_back(c); size_t now = commandList.size(); if (now - before == 1) j["bindId"] = now - 1; else { auto i = std::find(commandList.begin(), commandList.end(), c); if (i != commandList.end()) j["bindId"] = i - commandList.begin(); else throw IllegalArgumentException("找不到合适的bindId", MIRAICP_EXCEPTION_WHERE); } nlohmann::json rej; rej["command"] = j.dump(); std::string re = KtOperation::ktOperation(KtOperation::CommandReg, rej); return re == "true"; } static CommandManager commandManager; }; } // namespace MiraiCP #endif //MIRAICP_PRO_COMMAND_H #ifndef MIRAICP_PRO_CONTACT_H #define MIRAICP_PRO_CONTACT_H // #include "MessageChain.h" #ifndef MIRAICP_PRO_MESSAGECHAIN_H #define MIRAICP_PRO_MESSAGECHAIN_H // #include "Exception.h" // #include "SingleMessage.h" #ifndef MIRAICP_PRO_SINGLEMESSAGE_H #define MIRAICP_PRO_SINGLEMESSAGE_H #include #include #include #include // #include "MessageSource.h" #ifndef MIRAICP_PRO_MESSAGESOURCE_H #define MIRAICP_PRO_MESSAGESOURCE_H #include // #include "MiraiDefs.h" namespace MiraiCP { class MiraiCodeable; // forward declaration /*! 消息源声明 * @doxygenEg{1014, message.cpp, 回复信息} */ class MessageSource { public: /// 消息的ids std::string ids; /// 消息的internalids std::string internalids; /// 消息源序列化 std::string source; MessageSource() = default; /// @deprecated 用Contact.quoteAndSendMessage, since v2.8.1 ShouldNotUse("Use Contact.quoteAndSendMessage") MessageSource quoteAndSendMiraiCode(MiraiCodeable *msg, QQID groupid = 0, void *env = nullptr) const = delete; /// @deprecated use Contact.quoteAndSendMessage, since v2.8.1 ShouldNotUse("Use Contact.quoteAndSendMessage") MessageSource quoteAndSendMsg(const std::string &c, QQID groupid = 0, void * = nullptr) const = delete; /// @deprecated use Contact.quoteAndSendMessage, since v2.8.1 ShouldNotUse("Use Contact.quoteAndSendMessage") MessageSource quoteAndSendMiraiCode(const std::string &c, QQID groupid = 0, void * = nullptr) const = delete; /*! * @brief 构建消息源 * @param ids * @param internalids * @param source */ MessageSource(std::string ids, std::string internalids, std::string source); /*! * @brief 从json字符串反序列化到MessageSource对象 * @note json应该为以下格式 * @code * {"ids":"", "internalids":""} * @endcode */ static MessageSource deserializeFromString(const std::string &source); std::string serializeToString() const; /// @brief 撤回该信息 void recall() const; bool operator==(const MessageSource &ms) const { return this->ids == ms.ids && this->internalids == ms.internalids; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_MESSAGESOURCE_H // #include "MiraiCode.h" namespace MiraiCP { /// 用serviceMessage的分享信息 struct URLSharer { /// 简介 没点进来看见的样子 std::string brief = "简介 没点进来看见的样子"; /// 目标url std::string url = "目标url"; /// 图标地址 std::string cover = "图标地址"; /// 标题 std::string title = "标题"; /// 描述文字 std::string summary = "描述文字"; }; /// MessageChain的组成部分 class SingleMessage : public MiraiCodeable { public: /// MiraiCode类别 /// @see SingleMessage::messageType int type; std::string content; std::string prefix; public: static std::unordered_map messageType; public: /// @brief 构建单条 /// @param type 消息类型 @see messageType /// @param content 内容 /// @param prefix 前缀, 默认为`:`, 第二个冒号部分的内容, 目前在serviceMesage有使用 SingleMessage(int type, std::string content, std::string prefix = ":") : type(type), content(std::move(content)), prefix(std::move(prefix)) {} virtual ~SingleMessage() = default; public: /// @brief 找对应类型的index key /// @param value 类型名 /// @return 如果没找到返回-1 static int getKey(const std::string &value); public: virtual nlohmann::json toJson() const { nlohmann::json re; re["key"] = "miraicode"; re["content"] = this->toMiraiCode(); return re; } std::string toMiraiCode() const override; public: bool operator==(const SingleMessage &m) const { return this->type == m.type && this->toMiraiCode() == m.toMiraiCode(); } bool operator==(SingleMessage *m) const { return this->type == m->type && this->toMiraiCode() == m->toMiraiCode(); } }; /// 纯文本信息 class PlainText : public SingleMessage { public: explicit PlainText(const SingleMessage &sg); template explicit PlainText(const T &a) : SingleMessage(PlainText::type(), ([&a]() -> std::string { std::stringstream sst; sst << a; return sst.str(); })()) {} PlainText(PlainText &&_o) noexcept : SingleMessage(PlainText::type(), std::move(_o.content)) {} PlainText(const PlainText &_o) : SingleMessage(PlainText::type(), _o.content) {} public: static int type() { return 0; } public: std::string toMiraiCode() const override { return content; } nlohmann::json toJson() const override; bool operator==(const PlainText &p) const { return this->content == p.content; } }; /// @ class At : public SingleMessage { public: static int type() { return 1; } QQID target; nlohmann::json toJson() const override; explicit At(const SingleMessage &sg); explicit At(QQID a) : SingleMessage(At::type(), std::to_string(a)), target(a){}; std::string toMiraiCode() const override { return "[mirai:at:" + std::to_string(this->target) + "] "; // 后面有个空格 } bool operator==(const At &a) const { return this->target == a.target; } }; /// @brief \@全体 class AtAll : public SingleMessage { public: static int type() { return 2; } std::string toMiraiCode() const override { return "[mirai:atall] "; } nlohmann::json toJson() const override; AtAll() : SingleMessage(AtAll::type(), "", "") {} }; /// 图像类声明 class Image : public SingleMessage { public: static int type() { return 3; } //图片id,样式:` {xxx}.xx ` std::string id; /// 可为空, 用`refreshInfo`获取 std::optional md5; /// 可为0, 来源:用`refreshInfo`可能可以获取或者自己填充, 是isUploaded的必须条件, 默认0 size_t size; /// 可为空, 用`refreshInfo`获取 std::optional url; /// 宽度, 默认0, 单位px int width; /// 长度, 默认0, 单位px int height; /*! * @brief 图片类型 * - 0 png * - 1 bmp * - 2 jpg * - 3 gif * - 4 apng * - 5 unknown * 默认 5 */ int imageType; /*! * @brief 图片是否已经上传(如果已经上传即表明可以直接用ImageId发送, 如果没有需要手动上传) * @param md5 在kotlin端会用.toByteArray()转换 * @param size 图片大小, 不能为0 * @param botid 所属Botid * @return 是否已上传 */ bool isUploaded(QQID botid); /*! * @brief 从图片builder构造,适用于服务器上已经有的图片,即接收到的 * @param imageId 图片id, 必须 * @param size isUploaded的必要条件, 单纯用ImageId可能取不到图片size, 需要自己上传 * @param width 宽度 * @param height 长度 * @param type 图片类型 * @detail 图片miraiCode格式例子, `[mirai:image:{图片id}.jpg]` * 可以用这个正则表达式找出id `\\[mirai:image:(.*?)\\]` */ explicit Image(const std::string &imageId, size_t size = 0, int width = 0, int height = 0, int type = 5) : SingleMessage(Image::type(), imageId) { this->id = imageId; this->size = size; this->width = width; this->height = height; this->imageType = type; } explicit Image(const SingleMessage &sg); /// 刷新信息(获取图片下载Url,md5, size) void refreshInfo(); /// 取图片Mirai码 std::string toMiraiCode() const override { return "[mirai:image:" + this->id + "]"; } nlohmann::json toJson() const override; static Image deserialize(const std::string &); bool operator==(const Image &i) const { return this->id == i.id; } }; /// 闪照, 和Image属性类似 class FlashImage : public Image { public: static int type() { return 8; } std::string toMiraiCode() const override { return "[mirai:flash:" + this->id + "]"; } explicit FlashImage(const std::string &imageId, size_t size = 0, int width = 0, int height = 0, int type = 0) : Image(imageId, size, width, height, type) { this->SingleMessage::type = 8; } explicit FlashImage(const SingleMessage &sg) : Image(sg) {} explicit FlashImage(const Image &img) : Image(img) {} nlohmann::json toJson() const override; static FlashImage deserialize(const std::string &); bool operator==(const FlashImage &i) const { return this->id == i.id; } /// 转换到普通图片 Image toImage() { return Image(id, size, width, height, imageType); } }; /*! * @brief 小程序卡片 * @attention 自带的模板不稳定,可能发出现没有效果 * @doxygenEg{1015, lightApp.cpp, 从文本构建LightApp} */ class LightApp : public SingleMessage { public: static int type() { return 4; } /// @brief 使用纯文本构造,推荐使用其他结构体方法构造 /// @param content 构造文本 explicit LightApp(std::string content) : SingleMessage(LightApp::type(), std::move(content)) {} explicit LightApp(const SingleMessage &sg); nlohmann::json toJson() const override; /// 返回miraicode std::string toMiraiCode() const override; bool operator==(const LightApp &la) const { return this->content == la.content; } }; /// xml格式的超文本信息 /// @attention 自带的模板不稳定,可能发出现没有效果 class ServiceMessage : public SingleMessage { public: static int type() { return 5; } nlohmann::json toJson() const override; std::string toMiraiCode() const override; int id; /// @brief ServiceMessage /// @param id 在xml内容前面的id (不包括逗号) /// @param a xml内容 (不需要事先转码到miraiCode) explicit ServiceMessage(int id, std::string a) : SingleMessage(ServiceMessage::type(), std::move(a), ":" + std::to_string(id) + ','), id(id) {} explicit ServiceMessage(const SingleMessage &sg); explicit ServiceMessage(const URLSharer &a) : SingleMessage(5, "" + a.title + "" + a.summary + "", ":1,"), id(1) {} bool operator==(const ServiceMessage &s) const { return this->content == s.content; } }; /// 引用信息 class QuoteReply : public SingleMessage { public: static int type() { return -2; } // 不可直接发送, 发送引用信息用MessageChain.quoteAndSendMessage ShouldNotUse("don't have MiraiCode, use MessageChain.quote instead") std::string toMiraiCode() const override { return ""; } /// 引用信息的MessageSource MessageSource source; explicit QuoteReply(const SingleMessage &m); explicit QuoteReply(MessageSource source) : SingleMessage(QuoteReply::type(), source.serializeToString()), source(std::move(source)){}; bool operator==(const QuoteReply &qr) const { return this->source == qr.source; } }; /// 接收到的音频文件, 发送用`Contact.sendAudio` class OnlineAudio : public SingleMessage { public: static int type() { return -3; } /// 文件名 std::string filename; /// 下载地址 std::string url; /// 文件大小 int size; /// 编码方式 int codec; /// 时长(单位s) int length; /// 16位md5 std::array md5; /// 不支持直接发送, 用Contact.sendAudio ShouldNotUse("cannot use, use Contact.sendAudio") std::string toMiraiCode() const override { return ""; } explicit OnlineAudio(std::string f, std::array md5, int size, int codec, int length, std::string url) : SingleMessage(OnlineAudio::type(), ""), filename(std::move(f)), md5(md5), size(size), codec(codec), length(length), url(std::move(url)){}; bool operator==(const OnlineAudio &oa) const { return this->md5 == oa.md5; } }; /// @brief 远程(群)文件类型 class RemoteFile : public SingleMessage { public: static int type() { return 6; } /// @brief 下载信息 /// @see RemoteFile struct Dinfo { /// 下载地址, 可能会是 `null` 当文件不存在 std::string url; /// md5 可用于校验 std::string md5; /// sha1 可用于校验 std::string sha1; }; /// @brief 文件信息 /// @see RemoteFile struct Finfo { /// 文件大小 QQID size; /// 上传者id QQID uploaderid; /// 过期时间 long expirytime; /// 上传时间, 时间戳格式 QQID uploadtime; /// 上次更改时间, 时间戳格式 QQID lastmodifytime; }; /// 文件唯一id, 用于识别 std::string id; /// 文件内部id, 用于构造miraiCode发送 unsigned int internalid; /// 文件名 std::string name; /// 文件大小 long long size; /// 文件在群文件的路径 /// @attention 可能为空(通常出现于MessageChain从MiraiCode反序列化), 需要从Group重新获取文件 /// @see Group::getFileByFile std::optional path; /// 文件下载信息 /// @attention 可能为空(常出现于MessageChain从MiraiCode反序列化), 如果为空需要从Group重新获取 /// @see MiraiCP::Dinfo, Group::getFileByFile std::optional dinfo; /// 文件信息 /// @attention 可能为空(常出现于MessageChain从MiraiCode反序列化), 如果为空需要从Group重新获取 /// @see MiraiCP::Finfo, Group::getFileByFile std::optional finfo; std::string serializeToString(); RemoteFile plus(unsigned int ii); static RemoteFile deserializeFromString(const std::string &source); /*! * @brief 构造远程(群)文件 * @param i ids * @param ii internalids * @param n name * @param s size * @param p path * @param d dinfo * @param f finfo */ explicit RemoteFile(const std::string &i, unsigned int ii, std::string n, long long s, std::string p, struct Dinfo d, struct Finfo f); /*! * @brief 构造远程(群)文件 * @param i ids * @param ii internalids * @param n name * @param s size * @param p path * @param d dinfo * @param f finfo */ explicit RemoteFile(const std::string &i, unsigned int ii, std::string n, long long s); /// 上传后会自动发送 ShouldNotUse("Cannot send manually, use Group.sendFile") std::string toMiraiCode() const override { return ""; } bool operator==(const RemoteFile &rf) const { return this->id == rf.id; } }; /// 自带表情 /// @attention 有些表情会变成PlainText类型和\\xxx 的格式 class Face : public SingleMessage { public: static int type() { return 7; } int id; nlohmann::json toJson() const override; std::string toMiraiCode() const override { return "[mirai:face:" + std::to_string(id) + "]"; } explicit Face(int id) : SingleMessage(Face::type(), std::to_string(id)), id(id) {} bool operator==(const Face &f) const { return this->id == f.id; } }; /// 一些可以被mirai识别的音乐卡片, 如果不能被mirai识别, 那应该被表现成lightApp类型(可能收费/vip歌曲用lightApp, 免费用MusicShare) class MusicShare : public SingleMessage { public: static int type() { return 9; } /// 应用名称, 如NeteaseCloudMusic std::string appName; /// 歌名 std::string title; /// 卡片第二行的文字内容 std::string summary; /// 点击跳转到的链接 std::string jumpUrl; /// 图片链接 std::string picUrl; /// 音乐文件链接 std::string musicUrl; /// 简介, 点进聊天节目前显示的小文字, 一般是`分享` std::string brief; std::string toMiraiCode() const override { return "[mirai:musicshare:" + appName + "," + title + "," + summary + "," + jumpUrl + "," + picUrl + "," + musicUrl + "," + brief + "]"; } MusicShare(const std::string &appName, const std::string &title, const std::string &summary, const std::string &jumpUrl, const std::string &picUrl, const std::string &musicUrl, const std::string &brief) : SingleMessage(MusicShare::type(), ""), appName(appName), title(title), summary(summary), jumpUrl(jumpUrl), picUrl(picUrl), musicUrl(musicUrl), brief(brief) {} }; class MarketFace : public SingleMessage { public: static int type() { return -5; } /// 目前无法直接发送MarketFace, 可以转发 ShouldNotUse("暂不支持直接发送") std::string toMiraiCode() const override { return ""; } std::array faceId; explicit MarketFace(std::array id) : SingleMessage(MarketFace::type(), ""), faceId(id) {} bool operator==(const MarketFace &mf) const { return this->faceId == mf.faceId; } }; /// @brief 目前不支持的消息类型, 不支持发送 class UnSupportMessage : public SingleMessage { public: static int type() { return -1; } nlohmann::json toJson() const override; /// 不支持发送 ShouldNotUse("不支持直接发送UnSupportMessage") std::string toMiraiCode() const override { return ""; } explicit UnSupportMessage(const SingleMessage &s) : SingleMessage(s){}; explicit UnSupportMessage(const std::string &content) : SingleMessage(UnSupportMessage::type(), content) {} bool operator==(const UnSupportMessage &m) const { return this->content == m.content; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_SINGLEMESSAGE_H namespace MiraiCP { class MessageSource; // forward declaration namespace internal { class Message : public std::shared_ptr { private: // std::shared_ptr content; public: // constructor template explicit Message(const T &_singleMessage) { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); reset(new T(_singleMessage)); } explicit Message(std::shared_ptr msgptr) : std::shared_ptr(std::move(msgptr)) {} public: /// 代表的子类 /// @see MessageChain::messageType int type() const { return (*this)->type; }; /// 取指定类型 /// @throw IllegalArgumentException template T get() const { static_assert(std::is_base_of_v, "只支持SingleMessage的派生类"); if (T::type() != this->type()) throw IllegalArgumentException("cannot convert from " + SingleMessage::messageType[this->type()] + " to " + SingleMessage::messageType[T::type()], MIRAICP_EXCEPTION_WHERE); T *re = static_cast(std::shared_ptr::get()); if (re == nullptr) throw IllegalArgumentException("cannot convert from " + SingleMessage::messageType[this->type()] + " to " + SingleMessage::messageType[T::type()], MIRAICP_EXCEPTION_WHERE); return *re; } std::string toMiraiCode() const { return (*this)->toMiraiCode(); } bool operator==(const Message &m) const { return (*this)->type == m->type && (*this)->toMiraiCode() == m->toMiraiCode(); } bool operator!=(const Message &m) const { return (*this)->type != m->type || (*this)->toMiraiCode() != m->toMiraiCode(); } }; } // namespace internal /// 消息链, 一般由SingleMessage组成 class MessageChain : public std::vector, public MiraiCodeable { public: // typedefs using Message = internal::Message; public: /// 如果由MiraiCP构造(incoming)就会存在,否则则不存在 std::optional source = std::nullopt; public: MessageChain(const MessageChain &_o) = default; MessageChain(MessageChain &&_o) = default; /// incoming构造器 template explicit MessageChain(MessageSource ms, T... args) : source(std::move(ms)) { this->constructMessages(args...); }; /*! * @brief 从多个参数构建MessageChain * @tparam T 多个传入参数的类型 * 支持以下类型: * - std::string / const char* 相当于传入PlainText * - SingleMessage的派生类 * @param args 参数本身 */ template explicit MessageChain(T... args) { constructMessages(args...); }; /// outcoming 构造器 template explicit MessageChain(const T &msg) { static_assert(std::is_base_of_v, "只支持SingleMessage子类"); emplace_back(msg); }; public: [[deprecated("MessageChain继承自std::vector,无需获取内部vector")]] const std::vector &vector() const { return *static_cast *>(this); } std::string toMiraiCode() const override; std::vector toMiraiCodeVector() const { std::vector tmp; for (auto &&a: *this) tmp.emplace_back(a->toMiraiCode()); return tmp; } /// @brief 添加元素 /// @tparam T 任意的SingleMessage的子类 /// @param a 添加的值 template void add(const T &a) { static_assert(std::is_base_of_v, "只接受SingleMessage的子类"); emplace_back(a); } void add(const MessageSource &val) { source = val; } /// 筛选出某种类型的消息 template std::vector filter() { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); std::vector re; for (auto &&a: *this) { if (a.type() == T::type()) re.emplace_back(a.get()); } return re; } /// 自定义筛选器 template std::vector filter(const std::function &func) { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); std::vector re; for (auto &&a: *this) { if (func(a)) re.push_back(a.get()); } return re; } /// 找出第一个指定的type的消息,消息可能不存在 template std::optional first() { for (auto &&a: *this) if (a.type() == T::type()) return a.get(); return std::nullopt; } template [[nodiscard]] MessageChain plus(const T &a) const { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); MessageChain tmp(*this); tmp.emplace_back(a); return tmp; } [[nodiscard]] MessageChain plus(const MessageChain &mc) const { MessageChain tmp(*this); tmp.insert(tmp.end(), mc.begin(), mc.end()); return tmp; } [[nodiscard]] MessageChain plus(const MessageSource &ms) const { MessageChain tmp(*this); tmp.source = ms; return tmp; } template MessageChain operator+(const T &msg) const { return this->plus(msg); } bool operator==(const MessageChain &mc) const { if (size() != mc.size()) return false; for (size_t i = 0; i < size(); i++) { if ((*this)[i] != mc[i]) return false; } return true; } bool operator!=(const MessageChain &mc) const { return !(*this == mc); } bool empty() const { return std::vector::empty() || toMiraiCode().empty(); } /// @brief 回复并发送 /// @param s 内容 /// @param groupid 如果是来源于TempGroupMessage就要提供(因为要找到那个Member) /// @note 可以改MessageSource里的内容, 客户端在发送的时候并不会校验MessageSource的内容正确性(比如改originalMessage来改引用的文本的内容, 或者改id来定位到其他信息) /// @detail 支持以下类型传入 /// - std::string / const char* 相当于传入PlainText(str) /// - SingleMessage的各种派生类 /// - MessageChain /// @deprecated use Contact.quoteAndSend or `this->quoteAndSend1(s, groupid, env)`, since v2.8.1 template ShouldNotUse("use Contact.quoteAndSend") MessageSource quoteAndSendMessage(T s, QQID groupid = -1, void *env = nullptr) = delete; public: // static functions /// @brief 找到miraiCode结尾的`]` /// @param s 文本 /// @param start 开始位置 /// @return 如果不存在返回-1, 存在则返回index static size_t findEnd(const std::string &s, size_t start) { size_t pos = start; while (pos < s.length()) { switch (s[pos]) { case '\\': pos += 2; continue; case ']': return pos; } pos++; } return -1; } /// 从miraicode string构建MessageChain static MessageChain deserializationFromMiraiCode(const std::string &m); static MessageChain deserializationFromMessageSourceJson(const std::string &msg, bool origin = true) { return deserializationFromMessageSourceJson(nlohmann::json::parse(msg), origin); } /// 从MessageSource json中构建MessageChain, 常用于Incoming message /// @attention 本方法并不会自动附加MessageSource到MessageChain, 需要用.plus方法自行附加 static MessageChain deserializationFromMessageSourceJson(const nlohmann::json &j, bool origin = true); private: // private methods void constructMessages() {} template void constructMessages(T1 h, T2... args) { static_assert(std::is_base_of_v, "只支持SingleMessage子类"); emplace_back(h); constructMessages(args...); } template void constructMessages(const std::string &h, T2... args) { emplace_back(PlainText(h)); constructMessages(args...); } template void constructMessages(const char *h, T2... args) { emplace_back(PlainText(h)); constructMessages(args...); } template void constructMessages(const MessageChain &mc, T... args) { insert(end(), mc.begin(), mc.end()); constructMessages(args...); } MessageSource quoteAndSend0(std::string msg, QQID groupid = -1); template MessageSource quoteAndSend1(T s, QQID groupid = -1) { static_assert(std::is_base_of_v, "只支持SingleMessage的派生类"); return this->quoteAndSend0(s.toMiraiCode(), groupid); } MessageSource quoteAndSend1(std::string s, QQID groupid) { return this->quoteAndSend0(std::move(s), groupid); } MessageSource quoteAndSend1(const MessageChain &mc, QQID groupid) { return this->quoteAndSend0(mc.toMiraiCode(), groupid); } }; } // namespace MiraiCP #endif //MIRAICP_PRO_MESSAGECHAIN_H #include #include namespace MiraiCP { enum contactType { MIRAI_CONTACT = 0, MIRAI_FRIEND = 1, MIRAI_GROUP = 2, MIRAI_MEMBER = 3, MIRAI_OTHERTYPE = 4, }; /*! * @brief group, friend, member的父类 * @doxygenEg{1002, message.cpp, 发送以及回复群消息} */ class Contact { protected: // attrs contactType _type; QQID _id; QQID _groupid; std::string _nickOrNameCard; std::string _avatarUrl; QQID _botid; bool _anonymous = false; protected: /// 发送语音 MessageSource sendVoice0(const std::string &path); public: // constructors /*! * @brief 无参初始化Contact类型 * @internal 一般在MiraiCp内部构造 */ Contact() { this->_type = MIRAI_CONTACT; this->_id = 0; this->_groupid = 0; this->_nickOrNameCard = ""; this->_botid = 0; } /*! * @brief 构造contact类型 * @param type 类型 * @see Contact::type() * @param id ID * @see Contact::id() * @param gid 是member的时候是群id,否则为0 * @see Contact::groupid * @param name 群名片或昵称或群名 * @see Contact::name() * @param botid 对应的botid */ explicit Contact(int type, QQID id, QQID gid, const std::string &name, QQID botid, bool anonymous = false) { if (type < 0 || type > 4) throw APIException("Contact::type incorrect", MIRAICP_EXCEPTION_WHERE); this->_type = static_cast(type); this->_id = id; this->_groupid = gid; this->_nickOrNameCard = name; this->_botid = botid; this->_anonymous = anonymous; }; // Contact(Contact &&c) : _type(c._type), _id(c._id), _groupid(c._groupid), _botid(c._botid), _anonymous(c._anonymous), _nickOrNameCard(std::move(c._nickOrNameCard)), _avatarUrl(std::move(c._avatarUrl)) { // } // destructor virtual ~Contact() = default; bool operator==(const Contact &c) const { return this->id() == c.id(); } /// @brief 当前对象类型 /// @see contactType /// - 1 Friend 好友 /// - 2 Group 群聊 /// - 3 Member 群成员 contactType type() const { return this->_type; } /// @brief id在全部情况存在 /// - 当当前type为1(Friend)时,为好友id /// - 当当前type为2(Group)时,为群id /// - 当当前type为3(Member)时,为群成员id QQID id() const { return this->_id; } /// @brief 当type为3的时候存在,否则为0,可以看作补充id /// - 当当前type为1(Friend)时,为0 /// - 当当前type为2(Group)时,为0 /// - 当当前type为3(Member)时,为群号 /// @attention 当当前type为2(Group)时,为0,不为群号,id才是群号 QQID groupid() const { return this->_groupid; } /// 群名称,群成员群名片,或好友昵称 std::string nickOrNameCard() const { return this->_nickOrNameCard; }; /// 头像url地址 std::string avatarUrl() const { return this->_avatarUrl; }; /// 所属bot QQID botid() const { return this->_botid; }; public: // serialization /// 序列化到json对象 nlohmann::json toJson() const { nlohmann::json j; j["type"] = type(); j["id"] = id(); j["groupid"] = groupid(); j["nickornamecard"] = nickOrNameCard(); j["botid"] = botid(); return j; } /// @deprecated since v2.8.1, use `this->toJson()` ShouldNotUse("use toJson") nlohmann::json serialization() const = delete; /// 序列化成文本,可以通过deserializationFromString反序列化,利于保存 /// @see Contact::fromString() std::string toString() const { return this->toJson().dump(); } /// @deprecated since v2.8.1, use `this->toString()` ShouldNotUse("use toString") std::string serializationToString() const = delete; /// 反序列化成bot,可以通过serializationToString序列化,利于保存 /// @see Contact::serializationToString() /// @param source 序列化后的文本 /// @throw APIException static Contact deserialize(const std::string &source); static Contact deserialize(nlohmann::json source); public: /// @deprecated since v2.8.1, use `Contact::deserialize(source)` ShouldNotUse("use deserialize") static Contact deserializationFromString(const std::string &source) = delete; /// @deprecated since v2.8.1, use `sendMessage(MiraiCode)` or `sendMsg0(msg.toMiraiCode(), retryTime, true, env)` ShouldNotUse("Use sendMessage") MessageSource sendMiraiCode(const MiraiCode &msg, int retryTime = 3, void *env = nullptr) const = delete; /*! * @brief 回复并发送 * @param s 内容 * @detail 支持以下类型传入 * - std::string / const char* 相当于传入PlainText(str) * - SingleMessage的各种派生类 * - MessageChain * @param ms 回复的信息的MessageSource * @note 可以改MessageSource里的内容, 客户端在发送的时候并不会校验MessageSource的内容正确性(比如改originalMessage来改引用的文本的内容, 或者改id来定位到其他信息) */ template MessageSource quoteAndSendMessage(T s, MessageSource ms) { return this->quoteAndSend1(s, ms); } /*! * @brief 回复并发送 * @param s 内容 * @param groupid 如果是来源于TempGroupMessage就要提供(因为要找到那个Member) * @note 可以改MessageSource里的内容, 客户端在发送的时候并不会校验MessageSource的内容正确性(比如改originalMessage来改引用的文本的内容, 或者改id来定位到其他信息) * @detail 支持以下类型传入 * - std::string / const char* 相当于传入PlainText(str) * - SingleMessage的各种派生类 * - MessageChain */ template MessageSource quoteAndSendMessage(MessageSource ms, T... val) { return this->quoteAndSendMessage(MessageChain(val...), std::move(ms)); } /*! * @brief 发送信息 * @tparam T 类型 * 支持: * - SingleMessage的派生类 * - MessageChain * - std::string / const char* 相当于发送PlainText() * @param msg 内容 * @return MessageSource */ template MessageSource sendMessage(T... msg) { return this->sendMessage(MessageChain(msg...)); } /// @brief 发送一条Message /// @detail 支持 /// - std::string: 相当于发送PlainText(str) /// - MiraiCode 相当于发送反序列化MiraiCode后的 /// - 各种SingleMessage的派生类 /// - MessageChain /// @param msg Message /// @param retryTime 重试次数 /// @return MessageSource template MessageSource sendMessage(T msg, int retryTime = 3) { return this->send1(msg, retryTime); } /// @deprecated since v2.8.1, use `sendMessage(msg)` or `sendMsg0(msg, retryTime, false, env)` ShouldNotUse("Use sendMessage") MessageSource sendMsg(const std::string &msg, int retryTime = 3, void *env = nullptr) = delete; /// @deprecated since v2.8.1, use `sendMessage(MiraiCode)` or `sendMsg0(msg.toMiraiCode(), retryTime, false, env);` ShouldNotUse("Use sendMessage") MessageSource sendMsg(const MiraiCode &msg, int retryTime = 3, void *env = nullptr) = delete; /// @deprecated since v2.8.1, use `sendMessage(Tools::VectorToString(std::move(msg)))` or `sendMsg0(Tools::VectorToString(std::move(msg)), retryTime, false, env);` ShouldNotUse("Use sendMessage") MessageSource sendMsg(std::vector msg, int retryTime = 3, void *env = nullptr) = delete; /*! * @brief 上传本地图片,务必要用绝对路径 * 由于mirai要区分图片发送对象,所以使用本函数上传的图片只能发到群 * @attention 最大支持图片大小为30MB * @throws * -可能抛出UploadException异常代表路径无效或大小大于30MB * -可能抛出MemberException找不到群或群成员 */ Image uploadImg(const std::string &path) const; FlashImage uploadFlashImg(const std::string &path) const; template T to() { static_assert(std::is_base_of_v); return T(*this); } private: // private methods /// 发送纯文本信息 /// @throw IllegalArgumentException, TimeOutException, BotIsBeingMutedException MessageSource sendMsg0(const std::string &msg, int retryTime, bool miraicode = false) const; template MessageSource send1(T msg, int retryTime) { static_assert(std::is_base_of_v, "只支持SingleMessage的派生类"); return sendMsg0(msg.toMiraiCode(), retryTime, true); } MessageSource send1(MessageChain msg, int retryTime) { return sendMsg0(msg.toMiraiCode(), retryTime, true); } MessageSource send1(MiraiCode msg, int retryTime) { return sendMsg0(msg.toMiraiCode(), retryTime, true); } MessageSource send1(std::string msg, int retryTime) { return sendMsg0(msg, retryTime, false); } MessageSource send1(const char *msg, int retryTime) { return sendMsg0(std::string(msg), retryTime, false); } MessageSource quoteAndSend0(const std::string &msg, MessageSource ms); template MessageSource quoteAndSend1(T s, MessageSource ms) { static_assert(std::is_base_of_v, "只支持SingleMessage的派生类"); return this->quoteAndSend0(s.toMiraiCode(), ms); } MessageSource quoteAndSend1(std::string s, MessageSource ms) { return this->quoteAndSend0(s, ms); } MessageSource quoteAndSend1(MessageChain mc, MessageSource ms) { return this->quoteAndSend0(mc.toMiraiCode(), ms); } }; class INudgeSupport { public: /*! * @brief 发送戳一戳 * @warning 仅限Friend, Member类调用 * @see MiraiCP::Friend::sendNudge, MiraiCP::Member::sendNudge * @throw MiraiCP::BotException, MiraiCP::IllegalStateException */ virtual void sendNudge() = 0; }; } // namespace MiraiCP #endif //MIRAICP_PRO_CONTACT_H #ifndef MIRAICP_PRO_EVENT_H #define MIRAICP_PRO_EVENT_H // #include "Bot.h" // #include "Friend.h" #ifndef MIRAICP_PRO_FRIEND_H #define MIRAICP_PRO_FRIEND_H // #include "Contact.h" namespace MiraiCP { /// 好友类声明 class Friend : public Contact, INudgeSupport { public: /// 删除好友(delete是C++关键字) void deleteFriend(); void refreshInfo(); /*! * @brief 发送戳一戳 * @warning 发送戳一戳的前提是登录该bot的协议是android_phone/ipad, 否则抛出IllegalStateException * @throw MiraiCP::BotException, MiraiCP::IllegalStateException */ void sendNudge() override; /*! * @brief 构建好友对象 * @param friendid q号 * @param botid 对应机器人id */ explicit Friend(QQID friendid, QQID botid); explicit Friend(const Contact &c) : Contact(c) { if (c.type() != 1) throw IllegalArgumentException("无法从 type==" + std::to_string(c.type()) + " 转为 type == 1(friend)", MIRAICP_EXCEPTION_WHERE); refreshInfo(); }; }; } // namespace MiraiCP #endif //MIRAICP_PRO_FRIEND_H // #include "Group.h" #ifndef MIRAICP_PRO_GROUP_H #define MIRAICP_PRO_GROUP_H // #include "Contact.h" #include "json.hpp" namespace MiraiCP { class Member; // forward declaration /*! * @detail 群聊类声明 */ class Group : public Contact { public: // member classes and structs /// 群公告参数 class AnnouncementParams { public: /// 发送给新成员 bool send2new; /// 需要确认 bool requireConfirm; /// 置顶 bool pinned; /// 引导群成员修改群名片 bool showEditCard; /// 显示弹窗 bool showPopup; /// 序列化到文本 nlohmann::json serializeToJson(); explicit AnnouncementParams(bool send2New = false, bool requireConfirm = false, bool pinned = false, bool showEditCard = false, bool showPopup = false) : send2new(send2New), requireConfirm( requireConfirm), pinned(pinned), showEditCard(showEditCard), showPopup(showPopup) {} }; /// 在线群公告 class OnlineAnnouncement { public: /// 内容 std::string content; /// 所属bot QQID botid; /// 公告属性 AnnouncementParams params; /// 所在群id QQID groupid; /// 发送者id QQID senderid; /// 发送时间戳 long long publicationTime; /// 唯一识别属性 std::string fid; /// 如果需要确认,即为确认的人数 int confirmNum; /// 图片id, 如果不存在即为空 std::string imageid; /// 删除当前群公告 /// @throw BotException void deleteThis(); /// 反序列化 static OnlineAnnouncement deserializeFromJson(const nlohmann::json &); OnlineAnnouncement(const std::string &content, AnnouncementParams ¶ms, QQID groupid, QQID senderid, QQID botid, long long int publicationTime, const std::string &fid, int confirmNum, const std::string &imageid) : content(content), params(params), groupid(groupid), senderid(senderid), botid(botid), publicationTime(publicationTime), fid(fid), confirmNum(confirmNum), imageid(imageid) {} }; /// 本地(未发送)群公告 class OfflineAnnouncement { public: /// 内容 std::string content; /// 公告属性 AnnouncementParams params; /// 发布群公告 Group::OnlineAnnouncement publishTo(const Group &); OfflineAnnouncement(const std::string &content, AnnouncementParams params) : content(content), params(params) {} }; /** * @brief 群设置 * @details 使用uploadSetting上传设置,使用refreshInfo同步服务器设定,后面两项由于https://github.com/mamoe/mirai/issues/1307 还不能改 */ struct GroupSetting { /// 群名称 std::string name; /// 禁言全部 bool isMuteAll{}; /// 允许群成员邀请 bool isAllowMemberInvite{}; /// 自动同意进群 bool isAutoApproveEnabled{}; /// 允许匿名聊天 bool isAnonymousChatEnabled{}; }; /// 群文件的简短描述 struct file_short_info { // 路径带文件名 std::string path; // 唯一id std::string id; }; public: // attrs /// 群设置 GroupSetting setting; public: // constructors /// @brief 构建以群号构建群对象 /// @param groupid 群号 /// @param botid 机器人id /// @doxygenEg{1007, group.cpp, 从群号构建群对象} Group(QQID groupid, QQID botid); explicit Group(const Contact &c) : Contact(c) { if (c.type() != 2) throw IllegalArgumentException("无法从 type==" + std::to_string(c.type()) + " 转为 type == 2(group)", MIRAICP_EXCEPTION_WHERE); refreshInfo(); } public: // methods /** * @brief 更新群设置, 即覆盖服务器上的群设置 * @details 从服务器拉去群设置用refreshInfo * @see Group::refreshInfo() */ void updateSetting(); /// 取群成员列表 /// @return vector std::vector getMemberList(); /*! * 以string格式取群成员列表 * @note 格式: * 每个群成员id间用逗号分隔 */ std::string MemberListToString(); /// 取群主 Member getOwner(); /// 取群成员 Member getMember(QQID memberid); Member operator[](QQID id); /// 取群公告列表 std::vector getAnnouncementsList(); /// 刷新群聊信息 void refreshInfo(); void quit(); /*! @brief 上传并发送远程(群)文件 @param path-群文件路径(带文件名),根目录为/ @param filepath-本地文件路径 @attention 路径分隔符是 `/` @doxygenEg{1008, group.cpp, 发送群文件} */ RemoteFile sendFile(const std::string &path, const std::string &filepath); /// 发送语音 MessageSource sendVoice(const std::string &path) { return Contact::sendVoice0(path); } /*! 取群文件信息,会自动搜索子目录 @param path-群文件路径(不带文件名) @param id-文件id,可空,空则为用路径查找(此时路径要带文件名) @attention 因为群文件允许重名文件存在的特性,如果没有id该查找并不可靠,只能返回重名文件中的其中一个文件 @see RemoteFile @doxygenEg{1009, group.cpp, 获取群文件} */ RemoteFile getFile(const std::string &path, const std::string &id = ""); /*! * @brief 取文件信息(根据id) * @param id 文件id * @return 文件 * @detail 相当于从根目录开始遍历查找文件, 相当于getFile("/", id); */ RemoteFile getFileById(const std::string &id); RemoteFile getFileByFile(const RemoteFile &file) { return getFileById(file.id); } /*! * 获取path路径下全部文件信息 * @param path - 远程路径 * @return 返回值为一个vector容器, 每一项为short_info * @doxygenEg{1010, group.cpp, 获取群文件列表} */ std::vector getFileList(const std::string &path); /// 取文件列表以字符串形式返回 /// @param path 文件夹路径 std::string getFileListString(const std::string &path); }; } // namespace MiraiCP #endif //MIRAICP_PRO_GROUP_H // #include "Logger.h" // #include "Member.h" #ifndef MIRAICP_PRO_MEMBER_H #define MIRAICP_PRO_MEMBER_H // #include "Contact.h" namespace MiraiCP { /*! * @brief 群成员类声明 * @doxygenEg{1013, member.cpp, 群成员操作} */ class Member : public Contact, INudgeSupport { public: /// @brief 权限等级 /// - OWNER群主 为 2 /// - ADMINISTRATOR管理员 为 1 /// - MEMBER群成员 为 0 /// @note 上面那些变量在constants.h中有定义 unsigned int permission = 0; /// @brief 更改群成员权限 /// @param admin 如果为true为更改到管理员 /// @param env void modifyAdmin(bool admin); /// @brief 构建群成员对象 /// @param qqid 该成员q号 /// @param groupid 所在群号 /// @param botid 机器人id explicit Member(QQID qqid, QQID groupid, QQID botid); explicit Member(const Contact &c) : Contact(c) { if (c.type() != 3) throw IllegalArgumentException("无法从 type==" + std::to_string(c.type()) + " 转为 type == 3(member)", MIRAICP_EXCEPTION_WHERE); this->isAnonymous = this->_anonymous; refreshInfo(); }; /// 是否是匿名群成员, 如果是匿名群成员一些功能会受限 bool isAnonymous = false; /// 重新获取(刷新)群成员信息 void refreshInfo(); /// 发送语音 MessageSource sendVoice(const std::string &path) { return Contact::sendVoice0(path); } /// 获取权限,会在构造时调用,请使用permission缓存变量 /// @see Member::permission unsigned int getPermission() const; /*! * 禁言当前对象,单位是秒,最少0秒最大30天,如果为0或者为负则unmute * @throws BotException, MuteException */ void mute(int time); /// 取消禁言 /// @throws BotException, MuteException void unMute() { mute(0); } /*! 踢出这个群成员 * @param reason - 原因 */ void kick(const std::string &reason); /// At一个群成员 At at() { return At(this->id()); } /// 更改群名片 /// @throw MiraiCP::BotException 如果没权限时 void changeNameCard(std::string_view newName); /*! * @brief 发送戳一戳 * @warning 发送戳一戳的前提是登录该bot的协议是android_phone/ipad, 否则抛出IllegalStateException * @throw MiraiCP::BotException, MiraiCP::IllegalStateException */ void sendNudge() override; }; } // namespace MiraiCP #endif //MIRAICP_PRO_MEMBER_H namespace MiraiCP { /// Event 工厂 namespace eventTypes { enum Types { BaseEvent [[maybe_unused]], // 0 GroupMessageEvent, // 1 PrivateMessageEvent, // 2 GroupInviteEvent, // 3 NewFriendRequestEvent, // 4 MemberJoinEvent, // 5 MemberLeaveEvent, // 6 RecallEvent, // 7 BotJoinGroupEvent, // 8 GroupTempMessageEvent, // 9 TimeOutEvent, // 10 BotOnlineEvent, // 11 NudgeEvent, // 12 BotLeaveEvent, // 13 MemberJoinRequestEvent, // 14 MessagePreSendEvent, // 15 MiraiCPExceptionEvent, // 16 Command, // 17 count, // 事件在此位置前定义,此时count为事件种类数 error // 出现问题时使用此enum }; } /// Event抽象父类 class MiraiCPEvent { public: MiraiCPEvent() = default; virtual ~MiraiCPEvent() = default; public: static eventTypes::Types get_event_type() { return eventTypes::Types::error; } virtual eventTypes::Types getEventType() const = 0; }; /// 所有事件处理timeoutevent都是机器人事件,指都有机器人实例 template class BotEvent : public MiraiCPEvent { public: eventTypes::Types getEventType() const override { return T::get_event_type(); } // TODO: 考虑设置一个Bot全局变量,此处存储一个Bot指针,减少无用构造. /// 该事件接受的机器人 Bot bot; /// 以该机器人的名义发送日志 /// @see BotLogger IdLogger botlogger; explicit BotEvent(QQID botid) : bot(botid), botlogger(botid, &Logger::logger) {} }; /// MessageEvent类型的抽象接口,用于Message类型多态实现 class MessageEvent { public: /// 获取当前聊天,可能是群,私聊,或群临时回话 virtual Contact *chat() = 0; /// 获取当前聊天,可能是群,私聊,或群临时回话 virtual Contact *from() = 0; virtual MessageChain *getMessageChain() = 0; virtual const Contact *chat() const = 0; virtual const Contact *from() const = 0; virtual const MessageChain *getMessageChain() const = 0; }; /*! * @brief 群消息事件声明 * @doxygenEg{1003, group.cpp, 取群聊下一条消息} */ class GroupMessageEvent : public BotEvent, public MessageEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::GroupMessageEvent; } public: ///来源群 Group group; ///发送人 Member sender; /// 信息 MessageChain message; GroupMessageEvent(QQID botid, const Group &group, const Member &sender, MessageChain mc) : BotEvent(botid), group(group), sender(sender), message(std::move(mc)){}; /*! * @brief 取群聊下一个消息(群聊与本事件一样) * @param time 超时时间限制, 单位为ms, 超时后抛出TimeOutException * @param halt 是否拦截该事件(不让这个消息被注册的其他监听器收到处理) * @return 消息链 */ MessageChain nextMessage(long time = -1, bool halt = true) const; /*! * @brief 取群聊中同群成员的下一个消息(发送人和群与本事件一样) * @param time 超时时间限制, 单位为ms, 超时后抛出TimeOutException * @param halt 是否拦截该事件(不让消息被注册的其他监听器收到处理) * @return 消息链 */ MessageChain senderNextMessage(long time = -1, bool halt = true) const; public: Contact *chat() override { return &group; } const Contact *chat() const override { return &group; } Contact *from() override { return &sender; } const Contact *from() const override { return &sender; } MessageChain *getMessageChain() override { return &message; } const MessageChain *getMessageChain() const override { return &message; } }; /*! * @detail 私聊消息事件类声明 * @doxygenEg{1004, group.cpp, 取好友下一条信息} */ class PrivateMessageEvent : public BotEvent, public MessageEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::PrivateMessageEvent; } public: /// 发起人 Friend sender; /// 信息 MessageChain message{}; /*! * @brief 构建私聊信息 * @param botid 对应botid * @param sender 发送者 * @param message 消息 * @param messageSource 消息源 */ PrivateMessageEvent(QQID botid, Friend sender, MessageChain mc) : BotEvent(botid), sender(std::move(sender)), message(std::move(mc)){}; /*! * @brief 取下一个消息(发送人和接收人和本事件一样) * @warning 如果两次发送信息间隔过短可能会漏过信息 * @param time 超时时间限制, 单位为ms, 超时后抛出TimeOutException * @param halt 是否拦截该事件(不被注册的监听器收到处理) * @return 消息链 */ MessageChain nextMessage(long time = -1, bool halt = true) const; public: Contact *chat() override { return &sender; } const Contact *chat() const override { return &sender; } Contact *from() override { return &sender; } const Contact *from() const override { return &sender; } MessageChain *getMessageChain() override { return &message; } const MessageChain *getMessageChain() const override { return &message; } }; /// 群聊邀请事件类声明 class GroupInviteEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::GroupInviteEvent; } public: /// 事件序列化文本 std::string source; /// 发起人昵称 std::string inviterNick; /// 发起人id QQID inviterid = 0; /// 被邀请进的组 std::string groupName; /// 群号 QQID groupid = 0; static void operation0(const std::string &source, QQID botid, bool accept); void reject() { GroupInviteEvent::operation0(this->source, this->bot.id, false); } void accept() { GroupInviteEvent::operation0(this->source, this->bot.id, true); } /*! * @brief 群邀请事件 * @param botid 当前botid * @param source 序列化后字符串 * @param inviterNick 邀请人昵称 * @param inviterid 邀请人id * @param groupName 群聊名称 * @param groupid 群号 */ GroupInviteEvent(QQID botid, const std::string &source, const std::string &inviterNick, QQID inviterid, const std::string &groupName, QQID groupid) : BotEvent(botid), source(source), inviterNick(inviterNick), inviterid(inviterid), groupName(groupName), groupid(groupid) {} }; /// 好友申请事件声明 class NewFriendRequestEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::NewFriendRequestEvent; } public: /// @brief 序列化的事件信息 std::string source; /// @brief 对方id QQID fromid; QQID fromgroupid; /// @brief 对方昵称 std::string nick; /// @brief 申请理由 std::string message; /// @brief 接受好友申请 /// @param source 事件序列化信息 static void operation0(const std::string &source, QQID botid, bool accept, bool ban = false); /// @brief 拒绝好友申请 /// @param ban - 是否加入黑名单 void reject(bool ban = false) { NewFriendRequestEvent::operation0(this->source, this->bot.id, false, ban); } /// @brief 接受申请 void accept() { NewFriendRequestEvent::operation0(this->source, this->bot.id, true); } /*! * @brief 好友申请事件 * @param botid 对应botid * @param source 序列化后信息 * @param fromid 对方id * @param fromgroupid 从哪个群申请的,否则为0 * @param nick 对方昵称 * @param message 申请理由 */ NewFriendRequestEvent(QQID botid, const std::string &source, QQID fromid, QQID fromgroupid, const std::string &nick, const std::string &message) : BotEvent(botid), source(source), fromid(fromid), fromgroupid(fromgroupid), nick(nick), message(message) {} }; /// 新群成员加入 class MemberJoinEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::MemberJoinEvent; } public: enum joinType { error = 0, invited = 1, applied = 2, rehab = 3 }; /*! * @brief 事件类型 * 1 - 被邀请进来 * 2 - 主动加入 * 3 - 原群主通过 https://huifu.qq.com/ 恢复原来群主身份并入群 */ joinType type = joinType::error; ///新进入的成员 Member member; ///目标群 Group group; ///邀请人, 当type = 1时存在,否则则和member变量相同 QQID inviterid; /*! * @brief 新群成员入群事件 * @param botid botid * @param type 类别 @see MemberJoinEvent::type * @param member 入群群成员 * @param group 群组 * @param inviterid 邀请群成员id,如果不存在和member id参数一致 */ MemberJoinEvent(QQID botid, int type, const Member &member, const Group &group, QQID inviterid) : BotEvent(botid), type(joinType(type)), member(member), group(group), inviterid(inviterid) {} }; /// 群成员离开 class MemberLeaveEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::MemberLeaveEvent; } public: /*! * @brief 事件类型 * 1 - 被踢出 * 2 - 主动退出 */ int type = 0; /// 退出的成员q号 QQID memberid; /// 目标群 Group group; /// 操作人, 主动退出时与member相同,该成员可能是当前bot,名称为operater以与系统operator区分 QQID operaterid; /*! * @brief 群成员离开 * @param botid * @param type * @param memberid 退出的群成员 * @param group 群 * @param operaterid 操作人id, 主动退出时与member相同,该成员可能是当前bot,名称为operater以与系统operator区分 */ MemberLeaveEvent(QQID botid, int type, QQID memberid, Group group, QQID operaterid) : BotEvent(botid), type(type), memberid(memberid), group(std::move(group)), operaterid(operaterid) {} }; /// 撤回信息 class RecallEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::RecallEvent; } public: /// 为1时是好友私聊中撤回,为2时为群聊内撤回 int type = 0; /// 时间戳 int time = 0; /// 原发送者 QQID authorid = 0; /// 撤回者 QQID operatorid = 0; /// 信息id std::string ids; //内部ids std::string internalids; //当type是2的时候存在,否则为0 QQID groupid = 0; /*! * @brief 撤回事件 * @param botid 对应bot * @param type 类型 * @param time 时间 * @param authorid 发送者id * @param operatorid 撤回者id * @param ids 消息源ids * @param internalids 消息源internalids * @param groupid */ RecallEvent(QQID botid, int type, int time, QQID authorid, QQID operatorid, std::string ids, std::string internalids, QQID groupid) : BotEvent(botid), type(type), time(time), authorid(authorid), operatorid(operatorid), ids(std::move(ids)), internalids(std::move(internalids)), groupid(groupid) {} }; /// 机器人进入某群 class BotJoinGroupEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::BotJoinGroupEvent; } public: /// 1-主动加入,2-被邀请加入,3-提供恢复群主身份加入 int type; /// 进入的群 Group group; /// 当type=2时存在,为邀请人,否则为空,调用可能会报错 QQID inviterid; /*! * @brief bot加入群 * @param botid 对应bot * @param type 类别 * @param group 加入的群 * @param inviter 邀请人 */ BotJoinGroupEvent(QQID botid, int type, Group group, QQID inviter) : BotEvent(botid), type(type), group(std::move(group)), inviterid(inviter) {} }; /// 群临时会话 class GroupTempMessageEvent : public BotEvent, public MessageEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::GroupTempMessageEvent; } public: /// 来源群 Group group; /// 发送人 Member sender; /// 信息 MessageChain message; /*! * @brief 群临时会话消息事件 * @param botid 对应bot * @param group 发起的群 * @param sender 发送消息对象 * @param message 消息 * @param messageSource 消息源 */ GroupTempMessageEvent(QQID botid, Group group, Member sender, MessageChain message) : BotEvent(botid), group(std::move(group)), sender(std::move(sender)), message(std::move(message)) {} public: Contact *chat() override { return &sender; } const Contact *chat() const override { return &sender; } Contact *from() override { return &sender; } const Contact *from() const override { return &sender; } MessageChain *getMessageChain() override { return &message; } const MessageChain *getMessageChain() const override { return &message; } }; /// 定时任务结束 class TimeOutEvent : public MiraiCPEvent { public: /// 事件所附信息 std::string msg; public: explicit TimeOutEvent(std::string msg) : msg(std::move(msg)) {} public: static eventTypes::Types get_event_type() { return eventTypes::Types::TimeOutEvent; } eventTypes::Types getEventType() const override { return this->get_event_type(); } }; /// 机器人上线事件 class BotOnlineEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::BotOnlineEvent; } public: explicit BotOnlineEvent(QQID botid) : BotEvent(botid) {} }; /*! 戳一戳事件 /* @warning nudgeEvent事件也会被bot自己发的Nudge触发, 可能会造成无限循环 */ class NudgeEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::NudgeEvent; } public: ///发送人 Contact from; /// 目标 Contact target; /// 发送的环境, 可能为Group / Friend Contact subject; NudgeEvent(const Contact &c, const Contact &target, const Contact &subject, QQID botid) : BotEvent(botid), from(c), target(target), subject(subject) {} }; /// 机器人退群事件 /// 可能有3种类型, 主动退/被踢/解散 /// 目前mirai的botLeave事件还不稳定暂时不支持类型 class BotLeaveEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::BotLeaveEvent; } public: /// 退出的群 /// @attension 收到这个事件时已经退出该群, 可能取不到相关信息 QQID groupid; BotLeaveEvent(QQID g, QQID botid) : BotEvent(botid), groupid(g) {} }; /// 申请加群事件, bot需为管理员或者群主 class MemberJoinRequestEvent : public BotEvent { private: std::string source; public: /** * @brief 底层通过MemberJoinRequest * @param s 序列化后的文本 */ static void operate(std::string_view s, QQID botid, bool sign, const std::string &msg = ""); public: static eventTypes::Types get_event_type() { return eventTypes::Types::MemberJoinRequestEvent; } public: /// 申请的群, 如果不存在就表明广播这个事件的时候机器人已经退出该群 std::optional group; /// 邀请人, 如果不存在表明这个邀请人退出了群或没有邀请人为主动进群 std::optional inviter; /// 申请人id QQID requesterId; public: MemberJoinRequestEvent(std::optional g, std::optional i, QQID botid, QQID requesterId, std::string source) : BotEvent(botid), group(std::move(g)), inviter(std::move(i)), source(std::move(source)), requesterId(requesterId){}; /// 通过 void accept() { operate(this->source, this->bot.id, true); } /// 拒绝 void reject(const std::string &msg) { operate(this->source, this->bot.id, false, msg); } }; /*! 每条消息发送前的事件, 总是在消息实际上被发送和广播MessagePostSendEvent前广播 * @see MessagePostSendEvent * @warning 在这个事件里小心使用sendMessage, 可能会触发无限递归 preSend -> sendMessage -> preSend -> ... * */ class MessagePreSendEvent : public BotEvent { public: static eventTypes::Types get_event_type() { return eventTypes::Types::MessagePreSendEvent; } public: /// 发送目标 Contact target; /// 消息 MessageChain message; explicit MessagePreSendEvent(Contact c, MessageChain mc, QQID botid) : BotEvent(botid), target(std::move(c)), message(std::move(mc)) {} }; class MiraiCPExceptionBase; // forward declaration /// @brief 异常抛出事件 class MiraiCPExceptionEvent : public MiraiCPEvent { private: MiraiCPExceptionBase *exceptionPtr; public: explicit MiraiCPExceptionEvent(MiraiCPExceptionBase *err) { exceptionPtr = err; } public: static eventTypes::Types get_event_type() { return eventTypes::Types::MiraiCPExceptionEvent; } eventTypes::Types getEventType() const override { return this->get_event_type(); } const MiraiCPExceptionBase *getException() const { return exceptionPtr; } }; /// 事件监听操控, 可用于stop停止监听和resume继续监听 class NodeHandle { private: bool _enable; public: explicit NodeHandle(bool a) : _enable(a) {} bool isEnable() const { return _enable; } void stop() { _enable = false; } void resume() { _enable = true; } }; class Event { private: // typedefs class eventNode { private: /// 回调的handle,用于管理 std::shared_ptr _handle; public: std::function func; explicit eventNode(std::function f) : _handle(new NodeHandle(true)), func(std::move(f)) {} eventNode(eventNode &&_o) noexcept : _handle(std::move(_o._handle)), func(std::move(_o.func)) {} public: /// 返回true代表block之后的回调 bool run(MiraiCPEvent *a) const { return _handle->isEnable() && func(a); } std::shared_ptr getHandle() { return _handle; } }; using priority_level = unsigned char; using event_vector = std::vector; using eventNodeTable = std::vector>; private: // member eventNodeTable _all_events_; private: Event() : _all_events_(int(eventTypes::Types::count)){}; public: // singleton mode static Event processor; private: template constexpr static int id() { static_assert(std::is_base_of_v, "只支持广播继承MiraiCPEvent的事件"); return static_cast(EventClass::get_event_type()); } public: static bool noRegistered(int index) { return processor._all_events_[index].empty(); } /// 清空全部配置 static void clear() { for (auto &a: processor._all_events_) a.clear(); } static void incomingEvent(nlohmann::json j, int type); /// 广播一个事件, 必须为MiraiCPEvent的派生类 template static void broadcast(EventClass &&val) { static_assert(std::is_base_of_v, "只支持广播MiraiCPEvent的派生类"); MiraiCPEvent *p = &val; for (auto &&[k, v]: processor._all_events_[id()]) { for (auto &&a: v) { if (a.run(p)) return; } } } /** * @brief 注册一个事件的回调 * @param T 事件类型 * @param callback 要注册的回调函数,忽略返回值 * @param priority_level 优先级,范围:0-255,越低的优先级越先执行,默认100 * @doxygenEg{1018, callbackHandle.cpp, NodeHandle使用} */ template static std::shared_ptr registerEvent(std::function callback, priority_level level = 100) { static_assert(std::is_base_of_v, "只支持注册MiraiCPEvent的派生类事件"); std::function tmp = [=](MiraiCPEvent *p) { callback(*dynamic_cast(p)); return false; }; auto t = eventNode(tmp); auto ans = t.getHandle(); // 先获得shared_ptr才可以emplace_back processor._all_events_[id()][level].emplace_back(std::move(t)); return ans; } /** * @brief 注册一个可以阻塞后续回调函数的回调。 * 回调返回true时,将会忽略所有优先级低于当前回调,以及注册顺序晚于当前回调且优先级等于当前回调的所有其他回调函数 * @param T 事件类型 * @param callback 要注册的回调函数,必须返回bool值 * @param priority_level 优先级,范围:0-255,越低的优先级越先执行,默认100 * @doxygenEg{1019, callbackHandle.cpp, NodeHandle使用} */ template static std::shared_ptr registerBlockingEvent(std::function callback, priority_level level = 100) { static_assert(std::is_base_of_v, "只支持注册MiraiCPEvent的派生类事件"); std::function tmp = [=](MiraiCPEvent *p) { return callback(*dynamic_cast(p)); }; auto t = eventNode(tmp); auto ans = t.getHandle(); // 先获得shared_ptr才可以emplace_back processor._all_events_[id()][level].emplace_back(std::move(t)); return ans; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_EVENT_H #ifndef MIRAICP_PRO_EXCEPTION_H #define MIRAICP_PRO_EXCEPTION_H // #include "CPPPlugin.h" #include #include #include namespace MiraiCP { /// @brief 总异常抽象类,用于一般捕获,不要直接抛出该类,不知道抛出什么的时候请抛出 MiraiCPException /// @interface MiraiCPExceptionBase class MiraiCPExceptionBase : public ::std::exception { protected: using string = std::string; protected: /// @brief 异常内容 string re; public: /// @brief 发生异常的文件名 string filename; /// @brief 发生异常的行号 int lineNum = 0; protected: /// 受保护构造函数,供子类调用 MiraiCPExceptionBase(string info, string _filename, int _lineNum) : re(std::move(info)), filename(std::move(_filename)), lineNum(_lineNum) {} public: ~MiraiCPExceptionBase() override = default; public: /// 异常信息 const char *what() const noexcept override { return re.c_str(); } /// 返回std::string的异常信息 string getError() const { return re; } /// 实际抛出方法 void raise() const; public: // 暴露的接口 /// basicRaise 基本抛出方法,子类重写该方法 virtual void basicRaise() const; // CRTP实现一次,调用静态的exceptionType /// 获取异常类型,通用接口 virtual string getExceptionType() const = 0; // 每个子类需要单独实现该静态方法 /// 返回异常的类型,该静态方法无法正确实现多态,请使用 getExceptionType /// @see getExceptionType static string exceptionType() { return "MiraiCPException"; } }; /// @brief 总异常CRTP抽象类,不要直接抛出该类,不知道抛出什么的时候请抛出 MiraiCPException。 /// 该类是用于继承的基类,需要新的异常类型时,继承该类并以子类作为模板参数。 /// 子类需要实现的方法: /// 1. 构造函数,要求必须委托MiraiCPExceptionCRTP构造,其他成员需要在MiraiCPException构造前完成构造。 /// 2. `static std::string exceptionType()` 返回一个字符串表示异常类型。 /// 继承该类后异常类能正确实现多态。 /// @interface MiraiCPExceptionCRTP /// @note 请勿给该类增加新的属性。如果要增加属性应在 MiraiCPExceptionBase 中增加 template class MiraiCPExceptionCRTP : public MiraiCPExceptionBase { public: /// 委托构造函数 explicit MiraiCPExceptionCRTP(std::string _re, string _filename, int _lineNum) : MiraiCPExceptionBase(std::move(_re), std::move(_filename), _lineNum) { } public: // CRTP类型获取实现 string getExceptionType() const override { return T::exceptionType(); } }; /// @brief 通用MiraiCP异常 /// @param const string &description, string _filename, int _lineNum /// @see MiraiCPExceptionBase typedef MiraiCPExceptionCRTP MiraiCPException; /// 文件读取异常. /// @see MiraiCPExceptionBase class UploadException : public MiraiCPExceptionCRTP { public: explicit UploadException(const std::string &text, string _filename, int _lineNum) : MiraiCPExceptionCRTP("上传(图片/文件)异常" + text, std::move(_filename), _lineNum) {} static std::string exceptionType() { return "UploadException"; } }; /// 通常为Mirai返回 /// @see MiraiCPExceptionBase class IllegalStateException : public MiraiCPExceptionCRTP { public: explicit IllegalStateException(const std::string &text, string _filename, int _lineNum) : MiraiCPExceptionCRTP("状态异常:" + text, std::move(_filename), _lineNum) {} static std::string exceptionType() { return "IllegalStateException"; } }; /// 内部异常, 通常为json读写问题 /// @see MiraiCPExceptionBase class APIException : public MiraiCPExceptionCRTP { public: explicit APIException(const std::string &text, string _filename, int _lineNum) : MiraiCPExceptionCRTP("MiraiCP内部无法预料的错误:" + text, std::move(_filename), _lineNum) {} static string exceptionType() { return "APIException"; } }; /// 机器人操作异常 /// @see MiraiCPExceptionBase class BotException : public MiraiCPExceptionCRTP { public: explicit BotException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("没有权限执行该操作", std::move(_filename), _lineNum) {} explicit BotException(const string &d, string _filename, int _lineNum) : MiraiCPExceptionCRTP(d, std::move(_filename), _lineNum) {} static string exceptionType() { return "BotException"; } }; /// 被禁言异常, 通常发生于发送信息 class BotIsBeingMutedException : public MiraiCPExceptionCRTP { public: /// 剩余禁言时间, 单位秒 int timeRemain; public: explicit BotIsBeingMutedException(int t, string _filename, int _lineNum) : timeRemain(t), MiraiCPExceptionCRTP("发送信息失败, bot已被禁言, 剩余时间" + std::to_string(t), std::move(_filename), _lineNum) {} static string exceptionType() { return "BotIsBeingMutedException"; } }; /// 禁言异常 /// @see MiraiCPExceptionBase class MuteException : public MiraiCPExceptionCRTP { public: /* * 禁言时间超出0s~30d */ MuteException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("禁言时长不在0s~30d中间", std::move(_filename), _lineNum) {} static string exceptionType() { return "MuteException"; } }; /// 获取群成员错误 /// @see MiraiCPExceptionBase class MemberException : public MiraiCPExceptionCRTP { public: enum MemberExceptionType : int { OtherType, NoSuchGroup, NoSuchMember }; MemberExceptionType type = OtherType; /* * "1" - 找不到群 * "2" - 找不到群成员 */ explicit MemberException(int _type, string _filename, int _lineNum) : MiraiCPExceptionCRTP( [&]() -> string { type = MemberExceptionType(_type); switch (type) { case NoSuchGroup: return "找不到群"; case NoSuchMember: return "找不到群成员"; default: return ""; } }(), std::move(_filename), _lineNum) {} static string exceptionType() { return "MemberException"; } }; /// 获取群成员错误 /// @see MiraiCPExceptionBase class FriendException : public MiraiCPExceptionCRTP { public: /* * 找不到好友 */ FriendException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("找不到好友", std::move(_filename), _lineNum) {} static string exceptionType() { return "FriendException"; } }; /// 获取群错误 /// @see MiraiCPExceptionBase class GroupException : public MiraiCPExceptionCRTP { public: GroupException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("找不到群", std::move(_filename), _lineNum) {} static string exceptionType() { return "GroupException"; } }; /// 撤回异常 /// @see MiraiCPExceptionBase class RecallException : public MiraiCPExceptionCRTP { public: RecallException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("该消息已经被撤回", std::move(_filename), _lineNum) {} static string exceptionType() { return "RecallException"; } }; /// 远程资源出现问题 /// @see MiraiCPExceptionBase class RemoteAssetException : public MiraiCPExceptionCRTP { public: explicit RemoteAssetException(const string &e, string _filename, int _lineNum) : MiraiCPExceptionCRTP(e, std::move(_filename), _lineNum) {} static string exceptionType() { return "RemoteAssetException"; } }; /// 参数错误 /// @see MiraiCPExceptionBase class IllegalArgumentException : public MiraiCPExceptionCRTP { public: explicit IllegalArgumentException(const string &e, string _filename, int _lineNum) : MiraiCPExceptionCRTP(e, std::move(_filename), _lineNum) { } static string exceptionType() { return "IllegalArgumentException"; } }; /// 超时 /// @see MiraiCPExceptionBase class TimeOutException : public MiraiCPExceptionCRTP { public: explicit TimeOutException(const std::string &e, string _filename, int _lineNum) : MiraiCPExceptionCRTP(e, std::move(_filename), _lineNum) {} static string exceptionType() { return "TimeOutException"; } }; /// 事件被取消, 一般出现在发送消息时在preSendMessageEvent取消的时候抛出 /// @see MiraiCPExceptionBase class EventCancelledException : public MiraiCPExceptionCRTP { public: explicit EventCancelledException(const string &msg, string _filename, int _lineNum) : MiraiCPExceptionCRTP(msg, std::move(_filename), _lineNum) {} static string exceptionType() { return "EventCancelledException"; } }; /// 插件没有权限时抛出该异常 /// 该异常仅可能在插件尝试调用libLoader 高级权限的Api接口时抛出 /// 如插件尝试重载、加载、卸载插件等操作,但配置文件中并没有赋予该插件权限时 /// @see MiraiCPExceptionBase class PluginNotAuthorizedException : public MiraiCPExceptionCRTP { public: explicit PluginNotAuthorizedException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("插件" + CPPPlugin::config.getId() + "没有管理权限", std::move(_filename), _lineNum) {} static string exceptionType() { return "PluginNotAuthorizedException"; } }; /// 插件未加载抛出该异常 /// 在插件能正常运行时不会抛出,出现该异常事件时请不要再次尝试收发消息等Mirai操作, /// 否则可能导致异常处理时再次抛出异常 /// @see MiraiCPExceptionBase class PluginNotEnabledException : public MiraiCPExceptionCRTP { public: explicit PluginNotEnabledException(string _filename, int _lineNum) : MiraiCPExceptionCRTP("插件" + CPPPlugin::config.getId() + "未加载", std::move(_filename), _lineNum) {} static string exceptionType() { return "PluginNotEnabledException"; } }; /// 如果在 MiraiCPNewThread 中捕获到了非 MiraiCP 之外的异常抛出 /// @see MiraiCPNewThread class MiraiCPThreadException : public MiraiCPExceptionCRTP { public: /// 抛出异常的线程 ID std::thread::id threadId; public: explicit MiraiCPThreadException(const std::string &exception_content, std::thread::id threadId, string _filename, int _lineNum) : MiraiCPExceptionCRTP(exception_content + " at threadId: " + getThreadIdStr(threadId), std::move(_filename), _lineNum), threadId(threadId) {} public: std::string getThreadIdStr() const { return getThreadIdStr(threadId); } public: static string exceptionType() { return "MiraiCPThreadException"; } private: static std::string getThreadIdStr(const std::thread::id &id) { std::stringstream ss; ss << id; return ss.str(); } }; inline void ErrorHandle0(const std::string &name, int line, const std::string &re, const std::string &ErrorMsg = "") { if (re == "EF") throw FriendException(name, line); if (re == "EG") throw GroupException(name, line); if (re == "EM") throw MemberException(1, name, line); if (re == "EMM") throw MemberException(2, name, line); if (re == "EB") throw BotException("找不到bot:" + re, name, line); if (re == "EA") throw APIException(ErrorMsg, name, line); if (re == "EC") throw EventCancelledException("发送信息被取消", name, line); if (re == "ET") throw TimeOutException("发送信息超时", name, line); if (re == "EP") throw BotException(name, line); // equal to Tools::start_with if (re.rfind("EBM", 0) == 0) throw BotIsBeingMutedException(std::stoi(re.substr(3)), name, line); } } // namespace MiraiCP #endif //MIRAICP_PRO_EXCEPTION_H #ifndef MIRAICP_PRO_FORWARDEDMESSAGE_H #define MIRAICP_PRO_FORWARDEDMESSAGE_H // #include "MessageChain.h" // #include "MiraiDefs.h" #include #include namespace MiraiCP { class Contact; class ForwardedMessage; /// 转发信息显示策略, 目前好像只在转发信息内的转发信息生效 class ForwardedMessageDisplayStrategy { using string = std::string; public: string title = "群聊的聊天记录"; string brief = "[聊天记录]"; string source = "聊天记录"; string summary = "查看1条转发信息"; std::vector preview{"Name: message"}; public: static std::optional defaultStrategy() { return std::nullopt; } public: ForwardedMessageDisplayStrategy() = default; ForwardedMessageDisplayStrategy(std::string title, std::string brief, std::string source, std::string summary, std::vector preview) : title(std::move(title)), brief(std::move(brief)), source(std::move(source)), summary(std::move(summary)), preview(std::move(preview)) {} public: nlohmann::json toJson() { nlohmann::json j; j["title"] = title; j["brief"] = brief; j["source"] = source; j["summary"] = summary; j["preview"] = preview; return j; } }; ///聊天记录里每个消息 /// todo 传入头像 class ForwardedNode { public: ///发送者id QQID id = 0; ///发送者昵称 std::string name; ///发送信息 std::variant> message; ///发送时间(时间戳) int time = 0; private: bool isForwardedMessage; std::optional display = std::nullopt; public: /// @brief 聊天记录里的每条信息 /// @param id - 发送者id /// @param name - 发送者昵称 /// @param message - 发送的信息 /// @param time - 发送时间,以时间戳记 ForwardedNode(QQID id, std::string name, MessageChain message, int time) : id(id), name(std::move(name)), message(std::move(message)), time(time), isForwardedMessage(false) {} /// @brief 构造聊天记录里每条信息 /// @param c - 发送者的contact指针 /// @param message - 发送的信息 /// @param t - 发送时间,时间戳格式 ForwardedNode(QQID id, std::string name, ForwardedMessage message, int t, std::optional display = ForwardedMessageDisplayStrategy::defaultStrategy()); /* ForwardedNode(Contact *c, MessageChain message, int t); ForwardedNode(QQID id, std::string name, ForwardedMessage &message, int t); */ public: bool isForwarded() const { return isForwardedMessage; } }; /*!转发消息, 由ForwardNode组成 * @see class ForwardedNode * @doxygenEg{1005, forwardMessage.cpp, 构建聊天记录} */ class ForwardedMessage { private: /// json except value nlohmann::json sendmsg; public: /// 每条信息 std::vector nodes; /// 显示策略 std::optional display = std::nullopt; public: /*! *@brief 构建一条聊天记录 *@details 第一个参数是聊天记录发生的地方, 然后是每条信息 */ ForwardedMessage(std::initializer_list nodes, std::optional display = ForwardedMessageDisplayStrategy::defaultStrategy()) : ForwardedMessage(std::vector(nodes), std::move(display)) {} ForwardedMessage(std::vector nodes, std::optional display = ForwardedMessageDisplayStrategy::defaultStrategy()) : nodes(std::move(nodes)), display(std::move(display)) {} public: void add(const ForwardedNode &a) { this->nodes.push_back(a); } /// 发送给群或好友或群成员 MessageSource sendTo(Contact *c); nlohmann::json nodesToJson(); ForwardedMessage plus(const ForwardedNode &a) { ForwardedMessage tmp(*this); tmp.nodes.push_back(a); return tmp; } public: ForwardedNode &operator[](int index) { return nodes[index]; } const ForwardedNode &operator[](int index) const { return nodes[index]; } ForwardedMessage operator+(const ForwardedNode &a) { return this->plus(a); } public: static ForwardedMessage deserializationFromMessageSourceJson(const nlohmann::json &j); }; /// 接收到的转发消息, 发送用 MiraiCP::ForwardedMessage class OnlineForwardedMessage : public SingleMessage { public: /// 里面每条信息 std::vector nodelist; /// 用展示出来ServiceMessage ServiceMessage origin; // unknown 用途, 有一些情况下没有 // std::optional resourceId; public: explicit OnlineForwardedMessage(nlohmann::json o, /*std::optional rid,*/ std::vector nodes) : SingleMessage(OnlineForwardedMessage::type(), ""), nodelist(std::move(nodes)), /*resourceId(std::move(rid)),*/ origin(ServiceMessage(o["serviceId"], o["content"])) {} public: /// 转ForwardedMessage /// @param c 发生的环境, 比如群聊或者好友 ForwardedMessage toForwardedMessage(std::optional display = ForwardedMessageDisplayStrategy::defaultStrategy()) const; /// 不支持直接发送OnlineForwardMessage, ForwardedMessage发送 ShouldNotUse("use MiraiCP::ForwardedMessage to send") std::string toMiraiCode() const override { return ""; } public: ForwardedNode &operator[](int i) { return nodelist[i]; } const ForwardedNode &operator[](int i) const { return nodelist[i]; } bool operator==(const OnlineForwardedMessage &m) const; public: static int type() { return -4; } static OnlineForwardedMessage deserializationFromMessageSourceJson(const nlohmann::json &j); }; } // namespace MiraiCP #endif //MIRAICP_PRO_FORWARDEDMESSAGE_H #ifndef MIRAICP_PRO_FRIEND_H #define MIRAICP_PRO_FRIEND_H // #include "Contact.h" namespace MiraiCP { /// 好友类声明 class Friend : public Contact, INudgeSupport { public: /// 删除好友(delete是C++关键字) void deleteFriend(); void refreshInfo(); /*! * @brief 发送戳一戳 * @warning 发送戳一戳的前提是登录该bot的协议是android_phone/ipad, 否则抛出IllegalStateException * @throw MiraiCP::BotException, MiraiCP::IllegalStateException */ void sendNudge() override; /*! * @brief 构建好友对象 * @param friendid q号 * @param botid 对应机器人id */ explicit Friend(QQID friendid, QQID botid); explicit Friend(const Contact &c) : Contact(c) { if (c.type() != 1) throw IllegalArgumentException("无法从 type==" + std::to_string(c.type()) + " 转为 type == 1(friend)", MIRAICP_EXCEPTION_WHERE); refreshInfo(); }; }; } // namespace MiraiCP #endif //MIRAICP_PRO_FRIEND_H #ifndef MIRAICP_PRO_GROUP_H #define MIRAICP_PRO_GROUP_H // #include "Contact.h" #include "json.hpp" namespace MiraiCP { class Member; // forward declaration /*! * @detail 群聊类声明 */ class Group : public Contact { public: // member classes and structs /// 群公告参数 class AnnouncementParams { public: /// 发送给新成员 bool send2new; /// 需要确认 bool requireConfirm; /// 置顶 bool pinned; /// 引导群成员修改群名片 bool showEditCard; /// 显示弹窗 bool showPopup; /// 序列化到文本 nlohmann::json serializeToJson(); explicit AnnouncementParams(bool send2New = false, bool requireConfirm = false, bool pinned = false, bool showEditCard = false, bool showPopup = false) : send2new(send2New), requireConfirm( requireConfirm), pinned(pinned), showEditCard(showEditCard), showPopup(showPopup) {} }; /// 在线群公告 class OnlineAnnouncement { public: /// 内容 std::string content; /// 所属bot QQID botid; /// 公告属性 AnnouncementParams params; /// 所在群id QQID groupid; /// 发送者id QQID senderid; /// 发送时间戳 long long publicationTime; /// 唯一识别属性 std::string fid; /// 如果需要确认,即为确认的人数 int confirmNum; /// 图片id, 如果不存在即为空 std::string imageid; /// 删除当前群公告 /// @throw BotException void deleteThis(); /// 反序列化 static OnlineAnnouncement deserializeFromJson(const nlohmann::json &); OnlineAnnouncement(const std::string &content, AnnouncementParams ¶ms, QQID groupid, QQID senderid, QQID botid, long long int publicationTime, const std::string &fid, int confirmNum, const std::string &imageid) : content(content), params(params), groupid(groupid), senderid(senderid), botid(botid), publicationTime(publicationTime), fid(fid), confirmNum(confirmNum), imageid(imageid) {} }; /// 本地(未发送)群公告 class OfflineAnnouncement { public: /// 内容 std::string content; /// 公告属性 AnnouncementParams params; /// 发布群公告 Group::OnlineAnnouncement publishTo(const Group &); OfflineAnnouncement(const std::string &content, AnnouncementParams params) : content(content), params(params) {} }; /** * @brief 群设置 * @details 使用uploadSetting上传设置,使用refreshInfo同步服务器设定,后面两项由于https://github.com/mamoe/mirai/issues/1307 还不能改 */ struct GroupSetting { /// 群名称 std::string name; /// 禁言全部 bool isMuteAll{}; /// 允许群成员邀请 bool isAllowMemberInvite{}; /// 自动同意进群 bool isAutoApproveEnabled{}; /// 允许匿名聊天 bool isAnonymousChatEnabled{}; }; /// 群文件的简短描述 struct file_short_info { // 路径带文件名 std::string path; // 唯一id std::string id; }; public: // attrs /// 群设置 GroupSetting setting; public: // constructors /// @brief 构建以群号构建群对象 /// @param groupid 群号 /// @param botid 机器人id /// @doxygenEg{1007, group.cpp, 从群号构建群对象} Group(QQID groupid, QQID botid); explicit Group(const Contact &c) : Contact(c) { if (c.type() != 2) throw IllegalArgumentException("无法从 type==" + std::to_string(c.type()) + " 转为 type == 2(group)", MIRAICP_EXCEPTION_WHERE); refreshInfo(); } public: // methods /** * @brief 更新群设置, 即覆盖服务器上的群设置 * @details 从服务器拉去群设置用refreshInfo * @see Group::refreshInfo() */ void updateSetting(); /// 取群成员列表 /// @return vector std::vector getMemberList(); /*! * 以string格式取群成员列表 * @note 格式: * 每个群成员id间用逗号分隔 */ std::string MemberListToString(); /// 取群主 Member getOwner(); /// 取群成员 Member getMember(QQID memberid); Member operator[](QQID id); /// 取群公告列表 std::vector getAnnouncementsList(); /// 刷新群聊信息 void refreshInfo(); void quit(); /*! @brief 上传并发送远程(群)文件 @param path-群文件路径(带文件名),根目录为/ @param filepath-本地文件路径 @attention 路径分隔符是 `/` @doxygenEg{1008, group.cpp, 发送群文件} */ RemoteFile sendFile(const std::string &path, const std::string &filepath); /// 发送语音 MessageSource sendVoice(const std::string &path) { return Contact::sendVoice0(path); } /*! 取群文件信息,会自动搜索子目录 @param path-群文件路径(不带文件名) @param id-文件id,可空,空则为用路径查找(此时路径要带文件名) @attention 因为群文件允许重名文件存在的特性,如果没有id该查找并不可靠,只能返回重名文件中的其中一个文件 @see RemoteFile @doxygenEg{1009, group.cpp, 获取群文件} */ RemoteFile getFile(const std::string &path, const std::string &id = ""); /*! * @brief 取文件信息(根据id) * @param id 文件id * @return 文件 * @detail 相当于从根目录开始遍历查找文件, 相当于getFile("/", id); */ RemoteFile getFileById(const std::string &id); RemoteFile getFileByFile(const RemoteFile &file) { return getFileById(file.id); } /*! * 获取path路径下全部文件信息 * @param path - 远程路径 * @return 返回值为一个vector容器, 每一项为short_info * @doxygenEg{1010, group.cpp, 获取群文件列表} */ std::vector getFileList(const std::string &path); /// 取文件列表以字符串形式返回 /// @param path 文件夹路径 std::string getFileListString(const std::string &path); }; } // namespace MiraiCP #endif //MIRAICP_PRO_GROUP_H #ifndef MIRAICP_PRO_KTOPERATION_H #define MIRAICP_PRO_KTOPERATION_H #include /// @brief 配置类声明, MiraiCP内部使用, 不需要更改或其他操作 /// @internal 一般为MiraiCP内部调用jni接口使用 /// @namespace KtOperation namespace MiraiCP::KtOperation { /// 操作id enum operation_set { /// 撤回信息 Recall, /// 发送信息 Send, /// 查询信息接口 RefreshInfo, /// 上传图片 UploadImg, /// 取好友列表 QueryBFL, /// 取群组列表 QueryBGL, /// 上传文件 SendFile, /// 查询文件信息 RemoteFileInfo, /// 查询图片下载地址 QueryImgInfo, /// 禁言 MuteM, /// 查询权限 QueryM, /// 踢出 KickM, /// 取群主 QueryOwner, /// 语音 Voice, /// 查询群成员列表 QueryML, /// 群设置 GroupSetting, /// 构建转发信息 Buildforward, /// 好友申请事件 Nfroperation, /// 群聊邀请事件 Gioperation, /// 回复(引用并发送) SendWithQuote, /// 群公告操作 Announcement, /// 定时任务 TimeOut, /// 发送戳一戳 SendNudge, /// 下一条信息 NextMsg, /// 更改权限 ModifyAdmin, /// 群成员申请入群 MemberJoinRequest, /// 图片是否已经上传 ImageUploaded, /// 注册指令 CommandReg, /// 改名称 ChangeNameCard, }; /** * @brief 调用mirai操作 * @param type 操作id * @param data 传入数据 * @return 返回数据 */ std::string ktOperation( operation_set type, nlohmann::json data, bool catchErr = true, const std::string &errorInfo = ""); } // namespace MiraiCP::KtOperation #endif //MIRAICP_PRO_KTOPERATION_H #ifndef MIRAICP_PRO_LOGGER_H #define MIRAICP_PRO_LOGGER_H // #include "MiraiCode.h" // #include "MiraiDefs.h" #include #include namespace MiraiCP { class MiraiCodeable; // forward declaration /*! * @class Logger * @brief 以MiraiCP的名义发送日志, 日志表现格式是: 2021-06-28 09:37:22 [log level]/MiraiCP: [log content], 为最底层的logger * 发送消息级日志 * @code Logger::logger.info(string) @endcode * 发送警告级日志 * @code Logger::logger.warning(string) @endcode * 发送错误级日志 * @code Logger::logger.error(string) @endcode * @doxygenEg{1011, logger.cpp, 自定义日志handle} */ class Logger_interface { using string = std::string; public: /// @brief 封装lambda类型 /// @param string 日志内容 /// @param 日志级别 /// - 0 info /// - 1 warning /// - 2 error typedef std::function Action; /// @brief loggerhandler会在每次log执行前执行一遍,可用于执行自定义的保存操作等 struct Handler { /// @brief 是否启用 bool enable = true; /// @brief 执行的操作,格式为lambda Action action = [](const string &content, int level) {}; }; Handler loggerhandler; private: static std::string constructString() { return ""; } template static std::string constructString(T val, T1... val1) { std::stringstream sstream; sstream << val; return sstream.str() + constructString(val1...); } template static std::string constructString(std::string a, T... val1) { return a + constructString(val1...); } template static std::string constructString(MiraiCodeable &val, T... val1) { return val.toMiraiCode() + constructString(val1...); } protected: /// @brief 日志底层实现封装 /// @param log 日志内容 /// @param level 日志等级 virtual void log_interface(const string &log, int level) = 0; public: ///发送普通(info级日志) template void info(T... val) { this->log_interface(constructString(val...), 0); } ///发送警告(warning级日志) template void warning(T... val) { this->log_interface(constructString(val...), 1); } ///发送错误(error级日志) template void error(T... val) { this->log_interface(constructString(val...), 2); } /// @brief 设置loggerhandler的action /// @param action 执行的操作 /// @see Logger::handler void registerHandle(Action action) { this->loggerhandler.action = std::move(action); } /// @brief 设置handler的启用状态 /// @param state 状态,启用或者关闭 /// @doxygenEg{1012, logger.cpp, 启用或关闭日志} void setHandleState(bool state) { this->loggerhandler.enable = state; } }; class Logger : public Logger_interface { private: Logger() = default; protected: /// @brief 日志底层实现封装 /// @param content 日志内容 /// @param level 日志等级 void log_interface(const std::string &content, int level) override; public: static Logger logger; }; /// 带id(一般为bot账号)的logger class IdLogger : public Logger_interface { public: QQID id; protected: void log_interface(const std::string &content, int level) override; public: IdLogger(QQID id, Logger *l) : id(id) { this->loggerhandler = l->loggerhandler; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_LOGGER_H #ifndef MIRAICP_PRO_LOWLEVELAPI_H #define MIRAICP_PRO_LOWLEVELAPI_H #include namespace MiraiCP { /// 较底层api class LowLevelAPI { public: /// @brief 抽象封装底层发送信息接口 /// @param content 信息字符串 /// @param c 目标Contact->serialization() /// @param miraicode 是否为miraicode格式 /// @return static std::string send0(const std::string &content, nlohmann::json c, int retryTime, bool miraicode, const std::string &errorInfo = ""); /// @brief 取该联系人的一些信息 /// @param c 该联系人Contact->serializationToString() /// @return json格式字符串,待解析 static std::string getInfoSource(const std::string &); /*! * @brief 上传图片 * @param path 本地地址 * @param c 上传的对象, Contact->serializationToString() * @return string 待解析json */ static std::string uploadImg0(const std::string &, const std::string &); /// 每个对象的必有信息 struct info { std::string nickornamecard; std::string avatarUrl; }; /// 获取每个对象必有信息 /// @see LowLevelAPI::info static info info0(const std::string &source); }; } // namespace MiraiCP #endif //MIRAICP_PRO_LOWLEVELAPI_H #ifndef MIRAICP_PRO_MEMBER_H #define MIRAICP_PRO_MEMBER_H // #include "Contact.h" namespace MiraiCP { /*! * @brief 群成员类声明 * @doxygenEg{1013, member.cpp, 群成员操作} */ class Member : public Contact, INudgeSupport { public: /// @brief 权限等级 /// - OWNER群主 为 2 /// - ADMINISTRATOR管理员 为 1 /// - MEMBER群成员 为 0 /// @note 上面那些变量在constants.h中有定义 unsigned int permission = 0; /// @brief 更改群成员权限 /// @param admin 如果为true为更改到管理员 /// @param env void modifyAdmin(bool admin); /// @brief 构建群成员对象 /// @param qqid 该成员q号 /// @param groupid 所在群号 /// @param botid 机器人id explicit Member(QQID qqid, QQID groupid, QQID botid); explicit Member(const Contact &c) : Contact(c) { if (c.type() != 3) throw IllegalArgumentException("无法从 type==" + std::to_string(c.type()) + " 转为 type == 3(member)", MIRAICP_EXCEPTION_WHERE); this->isAnonymous = this->_anonymous; refreshInfo(); }; /// 是否是匿名群成员, 如果是匿名群成员一些功能会受限 bool isAnonymous = false; /// 重新获取(刷新)群成员信息 void refreshInfo(); /// 发送语音 MessageSource sendVoice(const std::string &path) { return Contact::sendVoice0(path); } /// 获取权限,会在构造时调用,请使用permission缓存变量 /// @see Member::permission unsigned int getPermission() const; /*! * 禁言当前对象,单位是秒,最少0秒最大30天,如果为0或者为负则unmute * @throws BotException, MuteException */ void mute(int time); /// 取消禁言 /// @throws BotException, MuteException void unMute() { mute(0); } /*! 踢出这个群成员 * @param reason - 原因 */ void kick(const std::string &reason); /// At一个群成员 At at() { return At(this->id()); } /// 更改群名片 /// @throw MiraiCP::BotException 如果没权限时 void changeNameCard(std::string_view newName); /*! * @brief 发送戳一戳 * @warning 发送戳一戳的前提是登录该bot的协议是android_phone/ipad, 否则抛出IllegalStateException * @throw MiraiCP::BotException, MiraiCP::IllegalStateException */ void sendNudge() override; }; } // namespace MiraiCP #endif //MIRAICP_PRO_MEMBER_H #ifndef MIRAICP_PRO_MESSAGECHAIN_H #define MIRAICP_PRO_MESSAGECHAIN_H // #include "Exception.h" // #include "SingleMessage.h" namespace MiraiCP { class MessageSource; // forward declaration namespace internal { class Message : public std::shared_ptr { private: // std::shared_ptr content; public: // constructor template explicit Message(const T &_singleMessage) { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); reset(new T(_singleMessage)); } explicit Message(std::shared_ptr msgptr) : std::shared_ptr(std::move(msgptr)) {} public: /// 代表的子类 /// @see MessageChain::messageType int type() const { return (*this)->type; }; /// 取指定类型 /// @throw IllegalArgumentException template T get() const { static_assert(std::is_base_of_v, "只支持SingleMessage的派生类"); if (T::type() != this->type()) throw IllegalArgumentException("cannot convert from " + SingleMessage::messageType[this->type()] + " to " + SingleMessage::messageType[T::type()], MIRAICP_EXCEPTION_WHERE); T *re = static_cast(std::shared_ptr::get()); if (re == nullptr) throw IllegalArgumentException("cannot convert from " + SingleMessage::messageType[this->type()] + " to " + SingleMessage::messageType[T::type()], MIRAICP_EXCEPTION_WHERE); return *re; } std::string toMiraiCode() const { return (*this)->toMiraiCode(); } bool operator==(const Message &m) const { return (*this)->type == m->type && (*this)->toMiraiCode() == m->toMiraiCode(); } bool operator!=(const Message &m) const { return (*this)->type != m->type || (*this)->toMiraiCode() != m->toMiraiCode(); } }; } // namespace internal /// 消息链, 一般由SingleMessage组成 class MessageChain : public std::vector, public MiraiCodeable { public: // typedefs using Message = internal::Message; public: /// 如果由MiraiCP构造(incoming)就会存在,否则则不存在 std::optional source = std::nullopt; public: MessageChain(const MessageChain &_o) = default; MessageChain(MessageChain &&_o) = default; /// incoming构造器 template explicit MessageChain(MessageSource ms, T... args) : source(std::move(ms)) { this->constructMessages(args...); }; /*! * @brief 从多个参数构建MessageChain * @tparam T 多个传入参数的类型 * 支持以下类型: * - std::string / const char* 相当于传入PlainText * - SingleMessage的派生类 * @param args 参数本身 */ template explicit MessageChain(T... args) { constructMessages(args...); }; /// outcoming 构造器 template explicit MessageChain(const T &msg) { static_assert(std::is_base_of_v, "只支持SingleMessage子类"); emplace_back(msg); }; public: [[deprecated("MessageChain继承自std::vector,无需获取内部vector")]] const std::vector &vector() const { return *static_cast *>(this); } std::string toMiraiCode() const override; std::vector toMiraiCodeVector() const { std::vector tmp; for (auto &&a: *this) tmp.emplace_back(a->toMiraiCode()); return tmp; } /// @brief 添加元素 /// @tparam T 任意的SingleMessage的子类 /// @param a 添加的值 template void add(const T &a) { static_assert(std::is_base_of_v, "只接受SingleMessage的子类"); emplace_back(a); } void add(const MessageSource &val) { source = val; } /// 筛选出某种类型的消息 template std::vector filter() { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); std::vector re; for (auto &&a: *this) { if (a.type() == T::type()) re.emplace_back(a.get()); } return re; } /// 自定义筛选器 template std::vector filter(const std::function &func) { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); std::vector re; for (auto &&a: *this) { if (func(a)) re.push_back(a.get()); } return re; } /// 找出第一个指定的type的消息,消息可能不存在 template std::optional first() { for (auto &&a: *this) if (a.type() == T::type()) return a.get(); return std::nullopt; } template [[nodiscard]] MessageChain plus(const T &a) const { static_assert(std::is_base_of_v, "只支持SingleMessage的子类"); MessageChain tmp(*this); tmp.emplace_back(a); return tmp; } [[nodiscard]] MessageChain plus(const MessageChain &mc) const { MessageChain tmp(*this); tmp.insert(tmp.end(), mc.begin(), mc.end()); return tmp; } [[nodiscard]] MessageChain plus(const MessageSource &ms) const { MessageChain tmp(*this); tmp.source = ms; return tmp; } template MessageChain operator+(const T &msg) const { return this->plus(msg); } bool operator==(const MessageChain &mc) const { if (size() != mc.size()) return false; for (size_t i = 0; i < size(); i++) { if ((*this)[i] != mc[i]) return false; } return true; } bool operator!=(const MessageChain &mc) const { return !(*this == mc); } bool empty() const { return std::vector::empty() || toMiraiCode().empty(); } /// @brief 回复并发送 /// @param s 内容 /// @param groupid 如果是来源于TempGroupMessage就要提供(因为要找到那个Member) /// @note 可以改MessageSource里的内容, 客户端在发送的时候并不会校验MessageSource的内容正确性(比如改originalMessage来改引用的文本的内容, 或者改id来定位到其他信息) /// @detail 支持以下类型传入 /// - std::string / const char* 相当于传入PlainText(str) /// - SingleMessage的各种派生类 /// - MessageChain /// @deprecated use Contact.quoteAndSend or `this->quoteAndSend1(s, groupid, env)`, since v2.8.1 template ShouldNotUse("use Contact.quoteAndSend") MessageSource quoteAndSendMessage(T s, QQID groupid = -1, void *env = nullptr) = delete; public: // static functions /// @brief 找到miraiCode结尾的`]` /// @param s 文本 /// @param start 开始位置 /// @return 如果不存在返回-1, 存在则返回index static size_t findEnd(const std::string &s, size_t start) { size_t pos = start; while (pos < s.length()) { switch (s[pos]) { case '\\': pos += 2; continue; case ']': return pos; } pos++; } return -1; } /// 从miraicode string构建MessageChain static MessageChain deserializationFromMiraiCode(const std::string &m); static MessageChain deserializationFromMessageSourceJson(const std::string &msg, bool origin = true) { return deserializationFromMessageSourceJson(nlohmann::json::parse(msg), origin); } /// 从MessageSource json中构建MessageChain, 常用于Incoming message /// @attention 本方法并不会自动附加MessageSource到MessageChain, 需要用.plus方法自行附加 static MessageChain deserializationFromMessageSourceJson(const nlohmann::json &j, bool origin = true); private: // private methods void constructMessages() {} template void constructMessages(T1 h, T2... args) { static_assert(std::is_base_of_v, "只支持SingleMessage子类"); emplace_back(h); constructMessages(args...); } template void constructMessages(const std::string &h, T2... args) { emplace_back(PlainText(h)); constructMessages(args...); } template void constructMessages(const char *h, T2... args) { emplace_back(PlainText(h)); constructMessages(args...); } template void constructMessages(const MessageChain &mc, T... args) { insert(end(), mc.begin(), mc.end()); constructMessages(args...); } MessageSource quoteAndSend0(std::string msg, QQID groupid = -1); template MessageSource quoteAndSend1(T s, QQID groupid = -1) { static_assert(std::is_base_of_v, "只支持SingleMessage的派生类"); return this->quoteAndSend0(s.toMiraiCode(), groupid); } MessageSource quoteAndSend1(std::string s, QQID groupid) { return this->quoteAndSend0(std::move(s), groupid); } MessageSource quoteAndSend1(const MessageChain &mc, QQID groupid) { return this->quoteAndSend0(mc.toMiraiCode(), groupid); } }; } // namespace MiraiCP #endif //MIRAICP_PRO_MESSAGECHAIN_H #ifndef MIRAICP_PRO_MESSAGESOURCE_H #define MIRAICP_PRO_MESSAGESOURCE_H #include // #include "MiraiDefs.h" namespace MiraiCP { class MiraiCodeable; // forward declaration /*! 消息源声明 * @doxygenEg{1014, message.cpp, 回复信息} */ class MessageSource { public: /// 消息的ids std::string ids; /// 消息的internalids std::string internalids; /// 消息源序列化 std::string source; MessageSource() = default; /// @deprecated 用Contact.quoteAndSendMessage, since v2.8.1 ShouldNotUse("Use Contact.quoteAndSendMessage") MessageSource quoteAndSendMiraiCode(MiraiCodeable *msg, QQID groupid = 0, void *env = nullptr) const = delete; /// @deprecated use Contact.quoteAndSendMessage, since v2.8.1 ShouldNotUse("Use Contact.quoteAndSendMessage") MessageSource quoteAndSendMsg(const std::string &c, QQID groupid = 0, void * = nullptr) const = delete; /// @deprecated use Contact.quoteAndSendMessage, since v2.8.1 ShouldNotUse("Use Contact.quoteAndSendMessage") MessageSource quoteAndSendMiraiCode(const std::string &c, QQID groupid = 0, void * = nullptr) const = delete; /*! * @brief 构建消息源 * @param ids * @param internalids * @param source */ MessageSource(std::string ids, std::string internalids, std::string source); /*! * @brief 从json字符串反序列化到MessageSource对象 * @note json应该为以下格式 * @code * {"ids":"", "internalids":""} * @endcode */ static MessageSource deserializeFromString(const std::string &source); std::string serializeToString() const; /// @brief 撤回该信息 void recall() const; bool operator==(const MessageSource &ms) const { return this->ids == ms.ids && this->internalids == ms.internalids; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_MESSAGESOURCE_H // Copyright (c) 2022 - 2022. Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_MIRAICPNEWTHREAD_H #define MIRAICP_PRO_MIRAICPNEWTHREAD_H // #include "Event.h" // #include "Exception.h" #include #include namespace MiraiCP { /// MiraiCP 对 std::thread 的封装 class MiraiCPNewThread : public std::thread { public: MiraiCPNewThread() noexcept = default; template explicit MiraiCPNewThread(Callable &&func, Args &&...args) : std::thread( [lambda_func = std::forward(func)](auto &&...argss) { try { lambda_func(std::forward(argss)...); } catch (MiraiCPExceptionBase &e) { e.raise(); Event::broadcast(MiraiCPExceptionEvent(&e)); } catch (const std::exception &e) { MiraiCPThreadException exNew(std::string(e.what()), std::this_thread::get_id(), MIRAICP_EXCEPTION_WHERE); exNew.raise(); Event::broadcast(MiraiCPExceptionEvent(&exNew)); } catch (...) { MiraiCPThreadException exNew("unknown exception type", std::this_thread::get_id(), MIRAICP_EXCEPTION_WHERE); exNew.raise(); Event::broadcast(MiraiCPExceptionEvent(&exNew)); } }, std::forward(args)...) {} MiraiCPNewThread &operator=(const std::thread &) = delete; MiraiCPNewThread &operator=(const MiraiCPNewThread &) = delete; MiraiCPNewThread &operator=(std::thread &&other) { *static_cast(this) = std::move(other); return *this; } MiraiCPNewThread &operator=(MiraiCPNewThread &&other) noexcept { *static_cast(this) = std::move(*static_cast(&other)); return *this; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_MIRAICPNEWTHREAD_H #ifndef MIRAICP_PRO_MIRAICODE_H #define MIRAICP_PRO_MIRAICODE_H #include namespace MiraiCP { /// MiraiCode父类, 指可以被转换成miraicode的类型 class MiraiCodeable { public: /// 返回MiraiCode virtual std::string toMiraiCode() const = 0; }; /// @brief miraicode字符串 /// @attention MiraiCode会把非miraicode组成部分(非[mirai:])转码, 输出转码前的文本用toString, 参考: https://github.com/mamoe/mirai/blob/dev/docs/Messages.md#%E8%BD%AC%E4%B9%89%E8%A7%84%E5%88%99 /// @detail 为了便捷使用,构造函数不以explicit注释 class MiraiCode : public MiraiCodeable { private: std::string content; public: /// 输出当前内容, 会自动转码 std::string toString(); /// 和toString作用一样, 不过不会自动转码 std::string toMiraiCode() const override { return content; } /// 从MiraiCodeable类型初始化一个miraicode字符串 MiraiCode(MiraiCodeable *a) { // NOLINT(google-explicit-constructor) content = a->toMiraiCode(); } /// 从文本初始化一个miraicode字符串, 根据第二个参数决定是否转码, 默认不转码 /// @attention 如果是传入文本MiraiCode,请勿转码,转码只是为了[mirai:xxx:<应该转码的部分>], 如果<应该转码>的部分里面含有'[]:,'内容,请调用Tools::escapeToMiraiCode转码 MiraiCode(const std::string &a, bool convert = false); MiraiCode operator+(MiraiCodeable *a) { return {content + a->toMiraiCode()}; } MiraiCode operator+(const std::string &a) { return {content + a}; } MiraiCode operator+(const MiraiCode &a) { return {content + a.content}; } MiraiCode operator+(MiraiCode *a) { return {content + a->content}; } MiraiCode &operator=(const std::string &a) { this->content = a; return *this; } MiraiCode plus(MiraiCodeable *a) { return {content + a->toMiraiCode()}; } MiraiCode plus(const std::string &a) { return MiraiCode(a) + this; } /// 不执行转义,适用于已经被MiraiCode转义过的字符串 static MiraiCode MiraiCodeWithoutEscape(const std::string &a) { return {a, false}; } /// 不执行转义,因为MiraiCodeable的toMiraiCode已经转义过了 static MiraiCode MiraiCodeWithoutEscape(MiraiCodeable *a) { return {a->toMiraiCode(), false}; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_MIRAICODE_H #ifndef MIRAICP_PRO_MIRAIDEFS_H #define MIRAICP_PRO_MIRAIDEFS_H // #define MiraiCPThrow(x) throw x.append(__FILE__, __LINE__) #define ErrorHandle(x, y) ErrorHandle0(__FILE__, __LINE__, (x), (y)) #define MIRAICP_EXCEPTION_WHERE __FILE__, __LINE__ #if defined(_MSC_VER) #define ShouldNotUse(msg) _Pragma("warning(error:4996)") [[deprecated(msg)]] _Pragma("warning(warning:4996)") #else // MSVC #if defined(__GNUC__) #define ShouldNotUse(msg) [[deprecated(msg)]] __attribute__((error(msg))) #else // GUNC #define ShouldNotUse(msg) #endif // ShouldNotUse #endif #include namespace MiraiCP { using QQID = unsigned long long; } // namespace MiraiCP #endif //MIRAICP_PRO_MIRAIDEFS_H #ifndef MIRAICP_PRO_SINGLEMESSAGE_H #define MIRAICP_PRO_SINGLEMESSAGE_H #include #include #include #include // #include "MessageSource.h" // #include "MiraiCode.h" namespace MiraiCP { /// 用serviceMessage的分享信息 struct URLSharer { /// 简介 没点进来看见的样子 std::string brief = "简介 没点进来看见的样子"; /// 目标url std::string url = "目标url"; /// 图标地址 std::string cover = "图标地址"; /// 标题 std::string title = "标题"; /// 描述文字 std::string summary = "描述文字"; }; /// MessageChain的组成部分 class SingleMessage : public MiraiCodeable { public: /// MiraiCode类别 /// @see SingleMessage::messageType int type; std::string content; std::string prefix; public: static std::unordered_map messageType; public: /// @brief 构建单条 /// @param type 消息类型 @see messageType /// @param content 内容 /// @param prefix 前缀, 默认为`:`, 第二个冒号部分的内容, 目前在serviceMesage有使用 SingleMessage(int type, std::string content, std::string prefix = ":") : type(type), content(std::move(content)), prefix(std::move(prefix)) {} virtual ~SingleMessage() = default; public: /// @brief 找对应类型的index key /// @param value 类型名 /// @return 如果没找到返回-1 static int getKey(const std::string &value); public: virtual nlohmann::json toJson() const { nlohmann::json re; re["key"] = "miraicode"; re["content"] = this->toMiraiCode(); return re; } std::string toMiraiCode() const override; public: bool operator==(const SingleMessage &m) const { return this->type == m.type && this->toMiraiCode() == m.toMiraiCode(); } bool operator==(SingleMessage *m) const { return this->type == m->type && this->toMiraiCode() == m->toMiraiCode(); } }; /// 纯文本信息 class PlainText : public SingleMessage { public: explicit PlainText(const SingleMessage &sg); template explicit PlainText(const T &a) : SingleMessage(PlainText::type(), ([&a]() -> std::string { std::stringstream sst; sst << a; return sst.str(); })()) {} PlainText(PlainText &&_o) noexcept : SingleMessage(PlainText::type(), std::move(_o.content)) {} PlainText(const PlainText &_o) : SingleMessage(PlainText::type(), _o.content) {} public: static int type() { return 0; } public: std::string toMiraiCode() const override { return content; } nlohmann::json toJson() const override; bool operator==(const PlainText &p) const { return this->content == p.content; } }; /// @ class At : public SingleMessage { public: static int type() { return 1; } QQID target; nlohmann::json toJson() const override; explicit At(const SingleMessage &sg); explicit At(QQID a) : SingleMessage(At::type(), std::to_string(a)), target(a){}; std::string toMiraiCode() const override { return "[mirai:at:" + std::to_string(this->target) + "] "; // 后面有个空格 } bool operator==(const At &a) const { return this->target == a.target; } }; /// @brief \@全体 class AtAll : public SingleMessage { public: static int type() { return 2; } std::string toMiraiCode() const override { return "[mirai:atall] "; } nlohmann::json toJson() const override; AtAll() : SingleMessage(AtAll::type(), "", "") {} }; /// 图像类声明 class Image : public SingleMessage { public: static int type() { return 3; } //图片id,样式:` {xxx}.xx ` std::string id; /// 可为空, 用`refreshInfo`获取 std::optional md5; /// 可为0, 来源:用`refreshInfo`可能可以获取或者自己填充, 是isUploaded的必须条件, 默认0 size_t size; /// 可为空, 用`refreshInfo`获取 std::optional url; /// 宽度, 默认0, 单位px int width; /// 长度, 默认0, 单位px int height; /*! * @brief 图片类型 * - 0 png * - 1 bmp * - 2 jpg * - 3 gif * - 4 apng * - 5 unknown * 默认 5 */ int imageType; /*! * @brief 图片是否已经上传(如果已经上传即表明可以直接用ImageId发送, 如果没有需要手动上传) * @param md5 在kotlin端会用.toByteArray()转换 * @param size 图片大小, 不能为0 * @param botid 所属Botid * @return 是否已上传 */ bool isUploaded(QQID botid); /*! * @brief 从图片builder构造,适用于服务器上已经有的图片,即接收到的 * @param imageId 图片id, 必须 * @param size isUploaded的必要条件, 单纯用ImageId可能取不到图片size, 需要自己上传 * @param width 宽度 * @param height 长度 * @param type 图片类型 * @detail 图片miraiCode格式例子, `[mirai:image:{图片id}.jpg]` * 可以用这个正则表达式找出id `\\[mirai:image:(.*?)\\]` */ explicit Image(const std::string &imageId, size_t size = 0, int width = 0, int height = 0, int type = 5) : SingleMessage(Image::type(), imageId) { this->id = imageId; this->size = size; this->width = width; this->height = height; this->imageType = type; } explicit Image(const SingleMessage &sg); /// 刷新信息(获取图片下载Url,md5, size) void refreshInfo(); /// 取图片Mirai码 std::string toMiraiCode() const override { return "[mirai:image:" + this->id + "]"; } nlohmann::json toJson() const override; static Image deserialize(const std::string &); bool operator==(const Image &i) const { return this->id == i.id; } }; /// 闪照, 和Image属性类似 class FlashImage : public Image { public: static int type() { return 8; } std::string toMiraiCode() const override { return "[mirai:flash:" + this->id + "]"; } explicit FlashImage(const std::string &imageId, size_t size = 0, int width = 0, int height = 0, int type = 0) : Image(imageId, size, width, height, type) { this->SingleMessage::type = 8; } explicit FlashImage(const SingleMessage &sg) : Image(sg) {} explicit FlashImage(const Image &img) : Image(img) {} nlohmann::json toJson() const override; static FlashImage deserialize(const std::string &); bool operator==(const FlashImage &i) const { return this->id == i.id; } /// 转换到普通图片 Image toImage() { return Image(id, size, width, height, imageType); } }; /*! * @brief 小程序卡片 * @attention 自带的模板不稳定,可能发出现没有效果 * @doxygenEg{1015, lightApp.cpp, 从文本构建LightApp} */ class LightApp : public SingleMessage { public: static int type() { return 4; } /// @brief 使用纯文本构造,推荐使用其他结构体方法构造 /// @param content 构造文本 explicit LightApp(std::string content) : SingleMessage(LightApp::type(), std::move(content)) {} explicit LightApp(const SingleMessage &sg); nlohmann::json toJson() const override; /// 返回miraicode std::string toMiraiCode() const override; bool operator==(const LightApp &la) const { return this->content == la.content; } }; /// xml格式的超文本信息 /// @attention 自带的模板不稳定,可能发出现没有效果 class ServiceMessage : public SingleMessage { public: static int type() { return 5; } nlohmann::json toJson() const override; std::string toMiraiCode() const override; int id; /// @brief ServiceMessage /// @param id 在xml内容前面的id (不包括逗号) /// @param a xml内容 (不需要事先转码到miraiCode) explicit ServiceMessage(int id, std::string a) : SingleMessage(ServiceMessage::type(), std::move(a), ":" + std::to_string(id) + ','), id(id) {} explicit ServiceMessage(const SingleMessage &sg); explicit ServiceMessage(const URLSharer &a) : SingleMessage(5, "" + a.title + "" + a.summary + "", ":1,"), id(1) {} bool operator==(const ServiceMessage &s) const { return this->content == s.content; } }; /// 引用信息 class QuoteReply : public SingleMessage { public: static int type() { return -2; } // 不可直接发送, 发送引用信息用MessageChain.quoteAndSendMessage ShouldNotUse("don't have MiraiCode, use MessageChain.quote instead") std::string toMiraiCode() const override { return ""; } /// 引用信息的MessageSource MessageSource source; explicit QuoteReply(const SingleMessage &m); explicit QuoteReply(MessageSource source) : SingleMessage(QuoteReply::type(), source.serializeToString()), source(std::move(source)){}; bool operator==(const QuoteReply &qr) const { return this->source == qr.source; } }; /// 接收到的音频文件, 发送用`Contact.sendAudio` class OnlineAudio : public SingleMessage { public: static int type() { return -3; } /// 文件名 std::string filename; /// 下载地址 std::string url; /// 文件大小 int size; /// 编码方式 int codec; /// 时长(单位s) int length; /// 16位md5 std::array md5; /// 不支持直接发送, 用Contact.sendAudio ShouldNotUse("cannot use, use Contact.sendAudio") std::string toMiraiCode() const override { return ""; } explicit OnlineAudio(std::string f, std::array md5, int size, int codec, int length, std::string url) : SingleMessage(OnlineAudio::type(), ""), filename(std::move(f)), md5(md5), size(size), codec(codec), length(length), url(std::move(url)){}; bool operator==(const OnlineAudio &oa) const { return this->md5 == oa.md5; } }; /// @brief 远程(群)文件类型 class RemoteFile : public SingleMessage { public: static int type() { return 6; } /// @brief 下载信息 /// @see RemoteFile struct Dinfo { /// 下载地址, 可能会是 `null` 当文件不存在 std::string url; /// md5 可用于校验 std::string md5; /// sha1 可用于校验 std::string sha1; }; /// @brief 文件信息 /// @see RemoteFile struct Finfo { /// 文件大小 QQID size; /// 上传者id QQID uploaderid; /// 过期时间 long expirytime; /// 上传时间, 时间戳格式 QQID uploadtime; /// 上次更改时间, 时间戳格式 QQID lastmodifytime; }; /// 文件唯一id, 用于识别 std::string id; /// 文件内部id, 用于构造miraiCode发送 unsigned int internalid; /// 文件名 std::string name; /// 文件大小 long long size; /// 文件在群文件的路径 /// @attention 可能为空(通常出现于MessageChain从MiraiCode反序列化), 需要从Group重新获取文件 /// @see Group::getFileByFile std::optional path; /// 文件下载信息 /// @attention 可能为空(常出现于MessageChain从MiraiCode反序列化), 如果为空需要从Group重新获取 /// @see MiraiCP::Dinfo, Group::getFileByFile std::optional dinfo; /// 文件信息 /// @attention 可能为空(常出现于MessageChain从MiraiCode反序列化), 如果为空需要从Group重新获取 /// @see MiraiCP::Finfo, Group::getFileByFile std::optional finfo; std::string serializeToString(); RemoteFile plus(unsigned int ii); static RemoteFile deserializeFromString(const std::string &source); /*! * @brief 构造远程(群)文件 * @param i ids * @param ii internalids * @param n name * @param s size * @param p path * @param d dinfo * @param f finfo */ explicit RemoteFile(const std::string &i, unsigned int ii, std::string n, long long s, std::string p, struct Dinfo d, struct Finfo f); /*! * @brief 构造远程(群)文件 * @param i ids * @param ii internalids * @param n name * @param s size * @param p path * @param d dinfo * @param f finfo */ explicit RemoteFile(const std::string &i, unsigned int ii, std::string n, long long s); /// 上传后会自动发送 ShouldNotUse("Cannot send manually, use Group.sendFile") std::string toMiraiCode() const override { return ""; } bool operator==(const RemoteFile &rf) const { return this->id == rf.id; } }; /// 自带表情 /// @attention 有些表情会变成PlainText类型和\\xxx 的格式 class Face : public SingleMessage { public: static int type() { return 7; } int id; nlohmann::json toJson() const override; std::string toMiraiCode() const override { return "[mirai:face:" + std::to_string(id) + "]"; } explicit Face(int id) : SingleMessage(Face::type(), std::to_string(id)), id(id) {} bool operator==(const Face &f) const { return this->id == f.id; } }; /// 一些可以被mirai识别的音乐卡片, 如果不能被mirai识别, 那应该被表现成lightApp类型(可能收费/vip歌曲用lightApp, 免费用MusicShare) class MusicShare : public SingleMessage { public: static int type() { return 9; } /// 应用名称, 如NeteaseCloudMusic std::string appName; /// 歌名 std::string title; /// 卡片第二行的文字内容 std::string summary; /// 点击跳转到的链接 std::string jumpUrl; /// 图片链接 std::string picUrl; /// 音乐文件链接 std::string musicUrl; /// 简介, 点进聊天节目前显示的小文字, 一般是`分享` std::string brief; std::string toMiraiCode() const override { return "[mirai:musicshare:" + appName + "," + title + "," + summary + "," + jumpUrl + "," + picUrl + "," + musicUrl + "," + brief + "]"; } MusicShare(const std::string &appName, const std::string &title, const std::string &summary, const std::string &jumpUrl, const std::string &picUrl, const std::string &musicUrl, const std::string &brief) : SingleMessage(MusicShare::type(), ""), appName(appName), title(title), summary(summary), jumpUrl(jumpUrl), picUrl(picUrl), musicUrl(musicUrl), brief(brief) {} }; class MarketFace : public SingleMessage { public: static int type() { return -5; } /// 目前无法直接发送MarketFace, 可以转发 ShouldNotUse("暂不支持直接发送") std::string toMiraiCode() const override { return ""; } std::array faceId; explicit MarketFace(std::array id) : SingleMessage(MarketFace::type(), ""), faceId(id) {} bool operator==(const MarketFace &mf) const { return this->faceId == mf.faceId; } }; /// @brief 目前不支持的消息类型, 不支持发送 class UnSupportMessage : public SingleMessage { public: static int type() { return -1; } nlohmann::json toJson() const override; /// 不支持发送 ShouldNotUse("不支持直接发送UnSupportMessage") std::string toMiraiCode() const override { return ""; } explicit UnSupportMessage(const SingleMessage &s) : SingleMessage(s){}; explicit UnSupportMessage(const std::string &content) : SingleMessage(UnSupportMessage::type(), content) {} bool operator==(const UnSupportMessage &m) const { return this->content == m.content; } }; } // namespace MiraiCP #endif //MIRAICP_PRO_SINGLEMESSAGE_H #ifndef MIRAICP_PRO_TOOLS_H #define MIRAICP_PRO_TOOLS_H // #include "MiraiDefs.h" #include #include #include #if defined(__clang__) || defined(__GNUC__) #define MIRAICP_CPP_STANDARD __cplusplus #elif defined(_MSC_VER) #define MIRAICP_CPP_STANDARD _MSVC_LANG #endif //#if MIRAICP_CPP_STANDARD >= 201703L //#define get_return_type std::invoke_result_t //#else //#define get_return_type std::result_of_t //#endif namespace MiraiCP { /// @brief 工具类声明, 常用的一些转换工具, 如需转码使用std::filesystem /// @class Tools namespace Tools { /*! * @brief 替换全部在一个字符串中. * @param str 原字符串. * @param from 需要被替换的字符. * @param to 替换到的字符. * @return 返回替换后的字符串. * @note 来源:https://stackoverflow.com/a/24315631/14646226 */ std::string replace(std::string str, std::string_view from, std::string_view to); /// @brief long long 类型的vector格式化输出 /// @param a vector /// @return string template std::string VectorToString(const std::vector &a, const std::string &separator = ",") { std::stringstream ss; for (size_t i = 0; i < a.size(); ++i) { if (i != 0) ss << separator; ss << a[i]; } std::string s = ss.str(); return s; } /// @brief 从string格式化到vector /// @param temp string /// @return vector std::vector StringToVector(std::string temp); /// @brief 从miraicode转义到正常 /// @param s 经过miraicode转义的字符串 /// @return 原字符串 std::string escapeFromMiraiCode(const std::string &s); /// @brief 转义miraicode格式 std::string escapeToMiraiCode(const std::string &s); /// starts_with, from bool starts_with(std::string_view f, std::string_view s); /// compare char with case-insensitive bool icompareChar(const char &c1, const char &c2); /// case insensitive string compare from https://thispointer.com/c-case-insensitive-string-comparison-using-stl-c11-boost-library/ bool iequal(std::string_view str1, std::string_view str2); /// from https://www.zhihu.com/question/36642771, delim is regex(ignore last `+`) std::vector split(const std::string &text, const std::string &delim); }; // namespace Tools } // namespace MiraiCP // #undef get_return_type #endif //MIRAICP_PRO_TOOLS_H // Copyright (c) 2022 - 2022. Antares, Eritque arcus and contributors. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or any later version(in your opinion). // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // #ifndef MIRAICP_PRO_LOADERAPI_H #define MIRAICP_PRO_LOADERAPI_H // #include "loaderApiInternal.h" namespace LibLoader::LoaderApi { } // namespace LibLoader::LoaderApi #endif //MIRAICP_PRO_LOADERAPI_H #ifndef MIRAICP_PRO_UTILS_H #define MIRAICP_PRO_UTILS_H // #include "CPPPlugin.h" // #include "KtOperation.h" // #include "PluginConfig.h" // #include "commonTypes.h" namespace MiraiCP { /*! * @brief 定时任务, 在一定时间后广播**一次**TimeOutEvent * @param time 在多少毫秒后执行 * @param msg 附加的string类型信息 * @doxygenEg{1017, schedule.cpp, 定时任务} */ inline void schedule(long time, const std::string &msg) { nlohmann::json j; j["time"] = time; j["msg"] = msg; KtOperation::ktOperation(KtOperation::TimeOut, j); } /// 注册插件函数, 需要被实现, 类似onStart(); void enrollPlugin(); /// 用指针绑定插件 inline void enrollPlugin(CPPPlugin *p) { CPPPlugin::plugin.reset(p); } [[deprecated("use enrollPlugin instead")]] inline void enrollPlugin0(CPPPlugin *p) { enrollPlugin(p); } } // namespace MiraiCP #ifndef MIRAICP_EXPORT #if _WIN32 || _WIN64 || WIN32 #define MIRAICP_EXPORT __declspec(dllexport) #else #define MIRAICP_EXPORT #endif #endif extern "C" { MIRAICP_EXPORT void FUNC_ENTRANCE(const LibLoader::LoaderApi::interface_funcs &); MIRAICP_EXPORT void FUNC_EVENT(const char *content); MIRAICP_EXPORT void FUNC_EXIT(); MIRAICP_EXPORT const MiraiCP::PluginConfig &PLUGIN_INFO(); } #endif //MIRAICP_PRO_UTILS_H