Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

改进的错误支持,MessageSegment::type重构为enum,对MessageSegment的其他重构和优化 #22

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
50 changes: 49 additions & 1 deletion src/core/api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,61 @@ namespace cq {
: RuntimeError("failed to call coolq api, error code: " + to_string(code)), code(code) {
}

ApiError(const char *what, const int code) : RuntimeError(what), code(code) {
}

const int code; // 错误码

static const auto INVALID_DATA = 100; // 酷Q返回的数据无效
static const auto INVALID_TARGET = 101; // 发送目标无效
static const auto INVALID_ARGS = 102; // 参数无效
static const auto LOG_DISABLED = -5; // 日志功能未启用
static const auto LOG_PRIORITY_ERR = -6; // 日志优先级错误
static const auto DATABASE_ERR = -7; // 数据入库失败
static const auto APP_DISABLED = -997; // 应用未启用,请在应用窗中启用应用
static const auto UNAUTHORIZED = -998; // 应用调用在 auth 声明之外的 Api,见日志警告。在 app.json
// 中添加相应的 auth,授予应用该 Api 的调用权限。
static const auto UNKOWN_ERR = -1000; // 发生未知错误,由于系统限制,实际错误代码未能传递。

inline static void InvokeError(int code);
};

#define ApiErrorImpl(_ERR_CLASS_NAME, _ERR_WHAT, _ERR_ID) \
struct _ERR_CLASS_NAME : ApiError { \
_ERR_CLASS_NAME() : ApiError(_ERR_WHAT, _ERR_ID) { \
} \
}

ApiErrorImpl(ApiErrorInvalidData, "Failed to call coolq api (INVALID_DATA).", INVALID_DATA);
ApiErrorImpl(ApiErrorInvalidTarget, "Message sent to invalid target.", INVALID_TARGET);
ApiErrorImpl(ApiErrorInvalidArgs, "Arguments is not valid.", INVALID_ARGS);
ApiErrorImpl(ApiErrorLogDisabled, "Log is disabled.", LOG_DISABLED);
ApiErrorImpl(ApiErrorLogPriority, "Log priority error.", LOG_PRIORITY_ERR);

ApiErrorImpl(ApiErrorDatabaseErr, "Database error.", DATABASE_ERR);
ApiErrorImpl(ApiErrorAppDisabled, "App is disabled.", APP_DISABLED);
ApiErrorImpl(ApiErrorUnauthorized, "App is not authorized.", UNAUTHORIZED);
ApiErrorImpl(ApiErrorUnkownErr, "An unexpeted error has occurred.", UNKOWN_ERR);

#undef ApiErrorImpl

inline void ApiError::InvokeError(int code) {
switch (code) {
case LOG_DISABLED:
throw ApiErrorLogDisabled();
case LOG_PRIORITY_ERR:
throw ApiErrorLogPriority();
case DATABASE_ERR:
throw ApiErrorDatabaseErr();
case APP_DISABLED:
throw ApiErrorAppDisabled();
case UNAUTHORIZED:
throw ApiErrorUnauthorized();
case UNKOWN_ERR:
throw ApiErrorUnkownErr();
}
}

void _init_api();

// 发送私聊消息
Expand Down Expand Up @@ -49,7 +97,7 @@ namespace cq {
if (target.user_id.has_value()) {
return send_private_message(target.user_id.value(), message);
}
throw ApiError(ApiError::INVALID_TARGET);
throw ApiErrorInvalidTarget();
}

