├── Tool.h ├── robot.h ├── .gitignore ├── robot.c ├── win.c ├── go-cqhttp-C.vcxproj.user ├── .editorconfig ├── win.h ├── function.h ├── CMakeLists.txt ├── URLcode.h ├── function.c ├── Tool.c ├── go-cqhttp-C.sln ├── Log.h ├── AnaJSON.h ├── main.c ├── go-cqhttp-C.vcxproj.filters ├── URLcode.c ├── README.md ├── gocqhttp_Event.h ├── gocqhttp_API.h ├── go-cqhttp-C.vcxproj ├── Log.c ├── gocqhttp_API.c ├── AnaJSON.c └── gocqhttp_Event.c /Tool.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrzxrzx/go-cqhttp-C/HEAD/Tool.h -------------------------------------------------------------------------------- /robot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include"gocqhttp_API.h" 4 | #include"Log.h" 5 | #include"gocqhttp_Event.h" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # cmake 2 | build/ 3 | # visual studio code 4 | .vscode/ 5 | # Visual studio 6 | out/ 7 | x64/ 8 | log/ 9 | temp/ 10 | .vs/ -------------------------------------------------------------------------------- /robot.c: -------------------------------------------------------------------------------- 1 | #include"robot.h" 2 | #include"gocqhttp_API.h" 3 | #include"Log.h" 4 | #include"gocqhttp_Event.h" 5 | #include"URLcode.h" 6 | #include 7 | #include -------------------------------------------------------------------------------- /win.c: -------------------------------------------------------------------------------- 1 | #include"win.h" 2 | #include 3 | 4 | int color(int a) 5 | { 6 | SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), a); //更改文字颜色 7 | return 0; 8 | } -------------------------------------------------------------------------------- /go-cqhttp-C.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /win.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _WIN_H_ 3 | 4 | /*设置控制台文字颜色 5 | * color code 6 | * 控制台:7(蓝白) 7 | * 出错:4(红) 8 | * 信息:9(蓝) 9 | * 警告:14(黄) 10 | */ 11 | int color(int a); 12 | 13 | #endif // !_WIN_H_ -------------------------------------------------------------------------------- /function.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include"gocqhttp_Event.h" 3 | 4 | static const int bot_id = 114514; 5 | 6 | /*初始化,在初始化事件模块时调用(需包含注册各个事件函数)*/ 7 | void initialize(void); 8 | 9 | /*测试功能*/ 10 | void test(NoticeEventInfo info); 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(gocqhttp) 2 | 3 | set(SOURCE 4 | "gocqhttp_API.c" 5 | "gocqhttp_Event.c" 6 | "URLcode.c" 7 | "AnaJSON.c" 8 | "Log.c" 9 | "robot.c" 10 | "Tool.c" 11 | "win.c" 12 | "function.c" 13 | ) 14 | 15 | add_library(gocqhttplib STATIC ${SOURCE}) 16 | add_executable(gocqhttp "main.c") 17 | target_link_libraries(gocqhttp PRIVATE gocqhttplib) 18 | -------------------------------------------------------------------------------- /URLcode.h: -------------------------------------------------------------------------------- 1 | /* 本文件代码来自于 2 | * https://blog.csdn.net/langeldep/article/details/6264058 3 | * https://blog.csdn.net/bladeandmaster88/article/details/54800287 4 | * 并稍加修改,并非本作者编写,仅仅是为了初学者能更快地进行机器人的编写, 5 | * 并非抄袭之意,如有侵权请联系我 6 | */ 7 | #pragma once 8 | 9 | #define CODESIZE_MAX 1024 10 | 11 | /*URL编码*/ 12 | char* urlencode(char url[]); 13 | 14 | /*GBK转UTF-8*/ 15 | char* GBKtoUTF8(char* strGbk); 16 | 17 | /*UTF-8转GBK*/ 18 | char* UTF8toGBK(char* strUtf8); -------------------------------------------------------------------------------- /function.c: -------------------------------------------------------------------------------- 1 | #include"Log.h" 2 | #include"function.h" 3 | #include"gocqhttp_Event.h" 4 | #include"gocqhttp_API.h" 5 | 6 | void initialize(void) 7 | { 8 | //注册各个事件函数及一些自己想先初始化的功能 9 | //注册消息事件函数 10 | 11 | //注册请求事件函数 12 | 13 | //注册上报事件函数 14 | registerNoticeFunction(test, notify, "戳一戳"); 15 | } 16 | 17 | /*测试功能*/ 18 | void test(NoticeEventInfo info) 19 | { 20 | if (info.target_id == bot_id && info.subType.notify == poke) 21 | { 22 | send_group_msg_data msg = New_send_group_msg(info.group_id, "不要戳我了!", 0); 23 | send_group_msg(&msg); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tool.c: -------------------------------------------------------------------------------- 1 | #include"Tool.h" 2 | #include 3 | 4 | int getFileSize(FILE* fp) 5 | { 6 | fseek(fp, 0, SEEK_END); 7 | return ftell(fp); 8 | } 9 | 10 | void* malloc_s(size_t size) 11 | { 12 | void* memory; 13 | memory = malloc(size); 14 | if (!memory) 15 | { 16 | puts("内存申请失败!"); 17 | exit(1); 18 | } 19 | return memory; 20 | } 21 | 22 | //去除http消息头 23 | char* removeHeaders(char* httpMessage) 24 | { 25 | int headerSize, contentSize, i; 26 | char* jsonString; 27 | 28 | for (headerSize = 0; httpMessage[headerSize] != '{'; headerSize++) 29 | ; 30 | contentSize = strlen(httpMessage) - headerSize + 1; 31 | jsonString = (char*)malloc_s(contentSize); 32 | for (i = 0; i < contentSize; i++, headerSize++) 33 | { 34 | jsonString[i] = httpMessage[headerSize]; 35 | } 36 | 37 | return jsonString; 38 | } -------------------------------------------------------------------------------- /go-cqhttp-C.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "go-cqhttp-C", "go-cqhttp-C.vcxproj", "{B4C280F3-49C7-4AFF-96E1-4745CA8CF455}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Debug|x64.ActiveCfg = Debug|x64 17 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Debug|x64.Build.0 = Debug|x64 18 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Debug|x86.ActiveCfg = Debug|Win32 19 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Debug|x86.Build.0 = Debug|Win32 20 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Release|x64.ActiveCfg = Release|x64 21 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Release|x64.Build.0 = Release|x64 22 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Release|x86.ActiveCfg = Release|Win32 23 | {B4C280F3-49C7-4AFF-96E1-4745CA8CF455}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {C8AE278C-7510-437F-BF64-673D678F1C8D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _LOG_H_ 4 | #define _LOG_H_ 5 | 6 | /*设置控制台文字颜色 7 | * color code 8 | * 控制台:11(蓝白) 9 | * 出错:4(红) 10 | * 信息:9(蓝) 11 | * 警告:14(黄) 12 | */ 13 | int color(int a); 14 | 15 | #define DEFAULT_COLOR 11 16 | #define ERROR_COLOR 4 17 | #define INFO_COLOR 9 18 | #define WARNING_COLOR 14 19 | 20 | typedef enum _log_type 21 | { 22 | Information, //一般信息 23 | Warning, //一般警告 24 | Error, //一般错误 25 | WSAStartupError, //WSAStartup函数错误 26 | SocketInitError, //socket初始化异常 27 | BindError, //绑定端口失败 28 | ListenError, //监听端口失败 29 | AcceptFailed, //接受连接失败 30 | ConnectionError, //连接错误 31 | NetworkIOException, //网络IO错误 32 | NULLException, //空指针异常 33 | NotFound, //文件未找到 34 | StringException, //字符串操作异常 35 | Win32Exception, //调用win api异常 36 | AllocNULLEXception //内存申请函数调用异常 37 | }LogType; //错误类型 38 | 39 | //日志输出(根据预设) 40 | void logPreset(LogType type); 41 | void logPresetDetailed(char* functionName, char* filePath, int line, LogType type); 42 | #define logPresetAll(type) logPresetDetailed(__func__, __FILE__, __LINE__, type) 43 | 44 | //信息输出 45 | void logInfo(char* message, ...); 46 | void logInfoDetailed(char* functionName, char* filePath, int line, char* message, ...); 47 | #define logInfoAll(message,...) logInfoDetailed(__func__, __FILE__, __LINE__, message, __VA_ARGS__) 48 | 49 | //警告输出 50 | void logWarn(char* message, ...); 51 | void logWarnDetailed(char* functionName, char* filePath, int line, char* message, ...); 52 | #define logWarnAll(message,...) logWarnDetailed(__func__, __FILE__, __LINE__, message, __VA_ARGS__) 53 | 54 | //错误输出 55 | void logErr(char* message, ...); 56 | void logErrDetailed(char* functionName, char* filePath, int line, char* message, ...); 57 | #define logErrAll(message,...) logErrDetailed(__func__, __FILE__, __LINE__, message, __VA_ARGS__) 58 | 59 | #include 60 | 61 | static time_t now; 62 | static struct tm timeinfo; 63 | 64 | #include 65 | 66 | static FILE* logFile; 67 | 68 | /*初始化日志模块*/ 69 | void init_Log(); 70 | 71 | #endif -------------------------------------------------------------------------------- /AnaJSON.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 此JSON解析库暂不支持 浮点类型解析 3 | */ 4 | 5 | #ifndef _ANAJSON_H_ 6 | #define _ANAJSON_H_ 7 | 8 | typedef enum _json_type 9 | { 10 | IntType, //整型 11 | //DoubleType, //浮点型 12 | StringType, //字符串类型 13 | BoolType, //布尔类型 14 | ArrayType, //数组 15 | ObjectType //对象 16 | }JSONType; //JSON对象数据类型 17 | 18 | typedef struct _json 19 | { 20 | struct _json* next; 21 | struct _json* pre; 22 | char* name; 23 | JSONType type; 24 | union _json_data 25 | { 26 | struct _json* jsonData; //对象数据和数组数据 27 | int intData; 28 | //double doubleData; 29 | char* stringData; 30 | int boolData; 31 | }data; 32 | }JSON; 33 | 34 | //NULL from stdio.h 35 | #ifndef NULL 36 | #ifdef __cplusplus 37 | #ifndef _WIN64 38 | #define NULL 0 39 | #else 40 | #define NULL 0LL 41 | #endif /* W64 */ 42 | #else 43 | #define NULL ((void *)0) 44 | #endif 45 | #endif 46 | 47 | //创建JSON对象 48 | JSON* CreateJSON(char* name, JSONType type); 49 | 50 | //添加数据 51 | int AddJSON(JSON* json, char* name, JSONType type); 52 | int AddIntToArrayJSON(JSON* json, int intData); 53 | //int AddDoubleToArrayJSON(JSON* json, double doubleData); 54 | int AddStringToArrayJSON(JSON* json, const char* string); 55 | int AddObjectToArrayJSON(JSON* json, JSON* jsonData); 56 | 57 | //编辑数据 58 | int SetIntJSON(JSON* json, char* name, int intData); 59 | //int SetDoubleJSON(JSON* json, char* name, double doubleData); 60 | int SetStringJSON(JSON* json, char* name, char* string); 61 | int SetObjectJSON(JSON* json, char* name, JSON* jsonData); 62 | 63 | /*JSON工具*/ 64 | //寻找JSON节点 65 | JSON* FindJSON(JSON* json, const char* name); 66 | 67 | //解析字符串到JSON 68 | JSON* StringToJSON(const char* string); 69 | 70 | //释放JSON数据 71 | void DestoryJSON(JSON* data); 72 | //释放JSON节点数据 73 | void FreeJSON(JSON* data); 74 | 75 | /*获取对象的值(返回错误码,0为正常,-1为异常)*/ 76 | //JSON对象 77 | int getJSONValue(JSON*value, JSON* root, const char* name); 78 | //int整数 79 | int getIntValue(int* value, JSON* root, const char* name); 80 | //String字符串 81 | int getStringValue(char* value, JSON* root, const char* name); 82 | //Bool布尔 83 | int getBoolValue(int* value, JSON* root, const char* name); 84 | //Array(数组类型) 85 | //int getArrayValue(ValueType* value, JSON* root, const char* name); 86 | 87 | //字符串转数字 88 | int StringToInt(char* string); 89 | 90 | #endif // !_ANAJSON_H_ 91 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include"URLcode.h" 2 | #include"gocqhttp_API.h" 3 | #include"gocqhttp_Event.h" 4 | #include"robot.h" 5 | #include "Log.h" 6 | #include"AnaJSON.h" 7 | #include"function.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #pragma comment(lib, "ws2_32.lib") 16 | 17 | const char ip[] = "127.0.0.1"; 18 | const int port_API = 5700; 19 | const int port_Event = 5701; 20 | 21 | void event_switch(void* data); //事件检索 22 | 23 | int main(void) 24 | { 25 | init_Log(); 26 | 27 | RESTART: 28 | 29 | init_gocqhttpAPI(ip, port_API); 30 | 31 | init_gocqhttpEvent(ip, port_Event, event_switch, initialize); 32 | 33 | int recvErrorCode = recv_event(); 34 | if (recvErrorCode != 0) 35 | { 36 | logWarn("当前连接已异常中断,错误码:%d,5秒后开始重连", recvErrorCode); 37 | Sleep(5000); 38 | goto RESTART; 39 | } 40 | 41 | return 0; 42 | } 43 | 44 | /*事件检索*/ 45 | void event_switch(void* data) 46 | { 47 | char* event_msg = (char*)data; //待处理的事件 48 | char* jsonString; 49 | JSON* eventInfo; 50 | MessageEventInfo messageEvent; 51 | RequestEventInfo requestEvent; 52 | NoticeEventInfo noticeEvent; 53 | 54 | jsonString = removeHeaders(event_msg); 55 | eventInfo = StringToJSON(jsonString); 56 | 57 | switch (event_type_switch(eventInfo)) 58 | { 59 | case message_event: //消息事件 60 | logInfo("接收 消息事件"); 61 | messageEvent = message_event_analysis(eventInfo); 62 | message_event_responded(messageEvent); 63 | break; 64 | case request_event: //请求事件 65 | logInfo("接收 请求事件"); 66 | requestEvent = request_event_analysis(eventInfo); 67 | request_event_responded(requestEvent); 68 | break; 69 | case notice_event: //上报事件 70 | logInfo("接收 上报事件"); 71 | noticeEvent = notice_event_analysis(eventInfo); 72 | notice_event_responded(noticeEvent); 73 | break; 74 | case meta_event: //元事件 75 | logInfo("接收 元事件"); 76 | //code 77 | break; 78 | case unknow_event: //未知事件 79 | logInfo("接收 未知事件"); 80 | //code 81 | break; 82 | } 83 | FreeJSON(eventInfo); 84 | free(jsonString); 85 | free(event_msg); 86 | } 87 | -------------------------------------------------------------------------------- /go-cqhttp-C.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 6 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 7 | 8 | 9 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 10 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {fe808828-42c1-4e98-8e3f-3bdd50e3f5e3} 18 | 19 | 20 | {1a1b5cd6-9cf9-458c-818d-96592e29caee} 21 | 22 | 23 | {e2893dbe-396c-4fcb-a56f-87136e535069} 24 | 25 | 26 | {46d5bc09-fb81-4592-89e7-e35b8b1a955c} 27 | 28 | 29 | 30 | 31 | go-cqhttp 32 | 33 | 34 | go-cqhttp 35 | 36 | 37 | Main 38 | 39 | 40 | Main 41 | 42 | 43 | tool\log 44 | 45 | 46 | tool\AnaJSON 47 | 48 | 49 | tool\code 50 | 51 | 52 | tool 53 | 54 | 55 | Main\function 56 | 57 | 58 | 59 | 60 | go-cqhttp 61 | 62 | 63 | go-cqhttp 64 | 65 | 66 | Main 67 | 68 | 69 | tool\log 70 | 71 | 72 | tool\AnaJSON 73 | 74 | 75 | tool\code 76 | 77 | 78 | tool 79 | 80 | 81 | Main\function 82 | 83 | 84 | -------------------------------------------------------------------------------- /URLcode.c: -------------------------------------------------------------------------------- 1 | /* 本文件代码来自于 2 | * https://blog.csdn.net/langeldep/article/details/6264058 3 | * https://blog.csdn.net/bladeandmaster88/article/details/54800287 4 | * 并稍加修改,并非本作者编写,仅仅是为了初学者能更快地进行机器人的编写, 5 | * 并非抄袭之意,如有侵权请联系我 6 | */ 7 | #include "URLcode.h" 8 | #include"Tool.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef _CRT_SECURE_NO_WARNINGS 16 | #define _CRT_SECURE_NO_WARNINGS 17 | #endif 18 | 19 | static int hex2dec(char c); 20 | static char dec2hex(short c); 21 | 22 | int hex2dec(char c) 23 | { 24 | if ('0' <= c && c <= '9') 25 | return c - '0'; 26 | else if ('a' <= c && c <= 'f') 27 | return c - 'a' + 10; 28 | else if ('A' <= c && c <= 'F') 29 | return c - 'A' + 10; 30 | else 31 | return -1; 32 | } 33 | 34 | char dec2hex(short c) 35 | { 36 | if (0 <= c && c <= 9) 37 | return c + '0'; 38 | else if (10 <= c && c <= 15) 39 | return c + 'A' - 10; 40 | else 41 | return -1; 42 | } 43 | 44 | /*URL编码 */ 45 | char* urlencode(char url[]) 46 | { 47 | int i; 48 | int len = strlen(url); 49 | int res_len = 0; 50 | char* res = (char*)malloc(CODESIZE_MAX + 1); 51 | if (!res) 52 | return NULL; 53 | memset(res, 0, 1025); 54 | for (i = 0; i < len; ++i) 55 | { 56 | char c = url[i]; 57 | if (('0' <= c && c <= '9') || 58 | ('a' <= c && c <= 'z') || 59 | ('A' <= c && c <= 'Z') || 60 | c == '/' || c == '.') 61 | res[res_len++] = c; 62 | else 63 | { 64 | int j = (short)c; 65 | if (j < 0) 66 | j += 256; 67 | int i1, i0; 68 | i1 = j / 16; 69 | i0 = j - i1 * 16; 70 | res[res_len++] = '%'; 71 | res[res_len++] = dec2hex(i1); 72 | res[res_len++] = dec2hex(i0); 73 | } 74 | } 75 | res[res_len] = '\0'; 76 | return res; 77 | } 78 | 79 | /*GBK转UTF-8*/ 80 | char* GBKtoUTF8(char* strGbk) 81 | { 82 | //gbk转unicode 83 | int len = MultiByteToWideChar(CP_ACP, 0, strGbk, -1, NULL, 0); 84 | wchar_t* strUnicode = (wchar_t*)malloc(sizeof(wchar_t) * len + 2); 85 | if (!strUnicode) 86 | return NULL; 87 | wmemset(strUnicode, 0, len); 88 | MultiByteToWideChar(CP_ACP, 0, strGbk, -1, strUnicode, len); 89 | 90 | //unicode转UTF-8 91 | len = WideCharToMultiByte(CP_UTF8, 0, strUnicode, -1, NULL, 0, NULL, NULL); 92 | char* strUtf8 = (char*)malloc(sizeof(char) * len + 1); 93 | if (!strUtf8) 94 | return NULL; 95 | memset(strUtf8, 0, len + 1); 96 | WideCharToMultiByte(CP_UTF8, 0, strUnicode, -1, strUtf8, len, NULL, NULL); 97 | 98 | char* strTemp = (char*)malloc(sizeof(char) * strlen(strUtf8) + 1); 99 | if (!strTemp) 100 | return NULL; 101 | strcpy_s(strTemp, sizeof(char) * strlen(strUtf8) + 1, strUtf8);//此时的strTemp是UTF-8编码 102 | free(strUnicode); 103 | free(strUtf8); 104 | return strTemp; 105 | } 106 | 107 | /*UTF-8转GBK*/ 108 | char* UTF8toGBK(char* strUtf8) 109 | { 110 | //UTF-8转unicode 111 | int len = MultiByteToWideChar(CP_UTF8, 0, strUtf8, -1, NULL, 0); 112 | wchar_t* strUnicode = (wchar_t*)malloc(sizeof(wchar_t) * len + 2);//len = 2 113 | if (!strUnicode) 114 | return NULL; 115 | wmemset(strUnicode, 0, len); 116 | MultiByteToWideChar(CP_UTF8, 0, strUtf8, -1, strUnicode, len); 117 | 118 | //unicode转gbk 119 | len = WideCharToMultiByte(CP_ACP, 0, strUnicode, -1, NULL, 0, NULL, NULL); 120 | char* strGbk = (char*)malloc(len + 1);//len=3 本来为2,但是char*后面自动加上了\0 121 | if (!strGbk) 122 | return NULL; 123 | memset(strGbk, 0, len + 1); 124 | WideCharToMultiByte(CP_ACP, 0, strUnicode, -1, strGbk, len + 1, NULL, NULL); 125 | 126 | char* strTemp = (char*)malloc(sizeof(char) * strlen(strGbk) + 1);//此时的strTemp是GBK编码 127 | if (!strTemp) 128 | return NULL; 129 | strcpy_s(strTemp, sizeof(char) * strlen(strGbk) + 1, strGbk); 130 | free(strUnicode); 131 | free(strGbk); 132 | return strTemp; 133 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **go-cqhttp-C简介** 2 | * 这是一个基于[go-cqhttp](https://github.com/Mrs4s/go-cqhttp)的C语言QQ机器人开源框架,大致可分为 API操作和 3 | 事件接收解析响应调用两个部分,但只能在windows系统上使用。 4 | > 制作这个的初衷是为了方便开发者能更加快捷的使用C语言制作出QQ机器人(虽说我不觉得除了我还有谁会用C语言写机器人,这个项目算是自己想用C语言写机器人的所做的产物) 5 | 6 | ### 在使用此框架后,可以让开发QQ机器人的门槛下降一大截台阶,对于萌新十分友好,甚至可以拿来练练手,让自己有更多的开发经验。 7 | >### 框架较之前有了很大的改进这一年的时间里,前前后后重构了三次,日志模块重写了两次,JSON解析模块还没全部完成就直接重写,事件的响应机制也重写了,删去了很多冗杂的东西,现在框架整体易用了很多 8 | 9 | *** 10 | 11 | 因为本框架是基于go-cqhttp编写,所以在使用本框架时,需要同时使用 12 | go-cqhttp开发文档来查询(其实直接看代码的注释也可以用,结构的成员都有详细的注释)。 13 | > **特别注意:**
14 | > 部署go-cqhttp时,配置文件85行以后直接按这个复制,如果熟悉本框架的话当我没说 15 | > ```yml 16 | ># 连接服务列表 17 | >servers: 18 | > # 添加方式,同一连接方式可添加多个,具体配置说明请查看文档 19 | > #- http: # http 通信 20 | > #- ws: # 正向 Websocket 21 | > #- ws-reverse: # 反向 Websocket 22 | > #- pprof: #性能分析服务器 23 | > # HTTP 通信设置 24 | > - http: 25 | > # 服务端监听地址 26 | > host: 127.0.0.1 27 | > # 服务端监听端口 28 | > port: 5700 29 | > # 反向HTTP超时时间, 单位秒 30 | > # 最小值为5,小于5将会忽略本项设置 31 | > timeout: 5 32 | > # 长轮询拓展 33 | > long-polling: 34 | > # 是否开启 35 | > enabled: false 36 | > # 消息队列大小,0 表示不限制队列大小,谨慎使用 37 | > max-queue-size: 2000 38 | > middlewares: 39 | > <<: *default # 引用默认中间件 40 | > # 反向HTTP POST地址列表 41 | > post: 42 | > #- url: '' # 地址 43 | > # secret: '' # 密钥 44 | > - url: http://127.0.0.1:5701/ # 地址 45 | > # secret: '' # 密钥 46 | > ``` 47 | 48 | 本框架主要主要分为三大模块 49 | ### 实现go-cqhttp的API操作及事件接收响应调用 50 | * gocqhttp_API.* 51 | * gocqhttp_Event.* 52 | 53 | ### 机器人主体(机器人各功能实现) 54 | * main.c 55 | * robot.* 56 | * function.* 57 | 58 | ### 工具模块 59 | * AnaJSON.* 60 | * URLcode.* 61 | * Log.* 62 | * Tool.* 63 | 64 |
65 | 66 | # 使用方法 67 | * 把项目克隆后直接在克隆的目录创建项目把源码文件全部添加进去(CMakeKists.txt文件我不会写,先这样用吧) 68 | * 项目创建好后,别的文件都不要动,直接在function.*两个文件里声明并实现就行,功能实现完后按照功能类型再注册就完成了。 69 | 70 | ## **机器人各功能的事件响应** 71 | 72 | go-cqhttp目前一共有五种类型响应,此框架目前仅实现了其中用得到的三种,分别为:消息事件、请求事件和上报事件(详情参考[官方文档](https://github.com/ishkong/go-cqhttp-docs/tree/main/docs/event)) 73 | > 在`function.c`文件中的`initialize()`函数实现中,调用三个注册函数即可完成对功能的注册已经自动调用(功能函数需按照规定声明),这三个函数分别为: 74 | >```c 75 | >registerMessageFunction()//注册消息事件功能 76 | >registerRequestFunction()//注册请求事件功能 77 | >registerNoticeFunction()//注册上报类型功能 78 | >``` 79 | >* 当某个功能注册失败时会返回-1
80 | >* 每个功能都要有一个名字,相当于她们的id,这个id为字符串类型`char*`,为三个函数的最后一个参数
81 | >* 三个函数的第一个函数,都是要注册的功能函数,他们的声明都有着各自的规则: 82 | > 1. 消息事件 `void (*function)(MessageEventInfo)`返回`void`类型,有一个`MessageEventInfo`类型参数,这个类型包含了最基本的消息事件数据,相比cqhttp精简了很多,可以直接翻cqhttp的文档,或者直接看我从文档抄下来的注释 83 | > 2. 请求事件 `void (*function)(RequestEventInfo)`同上,只不过是换了个参数类型 84 | > 3. 上报事件`void (*function)(NoticeEventInfo)`同上同上 85 | 86 | ## **API的使用还是和以前一样,分为以下两步**: 87 | 1. 初始化发包(union类型) 88 | 2. 调用API 89 | > API与go-cqhttp同名,包括发包 90 | 91 | **大致如下:** 92 | ```c 93 | API名_data data = New_API名; //初始化发包(union) 94 | API名(&data); //代入指针,调用API 95 | ``` 96 | 调用API后,data的数据会被替换成API的返回数据,通过 97 | ` 98 | data.recv_data 99 | ` 100 | 可以查看
101 | 102 | 103 | 不过值得注意的一点是,C语言默认使用gbk字符集,但cqhttp只会接收utf8字符集或URL编码,否则会乱码,所以在API中包含了自动转码(转码函数包含在`URLcode.h`中),但这并不意味着开发者不需要注意到这个问题因为当你编写其他网络平台接口的时候很可能会乱码
104 | `URLcode.h`和`URLcode.c`的使用方法也很简单,大致如下: 105 | ```c 106 | char* str = 转码函数(data/*原字符串*/); 107 | //使用转码后的data,也就是str 108 | free(str);//记得一定要free!!!因为转码函数转换后的字符串是重新从堆申请的 109 | ``` 110 | ### **funciton.\*里有个小demo,可以参考一下** 111 | 112 | --- 113 | 114 | ## **日志模块** 115 | 日志模块较之前有了很大不同(听了大佬的建议做很多修改),现在模块可直接使用格式化字符串(printf一样的用法),也不用再声明一个错误类型
116 | >使用日志模块前要先调用`init_Log()`函数初始化(此框架已经调用过,不用再额外调用),日志会直接写入到`log`文件里 117 | 118 | 现在,日志模块输出分为三个等级,分比为:info、warn和err 119 | ```c 120 | logInfo() 121 | logWarn() 122 | logErr() 123 | ``` 124 | 这三个函数只会输出最基本的一些信息,及时间、日期、等级和日志,如果需要输出一些更完整的信息,比如:代码行、函数名、文件等,则需调用其对应的三个宏: 125 | ```c 126 | logInfoAll() 127 | logWarnAll() 128 | logErrAll() 129 | ``` 130 | 这三个宏的用法和三个函数一样,只是输出更完整了一些
131 | 不过还有一个比较特殊的 132 | ```c 133 | logPreset() 134 | logPresetAll() 135 | ``` 136 | 这个函数和宏接受的是预设好的错误类型参数,预设可以更具需求更改 137 | 138 |
139 | 140 | # 已知但还未修复的BUG 141 | **后天就开学了,修不了了** 142 | 1. JSON解析模块解析JSON对象时可能会出错,直接导致API `get_msg()` 无法使用 143 | 2. JSON解析模块解析CQ码时可能会出错 144 | 3. `NoticeEventInfo`类型的`comment`成员还不可用,因为我还没来得及写 145 | 4. API发送数据后如果没有收到响应可能会进入死循环,跳出循环机制还没加 146 | 147 |
148 | 149 | # **最后感谢各位帮助了我的大佬** -------------------------------------------------------------------------------- /gocqhttp_Event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include"AnaJSON.h" 3 | 4 | #define MESSAGE_DEFAULT_SIZE 1024 5 | 6 | /*消息事件*/ 7 | typedef struct _message_event_info 8 | { 9 | int isPrivate; //是否是私聊消息 10 | unsigned int group_id; //群号,当为私聊消息时,此成员为0 11 | unsigned int self_id; //机器人QQ 12 | unsigned int user_id; //发送人QQ 13 | char nickname[50]; //发送人昵称 14 | int message_id; //消息ID 15 | char message[1024]; //消息 16 | int time; //时间戳 17 | }MessageEventInfo; //消息事件通用数据结构 18 | 19 | /*请求事件*/ 20 | typedef enum _request_event_type 21 | { 22 | friend, 23 | group 24 | }RequestType; 25 | 26 | typedef struct _request_event_info 27 | { 28 | int isFriend; //是否为好友请求 29 | unsigned int group_id; //群号 30 | unsigned int self_id; //机器人QQ 31 | unsigned int user_id; //请求人QQ 32 | char comment[1024]; //验证消息 33 | int time; //时间戳 34 | RequestType type; //请求事件类型 35 | }RequestEventInfo; //请求事件通用数据结构 36 | 37 | /*上报事件*/ 38 | typedef enum _notice_event_type 39 | { 40 | friend_recall, //好友消息撤回 41 | group_recall, //群消息撤回 42 | group_increase, //群成员增加 43 | group_decrease, //群成员减少 44 | group_admin, //群管理员变动 45 | group_upload, //群文件上传 46 | group_ban, //群禁言 47 | friend_add, //好友添加 48 | notify, //一些通知(戳一戳、红包运气王、群荣耀变更、群成员头衔变更) 49 | group_card, //群成员名片更新 50 | offline_file, //接收到离线文件 51 | client_status, //其他客户端在线状态变更 52 | essence //精华消息变更 53 | }NoticeType; 54 | 55 | typedef union _notice_event_sub_type 56 | { 57 | enum 58 | { 59 | approve,//管理员同意 60 | invite //管理员邀请 61 | }group_increase; 62 | enum 63 | { 64 | leave, //主动退群 65 | kick, //成员被踢 66 | kick_me //登录号被踢 67 | }group_decrease; 68 | enum 69 | { 70 | set, //设置管理员 71 | unset //取消管理员 72 | }group_admin; 73 | enum 74 | { 75 | ban, //被禁言 76 | lift_ban//解除禁言 77 | }group_ban; 78 | enum 79 | { 80 | poke, //戳一戳 81 | honor, //群荣耀变更 82 | title //群成语头衔变更 83 | }notify; 84 | enum 85 | { 86 | add, //添加 87 | delete //删除 88 | }essence; 89 | }NoticeSubType; 90 | 91 | typedef struct _notice_event_info 92 | { 93 | int isPrivate; //是否是私人 94 | unsigned int group_id; //群号 95 | unsigned int self_id; //机器人QQ 96 | unsigned int user_id; //触发上报事件的QQ 97 | unsigned int target_id; //目标QQ 98 | char nickname[50]; //触发事件用户的昵称 99 | unsigned int operator_id;//操作者QQ 100 | unsigned int time; //时间戳 101 | unsigned int message_id;//此字段不一定会有,没有的话为0 102 | char comment[1024]; //杂项(一切上报事件所刚需的字符串数据都为这个成员) 103 | NoticeType type; //上报事件各类型 104 | NoticeSubType subType; //上报事件各类型的子类型 105 | }NoticeEventInfo; //上报事件通用数据结构 106 | 107 | //去除HTTP消息头 108 | char* removeHeaders(char* httpMessage); 109 | 110 | #define ACCEPT_COUNT_MAX 20 111 | #define Event_Response "HTTP/1.1 204 OK\r\n\r\n" 112 | 113 | /*事件类型*/ 114 | typedef enum _event_type 115 | { 116 | message_event, //消息 117 | notice_event, //通知 118 | meta_event, //元 119 | request_event, //请求 120 | unknow_event //未知 121 | }event_type; 122 | 123 | /*事件解析*/ 124 | //消息事件 125 | MessageEventInfo message_event_analysis(JSON* eventData); 126 | //请求事件 127 | RequestEventInfo request_event_analysis(JSON* eventData); 128 | //上报事件 129 | NoticeEventInfo notice_event_analysis(JSON* eventData); 130 | 131 | /*事件响应*/ 132 | //消息事件响应 133 | void message_event_responded(MessageEventInfo info); 134 | //请求事件响应 135 | void request_event_responded(RequestEventInfo info); 136 | //上报事件响应 137 | void notice_event_responded(NoticeEventInfo info); 138 | 139 | /*创建Event服务端*/ 140 | void init_gocqhttpEvent( 141 | const char* ip, 142 | const int port, 143 | void(*response)(void* data), //消息事件响应函数 144 | void(*init)(void) //初始化 145 | ); 146 | 147 | /*接收Event*/ 148 | int recv_event(void); 149 | 150 | /*事件检索*/ 151 | event_type event_type_switch(JSON* data); 152 | 153 | /*响应函数列表*/ 154 | typedef void (*messageRespondedFunction)(MessageEventInfo); //消息事件响应函数 155 | typedef void (*requestRespondedFunction)(RequestEventInfo); //请求事件响应函数 156 | typedef void (*noticeRespondedFunction)(NoticeEventInfo); //上报事件响应函数 157 | 158 | //消息事件 159 | typedef struct _message_responded_node 160 | { 161 | messageRespondedFunction function; 162 | char* callName; 163 | struct _message_responded_node* next; 164 | }MessageRespondedNode; 165 | 166 | //请求事件 167 | typedef struct _request_responded_node 168 | { 169 | requestRespondedFunction function; 170 | char* callName; 171 | RequestType type; 172 | struct _request_responded_node* next; 173 | }RequestRespondedNode; 174 | 175 | //上报事件 176 | typedef struct _notice_responded_node 177 | { 178 | noticeRespondedFunction function; 179 | char* callName; 180 | NoticeType type; 181 | struct _notice_responded_node* next; 182 | }NoticeRespondedNode; 183 | 184 | MessageRespondedNode* messageNodeHead; //消息事件响应函数注册列表 185 | RequestRespondedNode* requestNodeHead; //请求事件响应函数注册列表 186 | NoticeRespondedNode* noticeNodeHead; //上报事件响应函数注册列表 187 | 188 | /*注册事件响应函数*/ 189 | //注册消息事件 190 | int registerMessageFunction(messageRespondedFunction func, char* callName); 191 | //注册请求事件 192 | int registerRequestFunction(requestRespondedFunction func, RequestType type, char* callName); 193 | //注册上报事件 194 | int registerNoticeFunction(noticeRespondedFunction func, NoticeType type, char* callName); 195 | 196 | /*查找已注册函数*/ 197 | //查找消息事件函数 198 | messageRespondedFunction findMessageFunction(char* message); 199 | //查找请求事件函数 200 | requestRespondedFunction findRequestFunction(RequestType type); 201 | //查找上报事件函数 202 | noticeRespondedFunction findNoticeFunction(NoticeType type); 203 | 204 | //从消息开头比较名称(若匹配则返回1,否则返回0) 205 | static int cmpMsgwithName(const char* message, const char* name); 206 | 207 | //把RequestType转换成字符串 208 | static char* requestTypetoString(RequestType type); 209 | 210 | //把NoticeType转换成字符串 211 | static char* noticeTypetoString(NoticeType type); 212 | -------------------------------------------------------------------------------- /gocqhttp_API.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _CQHTTP_API_H_ 3 | #define _CQHTTP_API_H_ 4 | 5 | #include"Log.h" 6 | 7 | #define RECV_MAX 100 //最大接收次数 8 | 9 | /*初始化*/ 10 | void init_gocqhttpAPI(const char* ip, const int port); 11 | 12 | ///*退出*/ 13 | //void exit_gocqhttpAPI(void); 14 | 15 | ///////////////////////////////// 16 | /*send_private_msg 发送私密消息*/ 17 | ///////////////////////////////// 18 | 19 | #define API_SEND_PRIVATE_MSG_FORM "GET /send_private_msg?user_id=%u&group_id=%u&message=%s&auto_escape=%d HTTP/1.1\r\nHost: 127.0.0.1:5700\r\nConnection: keep-alive\r\n\r\n" 20 | 21 | typedef struct 22 | { 23 | unsigned long user_id; //对方 QQ 号 24 | unsigned long group_id; //主动发起临时会话群号(机器人本身必须是管理员/群主) 25 | char message[1024]; //要发送的内容 26 | int auto_escape; //消息内容是否作为纯文本发送,只在 message 字段是字符串时有效(默认值 false) 27 | }send_private_msg_s; //发送消息包 28 | 29 | typedef struct 30 | { 31 | struct 32 | { 33 | int message_id; //消息ID 34 | }data; //返回数据 35 | int retcode; //返回码 36 | char status[10]; //状态 37 | }send_private_msg_r; //接收消息包 38 | 39 | typedef union 40 | { 41 | send_private_msg_s send_msg; //发包 42 | send_private_msg_r recv_msg; //收包 43 | }send_private_msg_data;//组合包 44 | 45 | //API 46 | void send_private_msg( 47 | send_private_msg_data* data //发包 48 | ); 49 | 50 | //获取发包 51 | send_private_msg_data New_send_private_msg( 52 | unsigned int user_id, //用户ID 53 | unsigned int group_id, //群号 54 | char message[1024], //消息 55 | int auto_escape //是否纯文本 56 | ); 57 | 58 | ///////////////////////////// 59 | /*send_group_msg 发送群消息*/ 60 | ///////////////////////////// 61 | 62 | #define API_SEND_GROUP_MSG_FORM "GET /send_group_msg?group_id=%u&message=%s&auto_escape=%d HTTP/1.1\r\nHost: 127.0.0.1:5700\r\nConnection: keep-alive\r\n\r\n" 63 | #define API_SEND_GROUP_MSG_RECV "%*[^{]{\"data\":{\"message_id\":%d},\"retcode\":%d,\"status\":\"%[^\"]\"}" 64 | 65 | typedef struct 66 | { 67 | unsigned int group_id; //群号 68 | char message[1024]; //要发送的内容 69 | int auto_escape; //消息内容是否作为纯文本发送,只在 message 字段是字符串时有效(默认值 false) 70 | }send_group_msg_s; //发送消息包 71 | 72 | typedef struct 73 | { 74 | struct 75 | { 76 | int message_id; //消息ID 77 | }data; //返回数据 78 | int retcode; //返回码 79 | char status[10]; //状态 80 | }send_group_msg_r; //接收消息包 81 | 82 | typedef union 83 | { 84 | send_group_msg_s send_msg; //发包 85 | send_group_msg_r recv_msg; //收包 86 | }send_group_msg_data;//组合包 87 | 88 | //API 89 | void send_group_msg( 90 | send_group_msg_data* data //发包 91 | ); 92 | 93 | //获取发包 94 | send_group_msg_data New_send_group_msg( 95 | unsigned int group_id, //群号 96 | char message[1024], //消息 97 | int auto_escape //是否纯文本 98 | ); 99 | 100 | //////////// 101 | /*获取消息*/ 102 | //////////// 103 | 104 | #define API_GET_MSG_FORM "GET /get_msg?message_id=%d HTTP/1.1\r\nHost: 127.0.0.1:5700\r\nConnection: keep-alive\r\n\r\n" 105 | #define API_GET_MSG_RECV "%*[^{]{\"data\":{\"group\":%[^,],\"group_id\":%u,\"message\":\"%[^\"]\",\"message_id\":%d,\"message_id_v2\":\"%[^\"]\",\"message_seq\":%d,\"message_type\":\"%[^\"]\",\"real_id\":%d,\"sender\":{\"nickname\":\"%[^\"]\",\"user_id\":%lu},\"time\":%d},\"retcode\":%d,\"status\":\"%[^\"]\"}" 106 | 107 | typedef struct 108 | { 109 | int message_id; //消息 ID 110 | }get_msg_s; //发送消息包 111 | 112 | typedef struct 113 | { 114 | struct 115 | { 116 | int group; //我也不清楚,大概也用不到(布尔类型) 117 | unsigned int group_id; //群号 118 | int message_id; //消息 ID 119 | char message_id_v2[50]; //消息 ID 2.0版 120 | int real_id; //真实 ID 121 | int message_seq; //我也不知道是干啥的 122 | char message_type[20]; //消息类型 123 | struct 124 | { 125 | char nickname[100]; //昵称 126 | unsigned int user_id; //用户QQ号 127 | }sender; //发送者 128 | int time; //发送时间 129 | char message[1024]; //消息类容 130 | }data; //返回数据 131 | int retcode; //返回码 132 | char status[10]; //状态 133 | }get_msg_r; //接收消息包 134 | 135 | typedef union 136 | { 137 | get_msg_s send_msg; 138 | get_msg_r recv_msg; 139 | }get_msg_data; //组合包 140 | 141 | //API 142 | void get_msg( 143 | get_msg_data* data //发包 144 | ); 145 | 146 | //获取发包 147 | get_msg_data New_get_msg( 148 | int message_id //消息ID 149 | ); 150 | 151 | /////////////////////// 152 | /*delete_msg 撤回消息*/ 153 | /////////////////////// 154 | 155 | #define API_DELETE_MSG_FORM "GET /delete_msg?message_id=%d HTTP/1.1\r\nHost: 127.0.0.1:5700\r\nConnection: keep-alive\r\n\r\n" 156 | #define API_DELETE_MSG_RECV "%*[^{]{\"data\":%[^,],\"retcode\":%d,\"status\":\"%[^\"]\"}" 157 | 158 | typedef struct 159 | { 160 | int message_id; //消息 ID 161 | }delete_msg_s; //发送消息包 162 | 163 | typedef struct 164 | { 165 | char data[10]; //返回数据 166 | int retcode; //返回码 167 | char status[10]; //状态 168 | }delete_msg_r; //接收消息包 169 | 170 | typedef union 171 | { 172 | delete_msg_s send_msg; //发包 173 | delete_msg_r recv_msg; //收包 174 | }delete_msg_data; //组合包 175 | 176 | //API 177 | void delete_msg( 178 | delete_msg_data* data //发包 179 | ); 180 | 181 | //获取发包 182 | delete_msg_data New_delete_msg( 183 | int message_id //消息ID 184 | ); 185 | 186 | ////////////////// 187 | /*获取群成员信息*/ 188 | ////////////////// 189 | 190 | #define API_GET_GROUP_MEMBER_INFO_FORM "GET /get_group_member_info?group_id=%lu&user_id=%lu&no_cache=%d HTTP/1.1\r\nHost: 127.0.0.1:5700\r\nConnection: keep-alive\r\n\r\n" 191 | #define API_GET_GROUP_MEMBER_INFO_RECV "%*[^{]{\"data\":{\"age\":%d,\"area\":%[^,],\"card\":\"%[^\"]\",\"card_changeable\":%[^,],\"group_id\":%lu,\"join_time\":%d,\"last_sent_time\":%d,\"level\":\"%[^\"]\",\"nickname\":\"%[^\"]\",\"role\":\"%[^\"]\",\"sex\":\"%[^\"]\",\"shut_up_timestamp\":%d,\"title\":%[^,],\"title_expire_time\":%lu,\"unfriendly\":%[^,],\"user_id\":%lu},\"retcode\":%d,\"status\":\"%[^\"]\"}" 192 | 193 | typedef struct 194 | { 195 | unsigned long group_id; //群号 196 | unsigned long user_id; //QQ号 197 | int no_cache; //是否不使用缓存(使用缓存可能更新不及时, 但响应更快) 198 | }get_group_member_info_s; //发送消息包 199 | 200 | typedef struct 201 | { 202 | struct 203 | { 204 | unsigned long group_id; //群号 205 | unsigned long user_id; //QQ号 206 | char nickname[50]; //昵称 207 | char card[50]; //群名片/备注 208 | char sex[10]; //性别 209 | int age; //年龄 210 | char area[200]; //地区 211 | int join_time; //加群时间戳 212 | int last_sent_time; //最后发言时间戳 213 | char level[50]; //成员等级 214 | char role[10]; //角色 215 | int unfriendly; //是否不良记录成员 216 | char title[50]; //专属头衔 217 | long title_expire_time; //专属头衔过期时间戳 218 | int card_changeable; //是否允许修改群名片 219 | int shut_up_timestamp; 220 | }data; //返回数据 221 | int retcode; //返回码 222 | char status[10]; //状态 223 | }get_group_member_info_r; //接收消息包 224 | 225 | typedef union 226 | { 227 | get_group_member_info_s send_msg; 228 | get_group_member_info_r recv_msg; 229 | }get_group_member_info_data; //组合包 230 | 231 | //API 232 | void get_group_member_info( 233 | get_group_member_info_data* data 234 | ); 235 | 236 | //获取发包 237 | get_group_member_info_data New_get_group_member_info( 238 | unsigned long group_id, //群号 239 | unsigned long user_id, //QQ号 240 | int no_cache //是否不使用缓存(使用缓存可能更新不及时, 但响应更快) 241 | ); 242 | 243 | #endif // !_CQHTTP_API_H_ 244 | -------------------------------------------------------------------------------- /go-cqhttp-C.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {b4c280f3-49c7-4aff-96e1-4745ca8cf455} 25 | gocqhttpC 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | true 91 | 92 | 93 | Console 94 | true 95 | 96 | 97 | 98 | 99 | Level3 100 | true 101 | true 102 | true 103 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | true 105 | 106 | 107 | Console 108 | true 109 | true 110 | true 111 | 112 | 113 | 114 | 115 | Level3 116 | true 117 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | true 119 | 120 | 121 | Console 122 | true 123 | 124 | 125 | 126 | 127 | Level3 128 | true 129 | true 130 | true 131 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 132 | true 133 | 134 | 135 | Console 136 | true 137 | true 138 | true 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /Log.c: -------------------------------------------------------------------------------- 1 | #include"Log.h" 2 | #include"Tool.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct _logTypeInfoBase 14 | { 15 | LogType baseType; //预设类型的基本类型(信息,警告,错误) 16 | char* info; 17 | }; 18 | typedef struct _logTypeInfoBase LogTypeInfoBase; 19 | static LogTypeInfoBase setBaseType(LogType type, char* info); 20 | 21 | /*获取日志类型基本信息*/ 22 | static LogTypeInfoBase getLogTypeInfo(LogType type); 23 | 24 | //生成当前格式化时间 25 | static void getNowFormatTime(char* formatTime); 26 | 27 | //判断是否初始化 28 | void isLogInit(); 29 | 30 | //日志输出(根据预设) 31 | void logPreset(LogType type) 32 | { 33 | LogTypeInfoBase baseType; 34 | baseType = getLogTypeInfo(type); 35 | switch (baseType.baseType) 36 | { 37 | case Information: 38 | logInfo(baseType.info); 39 | break; 40 | case Warning: 41 | logWarn(baseType.info); 42 | break; 43 | case Error: 44 | logErr(baseType.info); 45 | break; 46 | } 47 | } 48 | void logPresetDetailed(char* functionName, char* filePath, int line, LogType type) 49 | { 50 | LogTypeInfoBase baseType; 51 | baseType = getLogTypeInfo(type); 52 | switch (baseType.baseType) 53 | { 54 | case Information: 55 | logInfoDetailed(functionName, filePath, line, baseType.info); 56 | break; 57 | case Warning: 58 | logWarnDetailed(functionName, filePath, line, baseType.info); 59 | break; 60 | case Error: 61 | logErrDetailed(functionName, filePath, line, baseType.info); 62 | break; 63 | } 64 | } 65 | 66 | //信息输出 67 | void logInfo(char* message, ...) 68 | { 69 | va_list(va); 70 | va_start(va, message); 71 | FILE* tempFile; 72 | char tempFilePath[40] = { '\0' }; 73 | char fileName[11] = { '\0' }; 74 | char* completeMessage; 75 | int messageSize; 76 | 77 | isLogInit(); 78 | 79 | char nowFormatTime[30]; 80 | getNowFormatTime(nowFormatTime); 81 | 82 | //把格式化字符串暂存到一个文件 83 | strncpy_s(fileName, sizeof(fileName), nowFormatTime, 10);//只截取 YYYY-MM-DD 的部分 84 | sprintf_s(tempFilePath, 40, "temp/%s", fileName); 85 | //fopen_s(&tempFile, tempFilePath, "wb+"); 86 | tmpfile_s(&tempFile); 87 | vfprintf(tempFile, message, va); 88 | 89 | messageSize = getFileSize(tempFile) + 1;//+1 补空字符的位置 90 | completeMessage = (char*)malloc_s(messageSize); 91 | fseek(tempFile, 0L, SEEK_SET); 92 | fread_s(completeMessage, messageSize, messageSize, 1, tempFile); 93 | completeMessage[messageSize - 1] = '\0';//在最后补空字符 94 | 95 | color(INFO_COLOR); 96 | printf("%s %s\n", nowFormatTime, completeMessage); 97 | color(DEFAULT_COLOR); 98 | fprintf(logFile, "%s %s\n", nowFormatTime, completeMessage); 99 | 100 | fclose(tempFile); 101 | free(completeMessage); 102 | //remove(tempFilePath); 103 | } 104 | void logInfoDetailed(char* functionName, char*filePath, int line, char* message, ...) 105 | { 106 | va_list(va); 107 | va_start(va, message); 108 | FILE* tempFile; 109 | char tempFilePath[40] = { '\0' }; 110 | char fileName[11] = { '\0' }; 111 | char* completeMessage; 112 | int messageSize; 113 | 114 | isLogInit(); 115 | 116 | char nowFormatTime[30]; 117 | getNowFormatTime(nowFormatTime); 118 | 119 | //把格式化字符串暂存到一个文件 120 | strncpy_s(fileName, sizeof(fileName), nowFormatTime, 10);//只截取 YYYY-MM-DD 的部分 121 | sprintf_s(tempFilePath, 40, "temp/%s", fileName); 122 | //fopen_s(&tempFile, tempFilePath, "wb+"); 123 | tmpfile_s(&tempFile); 124 | vfprintf(tempFile, message, va); 125 | 126 | messageSize = getFileSize(tempFile) + 1;//+1 补空字符的位置 127 | completeMessage = (char*)malloc_s(messageSize); 128 | fseek(tempFile, 0L, SEEK_SET); 129 | fread_s(completeMessage, messageSize, messageSize, 1, tempFile); 130 | completeMessage[messageSize - 1] = '\0';//在最后补空字符 131 | 132 | color(INFO_COLOR); 133 | printf("%s %s\nfunction: %s\npath: %s\nline: %d\n\n", nowFormatTime, completeMessage, functionName, filePath, line); 134 | color(DEFAULT_COLOR); 135 | fprintf(logFile, "%s %s\nfunction: %s\npath: %s\nline: %d\n\n", nowFormatTime, completeMessage, functionName, filePath, line); 136 | 137 | fclose(tempFile); 138 | free(completeMessage); 139 | //remove(tempFilePath); 140 | } 141 | 142 | //警告输出 143 | void logWarn(char* message, ...) 144 | { 145 | va_list(va); 146 | va_start(va, message); 147 | FILE* tempFile; 148 | char tempFilePath[40] = { '\0' }; 149 | char fileName[11] = { '\0' }; 150 | char* completeMessage; 151 | int messageSize; 152 | 153 | isLogInit(); 154 | 155 | char nowFormatTime[30]; 156 | getNowFormatTime(nowFormatTime); 157 | 158 | //把格式化字符串暂存到一个文件 159 | strncpy_s(fileName, sizeof(fileName), nowFormatTime, 10);//只截取 YYYY-MM-DD 的部分 160 | sprintf_s(tempFilePath, 40, "temp/%s", fileName); 161 | //fopen_s(&tempFile, tempFilePath, "wb+"); 162 | tmpfile_s(&tempFile); 163 | vfprintf(tempFile, message, va); 164 | 165 | messageSize = getFileSize(tempFile) + 1;//+1 补空字符的位置 166 | completeMessage = (char*)malloc_s(messageSize); 167 | fseek(tempFile, 0L, SEEK_SET); 168 | fread_s(completeMessage, messageSize, messageSize, 1, tempFile); 169 | completeMessage[messageSize - 1] = '\0';//在最后补空字符 170 | 171 | color(WARNING_COLOR); 172 | printf("%s %s\n", nowFormatTime, completeMessage); 173 | color(DEFAULT_COLOR); 174 | fprintf(logFile, "%s %s\n", nowFormatTime, completeMessage); 175 | 176 | fclose(tempFile); 177 | free(completeMessage); 178 | //remove(tempFilePath); 179 | } 180 | void logWarnDetailed(char* functionName, char* filePath, int line, char* message, ...) 181 | { 182 | va_list(va); 183 | va_start(va, message); 184 | FILE* tempFile; 185 | char tempFilePath[40] = { '\0' }; 186 | char fileName[11] = { '\0' }; 187 | char* completeMessage; 188 | int messageSize; 189 | 190 | isLogInit(); 191 | 192 | char nowFormatTime[30]; 193 | getNowFormatTime(nowFormatTime); 194 | 195 | //把格式化字符串暂存到一个文件 196 | strncpy_s(fileName, sizeof(fileName), nowFormatTime, 10);//只截取 YYYY-MM-DD 的部分 197 | sprintf_s(tempFilePath, 40, "temp/%s", fileName); 198 | //fopen_s(&tempFile, tempFilePath, "wb+"); 199 | tmpfile_s(&tempFile); 200 | vfprintf(tempFile, message, va); 201 | 202 | messageSize = getFileSize(tempFile) + 1;//+1 补空字符的位置 203 | completeMessage = (char*)malloc_s(messageSize); 204 | fseek(tempFile, 0L, SEEK_SET); 205 | fread_s(completeMessage, messageSize, messageSize, 1, tempFile); 206 | completeMessage[messageSize - 1] = '\0';//在最后补空字符 207 | 208 | color(WARNING_COLOR); 209 | printf("%s %s\nfunction: %s\npath: %s\nline: %d\n\n", nowFormatTime, completeMessage, functionName, filePath, line); 210 | color(DEFAULT_COLOR); 211 | fprintf(logFile, "%s %s\nfunction: %s\npath: %s\nline: %d\n\n", nowFormatTime, completeMessage, functionName, filePath, line); 212 | 213 | fclose(tempFile); 214 | free(completeMessage); 215 | //remove(tempFilePath); 216 | } 217 | 218 | //错误输出 219 | void logErr(char* message, ...) 220 | { 221 | va_list(va); 222 | va_start(va, message); 223 | FILE* tempFile; 224 | char tempFilePath[40] = { '\0' }; 225 | char fileName[11] = { '\0' }; 226 | char* completeMessage; 227 | int messageSize; 228 | 229 | isLogInit(); 230 | 231 | char nowFormatTime[30]; 232 | getNowFormatTime(nowFormatTime); 233 | 234 | //把格式化字符串暂存到一个文件 235 | strncpy_s(fileName, sizeof(fileName), nowFormatTime, 10);//只截取 YYYY-MM-DD 的部分 236 | sprintf_s(tempFilePath, 40, "temp/%s", fileName); 237 | //fopen_s(&tempFile, tempFilePath, "wb+"); 238 | tmpfile_s(&tempFile); 239 | vfprintf(tempFile, message, va); 240 | 241 | messageSize = getFileSize(tempFile) + 1;//+1 补空字符的位置 242 | completeMessage = (char*)malloc_s(messageSize); 243 | fseek(tempFile, 0L, SEEK_SET); 244 | fread_s(completeMessage, messageSize, messageSize, 1, tempFile); 245 | completeMessage[messageSize - 1] = '\0';//在最后补空字符 246 | 247 | color(4); 248 | printf("%s %s\n", nowFormatTime, completeMessage); 249 | color(7); 250 | fprintf(logFile, "%s %s\n", nowFormatTime, completeMessage); 251 | 252 | fclose(tempFile); 253 | free(completeMessage); 254 | //remove(tempFilePath); 255 | } 256 | void logErrDetailed(char* functionName, char* filePath, int line, char* message, ...) 257 | { 258 | va_list(va); 259 | va_start(va, message); 260 | FILE* tempFile; 261 | char tempFilePath[40] = { '\0' }; 262 | char fileName[11] = { '\0' }; 263 | char* completeMessage; 264 | int messageSize; 265 | 266 | isLogInit(); 267 | 268 | char nowFormatTime[30]; 269 | getNowFormatTime(nowFormatTime); 270 | 271 | //把格式化字符串暂存到一个文件 272 | strncpy_s(fileName, sizeof(fileName), nowFormatTime, 10);//只截取 YYYY-MM-DD 的部分 273 | sprintf_s(tempFilePath, 40, "temp/%s", fileName); 274 | fopen_s(&tempFile, tempFilePath, "wb+"); 275 | vfprintf(tempFile, message, va); 276 | 277 | messageSize = getFileSize(tempFile) + 1;//+1 补空字符的位置 278 | completeMessage = (char*)malloc_s(messageSize); 279 | fseek(tempFile, 0L, SEEK_SET); 280 | fread_s(completeMessage, messageSize, messageSize, 1, tempFile); 281 | completeMessage[messageSize - 1] = '\0';//在最后补空字符 282 | 283 | color(ERROR_COLOR); 284 | printf("%s %s\nfunction: %s\npath: %s\nline: %d\n\n", nowFormatTime, completeMessage, functionName, filePath, line); 285 | color(DEFAULT_COLOR); 286 | fprintf(logFile, "%s %s\nfunction: %s\npath: %s\nline: %d\n\n", nowFormatTime, completeMessage, functionName, filePath, line); 287 | 288 | fclose(tempFile); 289 | free(completeMessage); 290 | remove(tempFilePath); 291 | } 292 | 293 | //生成当前格式化时间 294 | static void getNowFormatTime(char* formatTime) 295 | { 296 | if (!formatTime) 297 | return; 298 | time(&now); 299 | localtime_s(&timeinfo, &now); 300 | sprintf_s(formatTime, 30, "%d-%d-%d %d:%d:%d", 301 | timeinfo.tm_year + 1900, 302 | timeinfo.tm_mon, 303 | timeinfo.tm_mday, 304 | timeinfo.tm_hour, 305 | timeinfo.tm_min, 306 | timeinfo.tm_sec); 307 | } 308 | 309 | static LogTypeInfoBase getLogTypeInfo(LogType type) 310 | { 311 | switch (type) 312 | { 313 | case Information: 314 | return setBaseType(Information, "Information"); 315 | case Warning: 316 | return setBaseType(Warning, "Warning"); 317 | case Error: 318 | return setBaseType(Error, "Error"); 319 | case WSAStartupError: 320 | return setBaseType(Error, "Failed to open socket service"); 321 | case SocketInitError: 322 | return setBaseType(Error, "Initialization scoket error"); 323 | case BindError: 324 | return setBaseType(Error, "Binding port failed"); 325 | case ListenError: 326 | return setBaseType(Error, "Listening port failed"); 327 | case AcceptFailed: 328 | return setBaseType(Warning, "Failed to accept connection"); 329 | case ConnectionError: 330 | return setBaseType(Error, "Connection server error"); 331 | case NetworkIOException: 332 | return setBaseType(Warning, "Failed to send or recv"); 333 | case NULLException: 334 | return setBaseType(Warning, "The pointer is null"); 335 | case NotFound: 336 | return setBaseType(Warning, "Not found the file"); 337 | case StringException: 338 | return setBaseType(Warning, "String operation error"); 339 | case AllocNULLEXception: 340 | return setBaseType(Error, "Memory request failed"); 341 | default: 342 | return setBaseType(Warning, "No such error type"); 343 | } 344 | } 345 | 346 | static LogTypeInfoBase setBaseType(LogType type, char* info) 347 | { 348 | LogTypeInfoBase base; 349 | base.baseType = type; 350 | base.info = info; 351 | return base; 352 | } 353 | 354 | //初始化日志模块 355 | void init_Log() 356 | { 357 | char logFileName[25] = { '\0' }; 358 | 359 | if (_access("log", 0) == -1)//如果文件夹不存在 360 | { 361 | int temp = _mkdir("log"); 362 | if (temp != temp)//对返回值进行处理,这个地方不处理可能过不了编译,但一般情况下都没问题,除非这个exe没权限,所以我就不写了 363 | { 364 | printf("不可能的运行我"); 365 | } 366 | } 367 | 368 | if (_access("temp", 0) == -1)//如果文件夹不存在 369 | { 370 | int temp = _mkdir("temp"); 371 | if (temp != temp)//对返回值进行处理,这个地方不处理可能过不了编译,但一般情况下都没问题,除非这个exe没权限,所以我就不写了 372 | { 373 | printf("不可能的运行我"); 374 | } 375 | } 376 | 377 | time(&now); 378 | localtime_s(&timeinfo, &now); 379 | sprintf_s(logFileName, 25, "log\\%d-%d-%d.log", 380 | timeinfo.tm_year + 1900, 381 | timeinfo.tm_mon, 382 | timeinfo.tm_mday); 383 | 384 | fopen_s(&logFile, logFileName, "a"); 385 | } 386 | 387 | void isLogInit() 388 | { 389 | if (!logFile)//是否初始化日志模块 390 | { 391 | color(DEFAULT_COLOR); 392 | puts("未初始化日志模块!"); 393 | exit(1); 394 | } 395 | } 396 | 397 | int color(int a) 398 | { 399 | SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), a); //更改文字颜色 400 | return 0; 401 | } -------------------------------------------------------------------------------- /gocqhttp_API.c: -------------------------------------------------------------------------------- 1 | #include"gocqhttp_API.h" 2 | #include"URLcode.h" 3 | #include"Log.h" 4 | #include"AnaJSON.h" 5 | #include"Tool.h" 6 | #include"AnaJSON.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #pragma comment(lib, "ws2_32.lib") 14 | 15 | #define MESSAGE_DEFAULT_SIZE 1024 16 | 17 | /* socket */ 18 | static SOCKADDR_IN server_addr;//服务端 19 | 20 | /*API连接*/ 21 | static SOCKET APIconection(); 22 | 23 | /*send_private_msg 发送私密消息*/ 24 | send_private_msg_data New_send_private_msg(unsigned int user_id, unsigned int group_id, char message[1024], int auto_escape) 25 | { 26 | char* temp = GBKtoUTF8(message); 27 | send_private_msg_data data; 28 | memset(&data, 0, sizeof(data)); 29 | data.send_msg.user_id = user_id; 30 | data.send_msg.group_id = group_id; 31 | strcpy_s(data.send_msg.message, MESSAGE_DEFAULT_SIZE, temp); 32 | data.send_msg.auto_escape = auto_escape; 33 | free(temp); 34 | return data; 35 | } 36 | 37 | void send_private_msg(send_private_msg_data* data) 38 | { 39 | SOCKET server; 40 | if ((server = APIconection()) == 0) //对接API 41 | { 42 | logErrAll("API连接错误"); 43 | return; 44 | } 45 | 46 | char rmsg[1024] = { '\0' }; //收包 47 | char smsg[1024] = { '\0' }; //发包 48 | char* temp; 49 | 50 | //构建URL 51 | temp = urlencode(data->send_msg.message); 52 | 53 | //构建发包(字符串) 54 | if ( 55 | sprintf_s(smsg, MESSAGE_DEFAULT_SIZE, 56 | API_SEND_PRIVATE_MSG_FORM, 57 | data->send_msg.user_id, 58 | data->send_msg.group_id, 59 | temp, 60 | data->send_msg.auto_escape) 61 | < 0) 62 | logErrAll("构建发包失败\n待发送消息:%s", data->send_msg.message); 63 | free(temp); 64 | int isend = send(server, smsg, strlen(smsg), 0); //发送 65 | if (isend < 0) 66 | { 67 | closesocket(server); 68 | logErrAll("发送 %s 失败", smsg); 69 | return; 70 | } 71 | //logInfo("发送 %s 成功", data->send_msg.message); 72 | 73 | memset(data, 0, sizeof(send_private_msg_data)); 74 | int count = 0; 75 | while (recv(server, rmsg, sizeof(rmsg), 0) < 0) //接收 76 | { 77 | count++; 78 | if (count > RECV_MAX) 79 | { 80 | closesocket(server); 81 | logWarnAll("发送 %s 后连接中断", smsg); 82 | return; 83 | } 84 | } 85 | JSON* recvJSON; 86 | char* jsonStr; 87 | jsonStr = removeHeaders(rmsg); 88 | recvJSON = StringToJSON(jsonStr); 89 | 90 | getIntValue(&data->recv_msg.retcode, recvJSON, "retconde"); 91 | getStringValue(data->recv_msg.status, recvJSON, "status"); 92 | 93 | free(jsonStr); 94 | FreeJSON(recvJSON); 95 | closesocket(server); 96 | } 97 | 98 | /*send_group_msg 发送群消息*/ 99 | send_group_msg_data New_send_group_msg(unsigned int group_id, char message[1024], int auto_escape) 100 | { 101 | char* temp = GBKtoUTF8(message); 102 | send_group_msg_data data; 103 | memset(&data, 0, sizeof(data)); 104 | data.send_msg.group_id = group_id; 105 | strcpy_s(data.send_msg.message, sizeof(data.send_msg.message), temp); 106 | data.send_msg.auto_escape = auto_escape; 107 | free(temp); 108 | return data; 109 | } 110 | 111 | void send_group_msg(send_group_msg_data* data) 112 | { 113 | SOCKET server; 114 | if ((server = APIconection()) == 0) //对接API 115 | { 116 | logErrAll("API连接错误"); 117 | return; 118 | } 119 | 120 | char rmsg[1024] = { '\0' }; //收包 121 | char smsg[1024] = { '\0' }; //发包 122 | char* temp; 123 | 124 | //构建URL 125 | temp = urlencode(data->send_msg.message); 126 | 127 | //构建发包 128 | if ( 129 | sprintf_s(smsg, MESSAGE_DEFAULT_SIZE, 130 | API_SEND_GROUP_MSG_FORM, 131 | data->send_msg.group_id, 132 | temp, 133 | data->send_msg.auto_escape) 134 | < 0) 135 | logErrAll("构建发包失败:%s", data->send_msg.message); 136 | free(temp); 137 | int isend = send(server, smsg, strlen(smsg), 0); //发送 138 | if (isend < 0) 139 | { 140 | closesocket(server); 141 | logErrAll("发送 %s 失败", smsg); 142 | return; 143 | } 144 | //logInfo("发送 %s 成功", data->send_msg.message); 145 | 146 | memset(data, 0, sizeof(send_group_msg_data)); 147 | int count = 0; //接收次数 148 | while (recv(server, rmsg, sizeof(rmsg), 0) < 0) //接收 149 | { 150 | count++; 151 | if (count > RECV_MAX) 152 | { 153 | closesocket(server); 154 | logWarnAll("发送 %s 后连接中断", smsg); 155 | return; 156 | } 157 | } 158 | JSON* recvJSON; 159 | char* jsonStr; 160 | jsonStr = removeHeaders(rmsg); 161 | recvJSON = StringToJSON(jsonStr); 162 | 163 | getIntValue(&data->recv_msg.retcode, recvJSON, "retconde"); 164 | getStringValue(data->recv_msg.status, recvJSON, "status"); 165 | 166 | free(jsonStr); 167 | FreeJSON(recvJSON); 168 | closesocket(server); 169 | } 170 | 171 | /*get_msg 获取消息*/ 172 | get_msg_data New_get_msg(int message_id) 173 | { 174 | get_msg_data data; 175 | memset(&data, 0, sizeof(data)); 176 | data.send_msg.message_id = message_id; 177 | return data; 178 | } 179 | 180 | void get_msg(get_msg_data* data) 181 | { 182 | SOCKET server; 183 | if ((server = APIconection()) == 0) //对接API 184 | { 185 | logErrAll("API连接错误"); 186 | return; 187 | } 188 | 189 | char rmsg[1024] = { '\0' }; //收包 190 | char smsg[1024] = { '\0' }; //发包 191 | char tempStr[1024] = { '\0' }; 192 | char* tempPtr; 193 | 194 | //构建发包 195 | if ( 196 | sprintf_s(smsg, MESSAGE_DEFAULT_SIZE, 197 | API_GET_MSG_FORM, 198 | data->send_msg.message_id) 199 | < 0) 200 | logErrAll("构建发包失败,消息ID:%d", data->send_msg.message_id); 201 | 202 | int isend = send(server, smsg, strlen(smsg), 0); //发送 203 | if (isend < 0) 204 | { 205 | closesocket(server); 206 | logErrAll("发送 %s 失败", smsg); 207 | return; 208 | } 209 | 210 | memset(data, 0, sizeof(get_msg_data)); 211 | while (recv(server, rmsg, sizeof(rmsg), 0) < 0); //接收 212 | 213 | JSON* recvJSON, subJSON, subJSON2; 214 | char* jsonStr; 215 | jsonStr = removeHeaders(rmsg); 216 | recvJSON = StringToJSON(jsonStr); 217 | 218 | getIntValue(&data->recv_msg.retcode, recvJSON, "retconde"); 219 | getStringValue(data->recv_msg.status, recvJSON, "status"); 220 | getJSONValue(&subJSON, recvJSON, "data"); 221 | getBoolValue(&data->recv_msg.data.group, &subJSON, "group"); 222 | getIntValue(&data->recv_msg.data.group_id, &subJSON, "group_id"); 223 | getIntValue(&data->recv_msg.data.message_id, &subJSON, "message_id"); 224 | getStringValue(tempStr, &subJSON, "message"); 225 | tempPtr = UTF8toGBK(tempStr); 226 | strcpy_s(data->recv_msg.data.message, 50, tempPtr); 227 | memset(tempStr, 0, sizeof(tempStr)); 228 | free(tempPtr); 229 | getStringValue(data->recv_msg.data.message_id_v2, &subJSON, "message_id_v2"); 230 | getIntValue(&data->recv_msg.data.message_seq, &subJSON, "message_seq"); 231 | getIntValue(&data->recv_msg.data.time, &subJSON, "time"); 232 | getIntValue(&data->recv_msg.data.real_id, &subJSON, "real_id"); 233 | getStringValue(data->recv_msg.data.message_type, &subJSON, "message_type"); 234 | getJSONValue(&subJSON2, &subJSON, "sender"); 235 | getStringValue(tempStr, &subJSON2, "nickname"); 236 | tempPtr = UTF8toGBK(tempStr); 237 | strcpy_s(data->recv_msg.data.message, 50, tempPtr); 238 | memset(tempStr, 0, sizeof(tempStr)); 239 | free(tempPtr); 240 | getIntValue(&data->recv_msg.data.sender.user_id, &subJSON2, "user_id"); 241 | 242 | FreeJSON(recvJSON); 243 | free(jsonStr); 244 | 245 | closesocket(server); 246 | logInfo("获取消息信息成功"); 247 | } 248 | 249 | /*delete_msg 撤回消息*/ 250 | delete_msg_data New_delete_msg(int message_id) 251 | { 252 | delete_msg_data data; 253 | memset(&data, 0, sizeof(data)); 254 | data.send_msg.message_id = message_id; 255 | return data; 256 | } 257 | 258 | void delete_msg(delete_msg_data* data) 259 | { 260 | SOCKET server; 261 | if ((server = APIconection()) == 0) //对接API 262 | { 263 | logErrAll("API连接错误"); 264 | return; 265 | } 266 | 267 | char rmsg[1024] = { '\0' }; //收包 268 | char smsg[1024] = { '\0' }; //发包 269 | 270 | //构建发包 271 | if ( 272 | sprintf_s(smsg, MESSAGE_DEFAULT_SIZE, 273 | API_DELETE_MSG_FORM, 274 | data->send_msg.message_id) 275 | < 0) 276 | logErrAll("构建发包失败,消息ID:%d", data->send_msg.message_id); 277 | 278 | int isend = send(server, smsg, strlen(smsg), 0); //发送 279 | if (isend < 0) 280 | { 281 | closesocket(server); 282 | logErrAll("发送 %s 失败", smsg); 283 | return; 284 | } 285 | 286 | memset(data, 0, sizeof(data)); 287 | while (recv(server, rmsg, sizeof(rmsg), 0) < 0); //接收 288 | scanf_s(rmsg, API_DELETE_MSG_RECV, 289 | data->recv_msg.data, 290 | &data->recv_msg.retcode, 291 | data->recv_msg.status); 292 | 293 | logInfoAll("%s", rmsg); 294 | JSON* recvJSON; 295 | char* jsonStr; 296 | jsonStr = removeHeaders(rmsg); 297 | recvJSON = StringToJSON(jsonStr); 298 | 299 | getIntValue(&data->recv_msg.retcode, recvJSON, "retconde"); 300 | getStringValue(data->recv_msg.status, recvJSON, "status"); 301 | 302 | free(jsonStr); 303 | FreeJSON(recvJSON); 304 | closesocket(server); 305 | logInfo("已成功撤回消息"); 306 | } 307 | 308 | /*get_group_member_info 获取群成员信息*/ 309 | get_group_member_info_data New_get_group_member_info(unsigned long group_id, unsigned long user_id, int no_cache) 310 | { 311 | get_group_member_info_data data; 312 | memset(&data, 0, sizeof(data)); 313 | data.send_msg.group_id = group_id; 314 | data.send_msg.user_id = user_id; 315 | data.send_msg.no_cache = no_cache; 316 | return data; 317 | } 318 | 319 | void get_group_member_info(get_group_member_info_data* data) 320 | { 321 | SOCKET server; 322 | if ((server = APIconection()) == 0) //对接API 323 | { 324 | logErrAll("API连接错误"); 325 | return; 326 | } 327 | 328 | char rmsg[1024] = { '\0' }; //收包 329 | char smsg[1024] = { '\0' }; //发包 330 | char tempStr[1024] = { '\0' }; 331 | char* tempPtr; 332 | 333 | //构建发包 334 | if ( 335 | sprintf_s(smsg, MESSAGE_DEFAULT_SIZE, 336 | API_GET_GROUP_MEMBER_INFO_FORM, 337 | data->send_msg.group_id, 338 | data->send_msg.user_id, 339 | data->send_msg.no_cache) 340 | < 0) 341 | logErrAll("构建发包失败,群ID:%d", data->send_msg.group_id); 342 | 343 | int isend = send(server, smsg, strlen(smsg), 0); //发送 344 | if (isend < 0) 345 | { 346 | closesocket(server); 347 | logErrAll("发送 %s 失败", smsg); 348 | return; 349 | } 350 | 351 | memset(data, 0, sizeof(get_group_member_info_data)); 352 | while (recv(server, rmsg, sizeof(rmsg), 0) < 0); //接收 353 | logInfoAll("%s", rmsg); 354 | JSON* recvJSON, subJSON; 355 | char* jsonStr; 356 | jsonStr = removeHeaders(rmsg); 357 | recvJSON = StringToJSON(jsonStr); 358 | 359 | getIntValue(&data->recv_msg.retcode, recvJSON, "retconde"); 360 | getStringValue(data->recv_msg.status, recvJSON, "status"); 361 | getJSONValue(&subJSON, recvJSON, "data"); 362 | getIntValue(&data->recv_msg.data.age, &subJSON, "age"); 363 | getIntValue(&data->recv_msg.data.group_id, &subJSON, "group_id"); 364 | getIntValue(&data->recv_msg.data.user_id, &subJSON, "user_id"); 365 | getStringValue(data->recv_msg.data.area, &subJSON, "area"); 366 | getIntValue(&data->recv_msg.data.join_time, &subJSON, "json_time"); 367 | getStringValue(data->recv_msg.data.card, &subJSON, "card"); 368 | getBoolValue(&data->recv_msg.data.card_changeable, &subJSON, "card_changeable"); 369 | getIntValue(&data->recv_msg.data.last_sent_time, &subJSON, "last_sent_time"); 370 | getStringValue(data->recv_msg.data.level, &subJSON, "level"); 371 | getStringValue(tempStr, &subJSON, "nickname"); 372 | tempPtr = UTF8toGBK(tempStr); 373 | strcpy_s(data->recv_msg.data.nickname, 50, tempPtr); 374 | memset(tempStr, 0, sizeof(tempStr)); 375 | free(tempPtr); 376 | getStringValue(data->recv_msg.data.role, &subJSON, "role"); 377 | getStringValue(data->recv_msg.data.sex, &subJSON, "sex"); 378 | getIntValue(&data->recv_msg.data.shut_up_timestamp, &subJSON, "shut_up_timestamp"); 379 | getStringValue(data->recv_msg.data.title, &subJSON, "title"); 380 | getIntValue(&data->recv_msg.data.title_expire_time, &subJSON, "title_expire_time"); 381 | getBoolValue(&data->recv_msg.data.unfriendly, &subJSON, "unfriendly"); 382 | 383 | free(jsonStr); 384 | FreeJSON(recvJSON); 385 | closesocket(server); 386 | logInfo("成功获取群成员信息"); 387 | } 388 | 389 | /*初始化*/ 390 | void init_gocqhttpAPI(const char* ip, const int port) 391 | { 392 | memset((void*)&server_addr, 0, sizeof(SOCKADDR_IN)); 393 | 394 | server_addr.sin_family = AF_INET; 395 | server_addr.sin_port = htons(port); 396 | inet_pton(AF_INET, ip, (void*)&server_addr.sin_addr); 397 | 398 | WSADATA wsaData; 399 | int e = WSAStartup(MAKEWORD(2, 2), &wsaData); 400 | if (e) 401 | { 402 | logErr("初始化动态库失败"); 403 | exit(1); 404 | } 405 | 406 | logInfo("API模块初始化完成"); 407 | } 408 | 409 | ///*退出*/ 410 | //VOID EXIT_GOCQHTTPAPI(VOID) 411 | //{ 412 | // CLOSESOCKET(SERVER); 413 | // MEMSET((VOID*)&SERVER_ADDR, 0, SIZEOF(SERVER_ADDR)); 414 | //} 415 | 416 | //API连接 417 | static SOCKET APIconection() 418 | { 419 | SOCKET server; 420 | if ((server = socket(AF_INET, SOCK_STREAM, 0)) < 0) 421 | { 422 | logPresetAll(SocketInitError); 423 | return 0; 424 | } 425 | if (connect(server, (SOCKADDR*)&server_addr, sizeof(server_addr)) < 0) 426 | { 427 | logPresetAll(ConnectionError); 428 | return 0; 429 | } 430 | return server; 431 | } 432 | -------------------------------------------------------------------------------- /AnaJSON.c: -------------------------------------------------------------------------------- 1 | /** 2 | * 此JSON解析库暂不支持 浮点类型、数组型、布尔型解析 3 | */ 4 | 5 | #include"AnaJSON.h" 6 | #include"Tool.h" 7 | #include"Log.h" 8 | #include 9 | #include 10 | 11 | /*计算到下个符号的长度*/ 12 | static int getLengthOfNextChar(const char* str, int startIndex, char ch); 13 | 14 | /*从指定位置复制字符串*/ 15 | static char* interceptString(const char* str, int startIndex, int size); 16 | 17 | /*解析JSON*/ 18 | static int AnalysisJSON(JSON** node, int index, char* JSONString); 19 | 20 | /*获取字符串副本*/ 21 | static char* GetStringDuplicate(const char* string); 22 | 23 | static char* getNULLString(); 24 | 25 | static int isNormalJSONStart(const char* string, int index); 26 | 27 | static int AnalysisJSONString(JSON** node, int index, char* JSONString); 28 | static int AnalysisJSONInt(JSON** node, int index, char* JSONString); 29 | static int AnalysisJSONBool(JSON** node, int index, char* JSONString); 30 | //static int AnalysisJSONArray(JSON** node, int index, char* JSONString); 31 | 32 | //解析字符串到JSON 33 | JSON* StringToJSON(const char* string) 34 | { 35 | JSON* json; 36 | 37 | if (!isNormalJSONStart(string, 0)) 38 | { 39 | logWarnAll("不正常的JSON数据开头\nstring: %s", string); 40 | return NULL; 41 | } 42 | 43 | AnalysisJSON(&json, 0, string); 44 | 45 | return json; 46 | } 47 | 48 | //解析JSON 49 | static int AnalysisJSON(JSON** node, int index, char* JSONString) 50 | { 51 | int length, preLength; 52 | char* name; 53 | 54 | preLength = index; 55 | index++;//自增操作是因为此时索引的字符是 " 或者 {,直接带入下一行的函数会返回0 56 | if (JSONString[index] == '\"')//如果此时为 " 则此 " 为字符串开头 " 直接跳过 57 | index++; 58 | 59 | length = getLengthOfNextChar(JSONString, index, '\"');//获取到对象名结束的双引号之前的字符的长度(不包括双引号) 60 | name = interceptString(JSONString, index, length); 61 | 62 | index = index + length + 2;//当前索引 + 对象名长度 + 对象名后 ": 两个字符的长度 63 | switch (JSONString[index])//检索冒号后的第一个字符(对象的值),以判断数据类型 64 | { 65 | case '\"'://字符串类型 66 | *node = CreateJSON(name, StringType); 67 | index += AnalysisJSONString(node, index, JSONString); 68 | break; 69 | case '1'://整数类型 70 | case '2': 71 | case '3': 72 | case '4': 73 | case '5': 74 | case '6': 75 | case '7': 76 | case '8': 77 | case '9': 78 | case '0': 79 | case'-': 80 | *node = CreateJSON(name, IntType); 81 | index += AnalysisJSONInt(node, index, JSONString); 82 | break; 83 | case 't'://布尔类型 84 | case 'f': 85 | *node = CreateJSON(name, BoolType); 86 | index += AnalysisJSONBool(node, index, JSONString); 87 | break; 88 | case '['://数组类型 89 | //AnalysisJSONArray(node, recursionData->string, &CurrentIndex); 90 | break; 91 | case '{'://对象类型 92 | *node = CreateJSON(name, ObjectType); 93 | index += AnalysisJSON(&(*node)->data.jsonData, index, JSONString); 94 | break; 95 | case 'n'://NULL空值 96 | *node = CreateJSON(name, StringType); 97 | (*node)->data.stringData = getNULLString(); 98 | index += 4; 99 | break; 100 | } 101 | 102 | //此时索引位置上的字符应该为 , 或者 } 103 | if (JSONString[index] == ',') 104 | { 105 | index++; 106 | index += AnalysisJSON(&(*node)->next, index, JSONString); 107 | } 108 | else if (JSONString[index] == '}' && JSONString[index + 1] != '\0') 109 | { 110 | index += 2; 111 | index += AnalysisJSON(&(*node)->next, index, JSONString); 112 | } 113 | 114 | return index - preLength;//返回索引增加的值 115 | } 116 | 117 | static int isNormalJSONStart(const char* string, int index) 118 | { 119 | return string[0] == '{'; 120 | } 121 | 122 | static int AnalysisJSONString(JSON** node, int index, char* JSONString) 123 | { 124 | char* value; 125 | int length = getLengthOfNextChar(JSONString, index + 1, '\"');//+1操作是因为此时索引的字符是",直接带入此函数会返回0 126 | value = interceptString(JSONString, index + 1, length); 127 | 128 | (*node)->data.stringData = value; 129 | 130 | return 1 + length + 1;//双引号长度 + 字符串长度 + 双引号长度 131 | } 132 | 133 | static int AnalysisJSONInt(JSON** node, int index, char* JSONString) 134 | { 135 | int length, length1, length2; 136 | 137 | length1 = getLengthOfNextChar(JSONString, index, ','); 138 | length2 = getLengthOfNextChar(JSONString, index, '}'); 139 | 140 | //判断当前对象结尾(可能为 } 或 ,) 141 | if (length1 == -1) 142 | length = length2; 143 | else if (length2 == -1) 144 | length = length1; 145 | else//对比到两个结尾符的距离,取最小的那个 146 | length = length1 > length2 ? length2 : length1; 147 | 148 | char* temp = interceptString(JSONString, index, length); 149 | //long Int = atol(temp); 150 | int Int = StringToInt(temp); 151 | (*node)->data.intData = Int; 152 | 153 | free(temp); 154 | return length; 155 | } 156 | 157 | static int AnalysisJSONBool(JSON** node, int index, char* JSONString) 158 | { 159 | char* value; 160 | int length = getLengthOfNextChar(JSONString, index, ','); 161 | value = interceptString(JSONString, index, length); 162 | 163 | if (!strcmp(value, "true")) 164 | { 165 | (*node)->data.boolData = 1; 166 | } 167 | else if (!strcmp(value, "false")) 168 | { 169 | (*node)->data.boolData = 0; 170 | } 171 | else 172 | { 173 | (*node)->data.boolData = -1; 174 | } 175 | 176 | free(value); 177 | return length; 178 | } 179 | 180 | //static int AnalysisJSONArray(JSON** node, int index, char* JSONString) 181 | //{ 182 | // //JSONArray* ArrayNode; 183 | // 184 | // //node->value.type = ArrayType; 185 | // //node->value.data.Array = (JSONArray*)malloc_s(sizeof(JSONArray)); 186 | // //ArrayNode = node->value.data.Array; 187 | // //ArrayNode->nodeCount = 0; 188 | // //ArrayNode->head = NULL; 189 | // 190 | // //recursionData->index++; 191 | // //switch (recursionData->string[recursionData->index])//检索冒号后的第一个字符(对象的值),以判断数据类型 192 | // //{//TODO 把这几个解析函数重新写一遍适配数组的 193 | // //case '\"'://字符串类型 194 | // // ArrayNode->head = (JSONArrayNode*)malloc_s(sizeof(JSONArrayNode)); 195 | // 196 | // // AnalysisJSONStringArray(node, recursionData->string, &CurrentIndex); 197 | // // break; 198 | // //case '1'://整数类型 199 | // //case '2': 200 | // //case '3': 201 | // //case '4': 202 | // //case '5': 203 | // //case '6': 204 | // //case '7': 205 | // //case '8': 206 | // //case '9': 207 | // //case '0': 208 | // // AnalysisJSONIntArray(node, recursionData->string, &CurrentIndex); 209 | // // break; 210 | // //case '['://数组类型 211 | // // //AnalysisJSONArray(node, recursionData->string, &CurrentIndex); 212 | // // break; 213 | // //case 't'://布尔类型 214 | // //case 'f': 215 | // // //AnalysisJSONBool(node, recursionData->string, &CurrentIndex); 216 | // //case '{'://对象类型 217 | // // break; 218 | // //} 219 | // 220 | // 221 | //} 222 | 223 | //计算到下个符号的长度(不会将用于转义的斜杠计入总长度) 224 | static int getLengthOfNextChar(const char* str, int startIndex, char ch) 225 | { 226 | int len = 0; 227 | while(1) 228 | { 229 | if(str[startIndex] == '\\') //如果遇到转义符 230 | { 231 | startIndex += 2; 232 | len++; 233 | } 234 | else if(str[startIndex++] == ch) //遇到匹配字符 235 | break; 236 | else //一般情况 237 | len++; 238 | if (str[len] == '\0') //如果到字符串末尾任未检测到字符 239 | return -1; 240 | } 241 | return len; 242 | } 243 | 244 | //从指定位置截取字符串(会将转义符作为一个字符截取,且只按1的大小计算,例如 \n 会被看做 n,长度为1) 245 | static char* interceptString(const char* str, int startIndex, int size) 246 | { 247 | int index, offset; 248 | char* returnString = (char*)malloc_s(size + 1);//+1为为\0字符预留位置 249 | 250 | for (index = 0, offset = 0; index < size; index++, offset++) 251 | { 252 | if (str[startIndex + offset] == '\0')//防止数组越界 253 | { 254 | logWarnAll("字符串字符串已到末尾\nstring: %s\nindex: %d\nsize: %d", str, startIndex, size); 255 | free(returnString); 256 | return getNULLString(); 257 | } 258 | else if (str[startIndex + offset] == '\\')//如果遇到转义符 259 | { 260 | returnString[index] = str[startIndex + ++offset];//直接跳过当前转义符,并用下一个字符赋值 261 | } 262 | else 263 | { 264 | returnString[index] = str[startIndex + offset]; 265 | } 266 | } 267 | returnString[index] = '\0'; 268 | 269 | return returnString; 270 | } 271 | 272 | char* GetStringDuplicate(const char* string) 273 | { 274 | char* duplicate; 275 | int len = strlen(string); 276 | duplicate = (char*)malloc_s(len + 1); 277 | strcpy_s(duplicate, len + 1, string); 278 | 279 | return duplicate; 280 | } 281 | 282 | static char* getNULLString() 283 | { 284 | char* str = (char*)malloc_s(5); 285 | strcpy_s(str, 5, "NULL"); 286 | return str; 287 | } 288 | 289 | //释放JSON数据 290 | void DestoryJSON(JSON* data) 291 | { 292 | FreeJSON(data); 293 | } 294 | 295 | JSON* CreateJSON(char* name, JSONType type) 296 | { 297 | JSON* json; 298 | json = (JSON*)malloc_s(sizeof(JSON)); 299 | json->next = NULL; 300 | json->pre = NULL; 301 | json->type = type; 302 | json->name = GetStringDuplicate(name); 303 | return json; 304 | } 305 | 306 | int AddJSON(JSON* json, char* name, JSONType type) 307 | { 308 | JSON* con = json; 309 | JSON* newObject; 310 | 311 | newObject = CreateJSON(name, type); 312 | 313 | if (!con) 314 | { 315 | free(newObject); 316 | return -1; 317 | } 318 | 319 | while (con->next != NULL) 320 | { 321 | con = con->next; 322 | } 323 | con->next = newObject; 324 | newObject->pre = con; 325 | 326 | return 0; 327 | } 328 | 329 | int AddIntToArrayJSON(JSON* json, int intData) 330 | { 331 | JSON* newData; 332 | if (!json || json->type != ArrayType) 333 | return -1; 334 | 335 | newData = CreateJSON(getNULLString(), IntType); 336 | newData->data.intData = intData; 337 | 338 | JSON* con = json->data.jsonData; 339 | if (!con) 340 | { 341 | json->data.jsonData = newData; 342 | return 0; 343 | } 344 | 345 | while (!con->next) 346 | { 347 | con = con->next; 348 | } 349 | con->next = newData; 350 | newData->pre = con; 351 | 352 | return 0; 353 | } 354 | 355 | int AddStringToArrayJSON(JSON* json, const char* string) 356 | { 357 | JSON* newData; 358 | if (!json || json->type != ArrayType) 359 | return -1; 360 | 361 | newData = CreateJSON(getNULLString(), StringType); 362 | newData->data.stringData = GetStringDuplicate(string); 363 | 364 | JSON* con = json->data.jsonData; 365 | if (!con) 366 | { 367 | json->data.jsonData = newData; 368 | return 0; 369 | } 370 | 371 | while (!con->next) 372 | { 373 | con = con->next; 374 | } 375 | con->next = newData; 376 | newData->pre = con; 377 | 378 | return 0; 379 | } 380 | 381 | int AddObjectToArrayJSON(JSON* json, JSON* jsonData) 382 | { 383 | JSON* newData; 384 | if (!json || json->type != ArrayType) 385 | return -1; 386 | 387 | newData = CreateJSON(getNULLString(), ObjectType); 388 | newData->data.jsonData = jsonData; 389 | 390 | JSON* con = json->data.jsonData; 391 | if (!con) 392 | { 393 | json->data.jsonData = newData; 394 | return 0; 395 | } 396 | 397 | while (!con->next) 398 | { 399 | con = con->next; 400 | } 401 | con->next = newData; 402 | newData->pre = con; 403 | 404 | return 0; 405 | } 406 | 407 | int SetIntJSON(JSON* json, char* name, int intData) 408 | { 409 | JSON* node; 410 | node = FindJSON(json, name); 411 | if (!node || node->type !=IntType) 412 | return -1; 413 | 414 | node->data.intData = intData; 415 | return 0; 416 | } 417 | 418 | int SetStringJSON(JSON* json, char* name, char* string) 419 | { 420 | JSON* node; 421 | node = FindJSON(json, name); 422 | if (!node || node->type != StringType) 423 | return -1; 424 | 425 | free(node->data.stringData); 426 | node->data.stringData = string; 427 | return 0; 428 | } 429 | 430 | int SetObjectJSON(JSON* json, char* name, JSON* jsonData) 431 | { 432 | JSON* node; 433 | node = FindJSON(json, name); 434 | if (!node || node->type != ObjectType) 435 | return -1; 436 | 437 | FreeJSON(node->data.jsonData); 438 | node->data.jsonData = jsonData; 439 | return 0; 440 | } 441 | 442 | JSON* FindJSON(JSON* json, const char* name) 443 | { 444 | JSON* con = json; 445 | while (!con) 446 | { 447 | if (!strcmp(con->name, name)) 448 | { 449 | return con; 450 | } 451 | con = con->next; 452 | } 453 | return NULL; 454 | } 455 | 456 | void FreeJSON(JSON* data) 457 | { 458 | JSON* tmp; 459 | if (!data) 460 | return; 461 | 462 | while (data->next != NULL) 463 | { 464 | free(data->name); 465 | //释放数据 466 | switch (data->type) 467 | { 468 | case IntType: 469 | //无需操作 470 | break; 471 | case StringType: 472 | free(data->data.stringData); 473 | break; 474 | case ObjectType: 475 | FreeJSON(data->data.jsonData); 476 | break; 477 | } 478 | tmp = data; 479 | data = data->next; 480 | free(tmp); 481 | } 482 | } 483 | 484 | /*获取JSON的值*/ 485 | //JSON对象 486 | int getJSONValue(JSON* value, JSON* root, const char* name) 487 | { 488 | JSON* con = root; 489 | 490 | while (con) 491 | { 492 | if (!strcmp(name, con->name)) 493 | { 494 | if (con->type != ObjectType) 495 | return - 1; 496 | value->name = con->data.jsonData->name; 497 | value->next = con->data.jsonData->next; 498 | value->pre = con->data.jsonData->pre; 499 | value->type = con->data.jsonData->type; 500 | value->data = con->data.jsonData->data; 501 | return 0; 502 | } 503 | con = con->next; 504 | } 505 | return -1; 506 | } 507 | 508 | //int整数 509 | int getIntValue(int* value, JSON* root, const char* name) 510 | { 511 | JSON* con = root; 512 | 513 | while (con) 514 | { 515 | if (!strcmp(name, con->name)) 516 | { 517 | if (con->type != IntType && con->type != BoolType) 518 | return -1; 519 | *value = con->data.intData; 520 | return 0; 521 | } 522 | con = con->next; 523 | } 524 | return -1; 525 | } 526 | 527 | //String字符串 528 | int getStringValue(char* value, JSON* root, const char* name) 529 | { 530 | JSON* con = root; 531 | 532 | while (con) 533 | { 534 | if (!strcmp(name, con->name)) 535 | { 536 | if (con->type != StringType) 537 | return -1; 538 | strcpy_s(value, strlen(con->data.stringData) + 1, con->data.stringData); 539 | return 0; 540 | } 541 | con = con->next; 542 | } 543 | return -1; 544 | } 545 | 546 | //Bool布尔 547 | int getBoolValue(int* value, JSON* root, const char* name) 548 | { 549 | return getIntValue(value, root, name); 550 | } 551 | 552 | int StringToInt(char* string) 553 | { 554 | int num = 0; 555 | unsigned int unum = 0; 556 | int temp; 557 | int temp2; 558 | int i; 559 | int index; 560 | int len; 561 | if (!string) 562 | return 0; 563 | len = strlen(string); 564 | 565 | if (string[0] == '-') 566 | { 567 | for (index = 1; index < len; index++) 568 | { 569 | temp2 = 1; 570 | switch (string[index]) 571 | { 572 | case '1': 573 | temp = 1; 574 | break; 575 | case '2': 576 | temp = 2; 577 | break; 578 | case '3': 579 | temp = 3; 580 | break; 581 | case '4': 582 | temp = 4; 583 | break; 584 | case '5': 585 | temp = 5; 586 | break; 587 | case '6': 588 | temp = 6; 589 | break; 590 | case '7': 591 | temp = 7; 592 | break; 593 | case '8': 594 | temp = 8; 595 | break; 596 | case '9': 597 | temp = 9; 598 | break; 599 | case '0': 600 | temp = 0; 601 | break; 602 | 603 | } 604 | for (i = 0; i < (len - index - 2); i++) 605 | temp2 *= 10; 606 | num += temp * temp2; 607 | } 608 | num *= -1; 609 | return num; 610 | } 611 | else 612 | { 613 | for (index = 0; index < len; index++) 614 | { 615 | temp2 = 1; 616 | switch (string[index]) 617 | { 618 | case '1': 619 | temp = 1; 620 | break; 621 | case '2': 622 | temp = 2; 623 | break; 624 | case '3': 625 | temp = 3; 626 | break; 627 | case '4': 628 | temp = 4; 629 | break; 630 | case '5': 631 | temp = 5; 632 | break; 633 | case '6': 634 | temp = 6; 635 | break; 636 | case '7': 637 | temp = 7; 638 | break; 639 | case '8': 640 | temp = 8; 641 | break; 642 | case '9': 643 | temp = 9; 644 | break; 645 | case '0': 646 | temp = 0; 647 | break; 648 | 649 | } 650 | for (i = 0; i < (len - index - 1); i++) 651 | temp2 *= 10; 652 | unum += temp * temp2; 653 | } 654 | return unum; 655 | } 656 | } 657 | -------------------------------------------------------------------------------- /gocqhttp_Event.c: -------------------------------------------------------------------------------- 1 | #include "gocqhttp_Event.h" 2 | #include "Log.h" 3 | #include"Tool.h" 4 | #include"AnaJSON.h" 5 | #include"URLcode.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #pragma comment(lib, "ws2_32.lib") 15 | 16 | /*socket*/ 17 | //socket返回的文件描述符 18 | SOCKET serverSocket; //服务端 19 | SOCKET client; //客户端 20 | 21 | //socket数据 22 | SOCKADDR_IN server_addr;//服务端 23 | SOCKADDR_IN client_addr;//客户端 24 | 25 | 26 | //结构大小 27 | int addr_len = sizeof(SOCKADDR_IN); 28 | 29 | void (*event_response)(void* data); //事件类型响应函数 30 | 31 | /*创建Event服务端*/ 32 | void init_gocqhttpEvent(const char* ip, const int port, void(*response)(void* data), void(*init)(void)) 33 | { 34 | if (!response || !init) 35 | { 36 | logErr("初始化Event模块失败!函数指针 response 为空"); 37 | exit(1); 38 | } 39 | 40 | init(); 41 | 42 | //初始化 43 | memset(&server_addr, 0, sizeof(SOCKADDR_IN)); 44 | 45 | server_addr.sin_family = AF_INET; 46 | server_addr.sin_port = htons(port); 47 | inet_pton(AF_INET, ip, (void*)&server_addr.sin_addr); 48 | 49 | event_response = response;//设置事件判断函数 50 | 51 | //动态库初始化 52 | WSADATA wsaData; 53 | int e = WSAStartup(MAKEWORD(2, 2), &wsaData); 54 | if (e) 55 | { 56 | logPresetAll(WSAStartupError); 57 | exit(1); 58 | } 59 | 60 | //创建套接字 61 | if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) 62 | { 63 | logPresetAll(SocketInitError); 64 | exit(1); 65 | } 66 | 67 | //绑定端口 68 | if (bind(serverSocket, (SOCKADDR*)&server_addr, sizeof(server_addr)) < 0) 69 | { 70 | logPresetAll(BindError); 71 | exit(1); 72 | } 73 | 74 | //监听端口 75 | if (listen(serverSocket, 100) < 0) 76 | { 77 | logPresetAll(ListenError); 78 | exit(1); 79 | } 80 | 81 | //开始接受连接 82 | logInfo("等待连接..."); 83 | if ((client = accept(serverSocket, (SOCKADDR*)&client_addr, (socklen_t*)&addr_len)) < 0) 84 | { 85 | logWarn("接受连接失败,五秒后重新接受连接"); 86 | Sleep(5000); 87 | } 88 | 89 | logInfo("已接受连接!"); 90 | } 91 | 92 | /*接收Event数据*/ 93 | int recv_event(void) 94 | { 95 | int recv_max = 1000, count; 96 | char* msg; 97 | if (!event_response) //如果未声明消息响应函数 98 | { 99 | logErr("event_response 是空指针"); 100 | exit(1); 101 | } 102 | 103 | while (1) 104 | { 105 | count = 0; 106 | msg = (char*)malloc_s(2048); 107 | if (!msg) 108 | logPresetAll(NULLException); 109 | memset(msg, 0, 2048); 110 | 111 | while (recv(client, msg, 2048, 0) < 0) 112 | { 113 | count++; 114 | if (count > recv_max) //如果连接中断 115 | { 116 | closesocket(client); 117 | closesocket(serverSocket); 118 | return -1; 119 | } 120 | } 121 | if (send(client, Event_Response, strlen(Event_Response), 0) < 0) 122 | { 123 | return -2;//发送http响应头失败 124 | } 125 | _beginthread(event_response, 0, (void*)msg); //开始检索事件 126 | } 127 | } 128 | 129 | 130 | /*事件解析*/ 131 | //消息事件 132 | MessageEventInfo message_event_analysis(JSON* eventData) 133 | { 134 | MessageEventInfo info; 135 | JSON subJSON; 136 | char tempStr[1024] = { '\0' }; 137 | char* tempPtr; 138 | 139 | //message 140 | getStringValue(tempStr, eventData, "message"); 141 | tempPtr = UTF8toGBK(tempStr); 142 | strcpy_s(info.message, MESSAGE_DEFAULT_SIZE, tempPtr); 143 | free(tempPtr); 144 | 145 | //self_id 146 | if (getIntValue(&info.self_id, eventData, "self_id") == -1) 147 | { 148 | info.self_id = 0; 149 | } 150 | 151 | //group_id 152 | if (getIntValue(&info.group_id, eventData, "group_id") == -1) 153 | { 154 | info.group_id = 0; 155 | info.isPrivate = 1; 156 | } 157 | else 158 | { 159 | info.isPrivate = 0; 160 | } 161 | 162 | //user_id 163 | int i = getIntValue(&info.user_id, eventData, "user_id"); 164 | 165 | //nickname 166 | memset(tempStr, '\0', 1024); 167 | getStringValue(tempStr, eventData, "nickname"); 168 | tempPtr = UTF8toGBK(tempStr); 169 | strcpy_s(info.nickname, 50, tempPtr); 170 | if(!tempPtr) 171 | free(tempPtr); 172 | 173 | //message_id 174 | getIntValue(&info.message_id, eventData, "message_id"); 175 | 176 | //time 177 | getIntValue(&info.time, eventData, "time"); 178 | 179 | return info; 180 | } 181 | //请求事件 182 | RequestEventInfo request_event_analysis(JSON* eventData) 183 | { 184 | RequestEventInfo info; 185 | char tempStr[1024] = {'\0'}; 186 | char* tempPtr; 187 | char type[20] = { '\0' }; 188 | 189 | //group_id 190 | if (getIntValue(&info.group_id, eventData, "group_id") == -1) 191 | { 192 | info.isFriend = 1; 193 | info.group_id = 0; 194 | } 195 | else 196 | { 197 | info.isFriend = 0; 198 | } 199 | 200 | //self_id 201 | if (getIntValue(&info.self_id, eventData, "self_id") == -1) 202 | { 203 | info.self_id = 0; 204 | } 205 | 206 | //user_id 207 | getIntValue(&info.user_id, eventData, "user_id"); 208 | 209 | //time 210 | getIntValue(&info.time, eventData, "time"); 211 | 212 | //comment 213 | getStringValue(tempStr, eventData, "comment"); 214 | tempPtr = UTF8toGBK(tempStr); 215 | strcpy_s(info.comment, MESSAGE_DEFAULT_SIZE, tempPtr); 216 | free(tempPtr); 217 | 218 | //type 219 | getStringValue(type, eventData, "request_type"); 220 | if (!strcmp(type, "friend")) 221 | { 222 | info.type = friend; 223 | } 224 | else if (!strcmp(type, "group")) 225 | { 226 | info.type = group; 227 | } 228 | 229 | return info; 230 | } 231 | //上报事件类型检索 232 | static NoticeType notice_event_type_switch(JSON* json); 233 | //上报事件 234 | NoticeEventInfo notice_event_analysis(JSON* eventData) 235 | { 236 | NoticeEventInfo info; 237 | char tempStr[1024] = { '\0' }; 238 | char* tempPtr; 239 | char subType[20] = { '\0' }; 240 | 241 | //group_id 242 | if (getIntValue(&info.group_id, eventData, "group_id") == -1) 243 | { 244 | info.isPrivate = 1; 245 | info.group_id = 0; 246 | } 247 | else 248 | { 249 | info.isPrivate = 0; 250 | } 251 | 252 | //self_id 253 | if (getIntValue(&info.self_id, eventData, "self_id") == -1) 254 | { 255 | info.self_id = 0; 256 | } 257 | 258 | //user_id 259 | getIntValue(&info.user_id, eventData, "user_id"); 260 | 261 | //nickname 262 | getStringValue(tempStr, eventData, "nickname"); 263 | tempPtr = UTF8toGBK(tempStr); 264 | strcpy_s(info.nickname, 50, tempPtr); 265 | free(tempPtr); 266 | 267 | //operator_id 268 | getIntValue(&info.operator_id, eventData, "operator_id"); 269 | 270 | //target_id 271 | getIntValue(&info.target_id, eventData, "target_id"); 272 | 273 | //time 274 | getIntValue(&info.time, eventData, "time"); 275 | 276 | //message_id 277 | if (getIntValue(&info.message_id, eventData, "message_id") == -1) 278 | { 279 | info.message_id = 0; 280 | } 281 | 282 | //type,subType 283 | getStringValue(subType, eventData, "sub_type"); 284 | switch (notice_event_type_switch(eventData)) 285 | { 286 | case friend_recall: 287 | info.type = friend_recall; 288 | break; 289 | case group_recall: 290 | info.type = group_recall; 291 | break; 292 | case group_increase: 293 | info.type = group_increase; 294 | if (!strcmp(subType, "approve")) 295 | info.subType.group_increase = approve; 296 | else if (!strcmp(subType, "invite")) 297 | info.subType.group_increase = invite; 298 | break; 299 | case group_decrease: 300 | info.type = group_decrease; 301 | if (!strcmp(subType, "leave")) 302 | info.subType.group_decrease = leave; 303 | else if (!strcmp(subType, "kick")) 304 | info.subType.group_decrease = kick; 305 | else if (!strcmp(subType, "kick_me")) 306 | info.subType.group_decrease = kick_me; 307 | break; 308 | case group_admin: 309 | info.type = group_admin; 310 | if (!strcmp(subType, "set")) 311 | info.subType.group_admin = set; 312 | else if (!strcmp(subType, "unset")) 313 | info.subType.group_admin = unset; 314 | break; 315 | case group_upload: 316 | info.type = group_upload; 317 | break; 318 | case group_ban: 319 | info.type = group_ban; 320 | if (!strcmp(subType, "ban")) 321 | info.subType.group_ban = ban; 322 | else if (!strcmp(subType, "lift_ban")) 323 | info.subType.group_ban = lift_ban; 324 | break; 325 | case friend_add: 326 | info.type = friend_add; 327 | break; 328 | case notify: 329 | info.type = notify; 330 | if (!strcmp(subType, "poke")) 331 | info.subType.notify = poke; 332 | else if (!strcmp(subType, "honor")) 333 | info.subType.notify = honor; 334 | else if (!strcmp(subType, "title")) 335 | info.subType.notify = title; 336 | break; 337 | case group_card: 338 | info.type = group_card; 339 | break; 340 | case offline_file: 341 | info.type = offline_file; 342 | break; 343 | case client_status: 344 | info.type = client_status; 345 | break; 346 | case essence: 347 | info.type = essence; 348 | if (!strcmp(subType, "add")) 349 | info.subType.essence = add; 350 | else if (!strcmp(subType, "delete")) 351 | info.subType.essence = delete; 352 | break; 353 | } 354 | 355 | return info; 356 | } 357 | 358 | static NoticeType notice_event_type_switch(JSON* json) 359 | { 360 | char type[20] = { '\0' }; 361 | 362 | getStringValue(type, json, "notice_type"); 363 | 364 | if (!strcmp(type, "friend_recall")) 365 | return friend_recall; 366 | else if (!strcmp(type, "group_recall")) 367 | return group_recall; 368 | else if (!strcmp(type, "group_increase")) 369 | return group_increase; 370 | else if (!strcmp(type, "group_decrease")) 371 | return group_decrease; 372 | else if (!strcmp(type, "group_admin")) 373 | return group_admin; 374 | else if (!strcmp(type, "group_upload")) 375 | return group_upload; 376 | else if (!strcmp(type, "group_ban")) 377 | return group_ban; 378 | else if (!strcmp(type, "friend_add")) 379 | return friend_add; 380 | else if (!strcmp(type, "notify")) 381 | return notify; 382 | else if (!strcmp(type, "group_card")) 383 | return group_card; 384 | else if (!strcmp(type, "offline_file")) 385 | return offline_file; 386 | else if (!strcmp(type, "client_status")) 387 | return client_status; 388 | else if (!strcmp(type, "essence")) 389 | return essence; 390 | return friend_recall; 391 | } 392 | 393 | /*事件响应*/ 394 | //消息事件响应 395 | void message_event_responded(MessageEventInfo info) 396 | { 397 | logInfo("接收群 %d 成员 %s 的消息:%s", info.group_id, info.nickname, info.message); 398 | messageRespondedFunction function = findMessageFunction(info.message); 399 | if (function == NULL) 400 | return; 401 | 402 | function(info); 403 | } 404 | //请求事件响应 405 | void request_event_responded(RequestEventInfo info) 406 | { 407 | logInfo("收到请求 %s 来自 %d", requestTypetoString(info.type), info.user_id); 408 | requestRespondedFunction function = findRequestFunction(info.type); 409 | if (function == NULL) 410 | return; 411 | 412 | function(info); 413 | } 414 | //上报事件响应 415 | void notice_event_responded(NoticeEventInfo info) 416 | { 417 | logInfo("收到群 %d 成员 %s 的上报:%s", info.group_id, info.nickname, noticeTypetoString(info.type)); 418 | noticeRespondedFunction function = findNoticeFunction(info.type); 419 | if (function == NULL) 420 | return; 421 | 422 | function(info); 423 | } 424 | 425 | //事件检索 426 | event_type event_type_switch(JSON* data) 427 | { 428 | char post_type[20] = { '\0' }; 429 | getStringValue(post_type, data, "post_type"); 430 | 431 | if (!strcmp(post_type, "message")) 432 | return message_event; 433 | else if (!strcmp(post_type, "notice")) 434 | return notice_event; 435 | else if (!strcmp(post_type, "meta_event")) 436 | return meta_event; 437 | else if (!strcmp(post_type, "request")) 438 | return request_event; 439 | else 440 | return unknow_event; 441 | } 442 | 443 | int registerMessageFunction(messageRespondedFunction func, char* callName) 444 | { 445 | MessageRespondedNode* con = messageNodeHead; 446 | MessageRespondedNode* node = (MessageRespondedNode*)malloc_s(sizeof(MessageRespondedNode)); 447 | 448 | node->callName = callName; 449 | node->function = func; 450 | node->next = NULL; 451 | 452 | if (con == NULL) 453 | { 454 | messageNodeHead = node; 455 | return 0; 456 | } 457 | else 458 | { 459 | if (!strcmp(con->callName, callName)) 460 | { 461 | logWarn("无法注册消息事件:%s,该名称已注册", callName); 462 | free(node); 463 | return -1; 464 | } 465 | } 466 | 467 | while (con->next != NULL) 468 | { 469 | if (!strcmp(con->next->callName, callName)) 470 | { 471 | logWarn("无法注册消息事件:%s,该名称已注册", callName); 472 | free(node); 473 | return -1; 474 | } 475 | con = con->next; 476 | } 477 | con->next = node; 478 | return 0; 479 | } 480 | 481 | int registerRequestFunction(requestRespondedFunction func, RequestType type, char* callName) 482 | { 483 | RequestRespondedNode* con = requestNodeHead; 484 | RequestRespondedNode* node = (RequestRespondedNode*)malloc_s(sizeof(RequestRespondedNode)); 485 | 486 | node->callName = callName; 487 | node->type = type; 488 | node->function = func; 489 | node->next = NULL; 490 | 491 | if (con == NULL) 492 | { 493 | requestNodeHead = node; 494 | return 0; 495 | } 496 | else 497 | { 498 | if (!strcmp(con->callName, callName)) 499 | { 500 | logWarn("无法注册请求事件:%s,该名称已注册", callName); 501 | free(node); 502 | return -1; 503 | } 504 | } 505 | 506 | while (con->next != NULL) 507 | { 508 | if (!strcmp(con->next->callName, callName)) 509 | { 510 | logWarn("无法注册请求事件:%s,该名称已注册", callName); 511 | free(node); 512 | return -1; 513 | } 514 | con = con->next; 515 | } 516 | con->next = node; 517 | return 0; 518 | } 519 | 520 | int registerNoticeFunction(noticeRespondedFunction func, NoticeType type, char* callName) 521 | { 522 | NoticeRespondedNode* con = noticeNodeHead; 523 | NoticeRespondedNode* node = (NoticeRespondedNode*)malloc_s(sizeof(NoticeRespondedNode)); 524 | 525 | node->callName = callName; 526 | node->type = type; 527 | node->function = func; 528 | node->next = NULL; 529 | 530 | if (con == NULL) 531 | { 532 | noticeNodeHead = node; 533 | return 0; 534 | } 535 | else 536 | { 537 | if (!strcmp(con->callName, callName)) 538 | { 539 | logWarn("无法注册上报事件:%s,该名称已注册", callName); 540 | free(node); 541 | return -1; 542 | } 543 | } 544 | 545 | while (con->next != NULL) 546 | { 547 | if (!strcmp(con->next->callName, callName)) 548 | { 549 | logWarn("无法注册上报事件:%s,该名称已注册", callName); 550 | free(node); 551 | return -1; 552 | } 553 | con = con->next; 554 | } 555 | con->next = node; 556 | return 0; 557 | } 558 | 559 | messageRespondedFunction findMessageFunction(const char* message) 560 | { 561 | MessageRespondedNode* con = messageNodeHead; 562 | 563 | if (!con) 564 | { 565 | logWarn("未注册消息事件响应函数"); 566 | return NULL; 567 | } 568 | 569 | while (con != NULL) 570 | { 571 | if (cmpMsgwithName(message, con->callName)) 572 | { 573 | logInfo("找到消息事件响应: %s", con->callName); 574 | return con->function; 575 | } 576 | con = con->next; 577 | } 578 | return NULL; 579 | } 580 | 581 | requestRespondedFunction findRequestFunction(RequestType type) 582 | { 583 | RequestRespondedNode* con = requestNodeHead; 584 | 585 | if (!con) 586 | { 587 | logWarn("未注册请求事件响应函数"); 588 | return NULL; 589 | } 590 | 591 | while (con != NULL) 592 | { 593 | if (con->type == type) 594 | { 595 | logInfo("找到请求事件响应: %s", con->callName); 596 | return con->function; 597 | } 598 | con = con->next; 599 | } 600 | return NULL; 601 | } 602 | 603 | noticeRespondedFunction findNoticeFunction(NoticeType type) 604 | { 605 | NoticeRespondedNode* con = noticeNodeHead; 606 | 607 | if (!con) 608 | { 609 | logWarn("未注册上报事件响应函数"); 610 | return NULL; 611 | } 612 | 613 | while (con != NULL) 614 | { 615 | if (con->type == type) 616 | { 617 | logInfo("找到上报事件响应: %s", con->callName); 618 | return con->function; 619 | } 620 | con = con->next; 621 | } 622 | return NULL; 623 | } 624 | 625 | //从消息开头比较名称(若匹配则返回1,否则返回0) 626 | static int cmpMsgwithName(const char* message, const char* name) 627 | { 628 | int len = strlen(name); 629 | int i; 630 | for (i = 0; i < len; i++) 631 | { 632 | if (message[i] == '\0' || message[i] != name[i]) 633 | { 634 | return 0; 635 | } 636 | } 637 | return 1; 638 | } 639 | 640 | //把RequestType转换成字符串 641 | static char* requestTypetoString(RequestType type) 642 | { 643 | switch (type) 644 | { 645 | case friend: 646 | return "friend"; 647 | case group: 648 | return "group"; 649 | } 650 | 651 | logErrAll("未知类型"); 652 | return "null"; 653 | } 654 | 655 | //把NoticeType转换成字符串 656 | static char* noticeTypetoString(NoticeType type) 657 | { 658 | switch (type) 659 | { 660 | case friend_recall: 661 | return "friend_recall"; 662 | case group_recall: 663 | return "group_recall"; 664 | case group_increase: 665 | return "group_increase"; 666 | case group_decrease: 667 | return "group_decrease"; 668 | case group_admin: 669 | return "group_admin"; 670 | case group_upload: 671 | return "group_upload"; 672 | case group_ban: 673 | return "group_ban"; 674 | case friend_add: 675 | return "friend_add"; 676 | case notify: 677 | return "notify"; 678 | case group_card: 679 | return "group_card"; 680 | case offline_file: 681 | return "offline_file"; 682 | case client_status: 683 | return "client_status"; 684 | case essence: 685 | return "essence"; 686 | } 687 | 688 | logErrAll("未知类型"); 689 | return "null"; 690 | } 691 | --------------------------------------------------------------------------------