// 撤回消息(可撤回自己 2 分钟内发的消息和比自己更低权限的群成员发的消息)
Expand Down
86 changes: 60 additions & 26 deletions src/core/message.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,91 +31,112 @@ namespace cq::message {

// 消息段 (即 CQ 码)
struct MessageSegment {
std::string type; // 消息段类型 (即 CQ 码的功能名)
enum class SegTypes {
#define MSG_SEG(val) val,
#include "./message_segment_types.inc"
#undef MSG_SEG
none
};

static constexpr char *const SegTypesName[] = {
#define MSG_SEG(val) #val,
#include "./message_segment_types.inc"
#undef MSG_SEG
""};

SegTypes type = SegTypes::none; // 消息段类型 (即 CQ 码的功能名)
std::map<std::string, std::string> data; // 消息段数据 (即 CQ 码参数), 字符串全部使用未经 CQ 码转义的原始文本

std::string SegTypeName() const {
return SegTypesName[static_cast<size_t>(this->type)];
}

// 转换为字符串形式
operator std::string() const {
std::string s;
if (this->type.empty()) {
switch (this->type) {
case SegTypes::none: {
return s;
}
if (this->type == "text") {
case SegTypes::text: {
if (const auto it = this->data.find("text"); it != this->data.end()) {
s += escape((*it).second, false);
}
} else {
s += "[CQ:" + this->type;
return s;
}
default: {
s += "[CQ:" + this->SegTypeName();
for (const auto &item : this->data) {
s += "," + item.first + "=" + escape(item.second, true);
}
s += "]";
return s;
}
}
return s;
}

// 纯文本
static MessageSegment text(const std::string &text) {
return {"text", {{"text", text}}};
return {SegTypes::text, {{"text", text}}};
}

// Emoji 表情
static MessageSegment emoji(const uint32_t id) {
return {"emoji", {{"id", to_string(id)}}};
return {SegTypes::emoji, {{"id", to_string(id)}}};
}

// QQ 表情
static MessageSegment face(const int id) {
return {"face", {{"id", to_string(id)}}};
return {SegTypes::face, {{"id", to_string(id)}}};
}

// 图片
static MessageSegment image(const std::string &file) {
return {"image", {{"file", file}}};
return {SegTypes::image, {{"file", file}}};
}

// 语音
static MessageSegment record(const std::string &file, const bool magic = false) {
return {"record", {{"file", file}, {"magic", to_string(magic)}}};
return {SegTypes::record, {{"file", file}, {"magic", to_string(magic)}}};
}

// @某人
static MessageSegment at(const int64_t user_id) {
return {"at", {{"qq", to_string(user_id)}}};
return {SegTypes::at, {{"qq", to_string(user_id)}}};
}

// 猜拳魔法表情
static MessageSegment rps() {
return {"rps", {}};
return {SegTypes::rps, {}};
}

// 掷骰子魔法表情
static MessageSegment dice() {
return {"dice", {}};
return {SegTypes::dice, {}};
}

// 戳一戳
static MessageSegment shake() {
return {"shake", {}};
return {SegTypes::shake, {}};
}

// 匿名发消息
static MessageSegment anonymous(const bool ignore_failure = false) {
return {"anonymous", {{"ignore", to_string(ignore_failure)}}};
return {SegTypes::anonymous, {{"ignore", to_string(ignore_failure)}}};
}

// 链接分享
static MessageSegment share(const std::string &url, const std::string &title, const std::string &content = "",
const std::string &image_url = "") {
return {"share", {{"url", url}, {"title", title}, {"content", content}, {"image", image_url}}};
return {SegTypes::share, {{"url", url}, {"title", title}, {"content", content}, {"image", image_url}}};
}

enum class ContactType { USER, GROUP };

// 推荐好友, 推荐群
static MessageSegment contact(const ContactType &type, const int64_t id) {
return {
"contact",
SegTypes::contact,
{
{"type", type == ContactType::USER ? "qq" : "group"},
{"id", to_string(id)},
Expand All @@ -127,7 +148,7 @@ namespace cq::message {
static MessageSegment location(const double latitude, const double longitude, const std::string &title = "",
const std::string &content = "") {
return {
"location",
SegTypes::location,
{
{"lat", to_string(latitude)},
{"lon", to_string(longitude)},
Expand All @@ -139,19 +160,19 @@ namespace cq::message {

// 音乐
static MessageSegment music(const std::string &type, const int64_t id) {
return {"music", {{"type", type}, {"id", to_string(id)}}};
return {SegTypes::music, {{"type", type}, {"id", to_string(id)}}};
}

// 音乐
static MessageSegment music(const std::string &type, const int64_t id, const int32_t style) {
return {"music", {{"type", type}, {"id", to_string(id)}, {"style", to_string(style)}}};
return {SegTypes::music, {{"type", type}, {"id", to_string(id)}, {"style", to_string(style)}}};
}

// 音乐自定义分享
static MessageSegment music(const std::string &url, const std::string &audio_url, const std::string &title,
const std::string &content = "", const std::string &image_url = "") {
return {
"music",
SegTypes::music,
{
{"type", "custom"},
{"url", url},
Expand All @@ -164,6 +185,12 @@ namespace cq::message {
}
};

const ::std::unordered_map<::std::string, MessageSegment::SegTypes> SegTypeName2SegTypes = {
#define MSG_SEG(val) {#val, MessageSegment::SegTypes::val},
#include "./message_segment_types.inc"
#undef MSG_SEG
{"", MessageSegment::SegTypes::none}};

struct Message : std::list<MessageSegment> {
using std::list<MessageSegment>::list;

Expand Down Expand Up @@ -215,7 +242,12 @@ namespace cq::message {
eq_pos != std::string::npos ? std::string(param.begin() + eq_pos + 1, param.end()) : "");
param.clear();
}
this->push_back(MessageSegment{std::move(type), std::move(data)});
auto iter = SegTypeName2SegTypes.find(type);
if (iter != SegTypeName2SegTypes.end()) {
this->push_back(MessageSegment{iter->second, std::move(data)});
} else {
this->push_back(MessageSegment{MessageSegment::SegTypes::none, std::move(data)});
}
cq_code.clear();
temp_text.clear();
};
Expand Down Expand Up @@ -287,7 +319,7 @@ namespace cq::message {
std::string extract_plain_text() const {
std::string result;
for (const auto &seg : *this) {
if (seg.type == "text") {
if (seg.type == MessageSegment::SegTypes::text) {
result += seg.data.at("text") + " ";
}
}
Expand Down Expand Up @@ -315,7 +347,8 @@ namespace cq::message {

auto last_seg_it = this->begin();
for (auto it = this->begin(); ++it != this->end();) {
if (it->type == "text" && last_seg_it->type == "text" && it->data.find("text") != it->data.end()
if (it->type == MessageSegment::SegTypes::text && last_seg_it->type == MessageSegment::SegTypes::text
&& it->data.find("text") != it->data.end()
&& last_seg_it->data.find("text") != last_seg_it->data.end()) {
// found adjacent "text" segments
last_seg_it->data["text"] += it->data["text"];
Expand All @@ -327,7 +360,8 @@ namespace cq::message {
}
}

if (this->size() == 1 && this->front().type == "text" && this->extract_plain_text().empty()) {
if (this->size() == 1 && this->front().type == MessageSegment::SegTypes::text
&& this->extract_plain_text().empty()) {
this->clear(); // the only item is an empty text segment, we should remove it
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/core/message_segment_types.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
MSG_SEG(text)
MSG_SEG(emoji)
MSG_SEG(face)
MSG_SEG(image)
MSG_SEG(record)
MSG_SEG(at)
MSG_SEG(rps)
MSG_SEG(dice)
MSG_SEG(shake)
MSG_SEG(anonymous)
MSG_SEG(share)
MSG_SEG(contact)
MSG_SEG(location)
MSG_SEG(music)
MSG_SEG(bface)
MSG_SEG(rich)
16 changes: 8 additions & 8 deletions src/std_mode/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ namespace cq {
template <typename T, enable_if_t<is_integral_v<T>> * = nullptr>
inline decltype(auto) chk(T &&res) noexcept(false) {
if (res < 0) {
throw ApiError(static_cast<int>(res));
ApiError::InvokeError(static_cast<int>(res));
}
return std::forward<T>(res);
}

template <typename T, enable_if_t<is_pointer_v<T>> * = nullptr>
inline decltype(auto) chk(T &&res_ptr) noexcept(false) {
if (!res_ptr) {
throw ApiError(ApiError::INVALID_DATA);
throw ApiErrorInvalidData();
}
return std::forward<T>(res_ptr);
}
Expand Down Expand Up @@ -154,31 +154,31 @@ namespace cq {
try {
return ObjectHelper::from_base64<User>(chk(raw::CQ_getStrangerInfo(_ac(), user_id, no_cache)));
} catch (ParseError &) {
throw ApiError(ApiError::INVALID_DATA);
throw ApiErrorInvalidData();
}
}

std::vector<Friend> get_friend_list() {
try {
return ObjectHelper::multi_from_base64<std::vector<Friend>>(chk(raw::CQ_getFriendList(_ac(), false)));
} catch (ParseError &) {
throw ApiError(ApiError::INVALID_DATA);
throw ApiErrorInvalidData();
}
}

std::vector<Group> get_group_list() {
try {
return ObjectHelper::multi_from_base64<std::vector<Group>>(chk(raw::CQ_getGroupList(_ac())));
} catch (ParseError &) {
throw ApiError(ApiError::INVALID_DATA);
throw ApiErrorInvalidData();
}
}

Group get_group_info(const int64_t group_id, const bool no_cache) {
try {
return ObjectHelper::from_base64<Group>(chk(raw::CQ_getGroupInfo(_ac(), group_id, no_cache)));
} catch (ParseError &) {
throw ApiError(ApiError::INVALID_DATA);
throw ApiErrorInvalidData();
}
}

Expand All @@ -187,7 +187,7 @@ namespace cq {
return ObjectHelper::multi_from_base64<std::vector<GroupMember>>(
chk(raw::CQ_getGroupMemberList(_ac(), group_id)));
} catch (ParseError &) {
throw ApiError(ApiError::INVALID_DATA);
throw ApiErrorInvalidData();
}
}

Expand All @@ -196,7 +196,7 @@ namespace cq {
return ObjectHelper::from_base64<GroupMember>(
chk(raw::CQ_getGroupMemberInfoV2(_ac(), group_id, user_id, no_cache)));
} catch (ParseError &) {
throw ApiError(ApiError::INVALID_DATA);
throw ApiErrorInvalidData();
}
}

Expand Down