├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── CQXQ ├── CQP.dll ├── CQPluginLoader.cpp ├── CQPluginLoader.h ├── CQTools.cpp ├── CQTools.h ├── CQXQ.cppcheck ├── EncodingConvert.cpp ├── EncodingConvert.h ├── ErrorHandler.cpp ├── ErrorHandler.h ├── GUI.cpp ├── GUI.h ├── GlobalVar.cpp ├── GlobalVar.h ├── Resource.rc ├── RichMessage.cpp ├── RichMessage.h ├── Unpack.cpp ├── Unpack.h ├── XQAPI.h ├── ctpl_stl.h ├── native.cpp ├── native.h └── resource.h ├── LICENSE ├── README.md ├── app_id.txt ├── cmake ├── Modules │ ├── FindVcpkgIncludeDir.cmake │ ├── FixDebugLibraryLookup.cmake │ ├── FixLinkConflict.cmake │ └── cotire.cmake └── x86-windows-static-custom.cmake ├── dummyCQP ├── native.cpp ├── native.h ├── native.sln ├── native.vcxproj ├── native.vcxproj.filters └── native.vcxproj.user ├── scripts ├── build.ps1 ├── clean.ps1 ├── generate.ps1 ├── helpers.ps1 ├── post_build.ps1 ├── prepare.ps1 └── vcpkg.ps1 └── vcpkg-requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | CQXQ/.vs 35 | CQXQ/Debug 36 | CQXQ/Release 37 | CQXQ/x64 38 | 39 | dummyCQP/.vs 40 | dummyCQP/Debug 41 | dummyCQP/Release 42 | dummyCQP/x64 43 | /CQXQ/Resource.aps 44 | /CQXQ/packages/libiconv.lib.1.16.0.6 45 | /CQXQ/CQXQ-cppcheck-build-dir 46 | /build 47 | /CMakeFiles 48 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | set(CMAKE_GENERATOR_TOOLSET "host=x86") 3 | set(CMAKE_GENERATOR_PLATFORM "Win32") 4 | project(CQXQ) 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_FLAGS "/source-charset:utf-8 /execution-charset:gbk ${CMAKE_CXX_FLAGS}") 8 | set(CMAKE_CXX_FLAGS "/MP ${CMAKE_CXX_FLAGS}") # build with object level parallelism 9 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") 10 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") 11 | 12 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") 13 | 14 | include(cotire) 15 | include(FindVcpkgIncludeDir) 16 | include(FixDebugLibraryLookup) 17 | 18 | include_directories(${VCPKG_INCLUDE_DIR}) 19 | include_directories(CQXQ) 20 | 21 | add_compile_definitions(BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE 22 | _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS 23 | WIN32_LEAN_AND_MEAN 24 | NOMINMAX) 25 | 26 | # read app id from app_id.txt 27 | file(READ "app_id.txt" APP_ID) 28 | string(STRIP "${APP_ID}" APP_ID) 29 | set(APP_ID "\"${APP_ID}\"") 30 | add_compile_definitions(APP_ID=${APP_ID}) 31 | 32 | find_package(nlohmann_json CONFIG REQUIRED) 33 | find_package(Iconv REQUIRED) 34 | 35 | file(GLOB_RECURSE SOURCE_FILES CQXQ/*.cpp CQXQ/*.rc) 36 | set(LIB_NAME "CQXQ.XQ") 37 | add_library(${LIB_NAME} SHARED ${SOURCE_FILES}) 38 | 39 | target_link_libraries(${LIB_NAME} PRIVATE Iconv::Iconv) 40 | target_link_libraries(${LIB_NAME} PRIVATE nlohmann_json nlohmann_json::nlohmann_json) 41 | 42 | cotire(${LIB_NAME}) 43 | 44 | add_custom_command(TARGET ${LIB_NAME} 45 | POST_BUILD 46 | COMMAND 47 | powershell -ExecutionPolicy Bypass -NoProfile -File "${PROJECT_SOURCE_DIR}/scripts/post_build.ps1" ${APP_ID} ${LIB_NAME} "$") -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "environments": [ 3 | { 4 | "VCPKG_ROOT": "${projectDir}\\vcpkg", 5 | "VCPKG_CMAKE": "${env.VCPKG_ROOT}\\scripts\\buildsystems\\vcpkg.cmake", 6 | "VCPKG_TRIPLET": "x86-windows-static-custom" 7 | } 8 | ], 9 | "configurations": [ 10 | { 11 | "name": "Debug", 12 | "generator": "Visual Studio 15 2017", 13 | "configurationType": "Debug", 14 | "inheritEnvironments": [ 15 | "msvc_x86" 16 | ], 17 | "buildRoot": "${projectDir}\\build\\${name}", 18 | "cmakeCommandArgs": "-T v141 -DCMAKE_BUILD_TYPE=${name}", 19 | "buildCommandArgs": "", 20 | "ctestCommandArgs": "", 21 | "variables": [ 22 | { 23 | "name": "CMAKE_TOOLCHAIN_FILE", 24 | "value": "${env.VCPKG_CMAKE}" 25 | }, 26 | { 27 | "name": "VCPKG_TARGET_TRIPLET", 28 | "value": "${env.VCPKG_TRIPLET}" 29 | } 30 | ] 31 | }, 32 | { 33 | "name": "Release", 34 | "generator": "Visual Studio 15 2017", 35 | "configurationType": "Release", 36 | "inheritEnvironments": [ 37 | "msvc_x86" 38 | ], 39 | "buildRoot": "${projectDir}\\build\\${name}", 40 | "cmakeCommandArgs": "-T v141 -DCMAKE_BUILD_TYPE=${name}", 41 | "buildCommandArgs": "", 42 | "ctestCommandArgs": "", 43 | "variables": [ 44 | { 45 | "name": "CMAKE_TOOLCHAIN_FILE", 46 | "value": "${env.VCPKG_CMAKE}" 47 | }, 48 | { 49 | "name": "VCPKG_TARGET_TRIPLET", 50 | "value": "${env.VCPKG_TRIPLET}" 51 | } 52 | ] 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /CQXQ/CQP.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w4123/CQXQ/fe400a1578ffd7deba05fd6c2e098cef719f9da4/CQXQ/CQP.dll -------------------------------------------------------------------------------- /CQXQ/CQPluginLoader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "nlohmann/json.hpp" 6 | #include "EncodingConvert.h" 7 | #include "XQAPI.h" 8 | #include "GlobalVar.h" 9 | #include "native.h" 10 | #include "GUI.h" 11 | 12 | // 按照优先级排序 13 | void sortEvents() 14 | { 15 | for (auto& ele : plugins_events) 16 | { 17 | std::sort(ele.second.begin(), ele.second.end()); 18 | } 19 | } 20 | 21 | // plugins_event Not Sorted after calling this function 22 | int loadCQPlugin(const std::filesystem::path& file, int id = -1) 23 | { 24 | std::string ppath = rootPath + "\\CQPlugins\\"; 25 | std::string newFile = ppath + "tmp\\" + std::to_string(time(nullptr)) + "_" + file.filename().string(); 26 | std::error_code ec; 27 | filesystem::copy_file(file, newFile, ec); 28 | if (ec) 29 | { 30 | XQAPI::OutPutLog(("加载"s + file.filename().string() + "失败!无法复制DLL文件!").c_str()); 31 | return ec.value(); 32 | } 33 | native_plugin plugin = { (id == -1) ? nextPluginId++ : id, file.filename().string(), newFile }; 34 | HMODULE dll; 35 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 36 | { 37 | dll = LoadLibraryA(newFile.c_str()); 38 | } 39 | else 40 | { 41 | dll = fakeMainThread.push([&newFile](int) {return LoadLibraryA(newFile.c_str()); }).get(); 42 | } 43 | 44 | if (!dll) 45 | { 46 | int err = GetLastError(); 47 | XQAPI::OutPutLog(("加载"s + file.filename().string() + "失败!LoadLibrary错误代码:" + std::to_string(err)).c_str()); 48 | return err; 49 | } 50 | // 读取Json 51 | auto fileCopy = file; 52 | fileCopy.replace_extension(".json"); 53 | ifstream jsonstream(fileCopy); 54 | FARPROC init = nullptr; 55 | if (jsonstream) 56 | { 57 | try 58 | { 59 | stringstream jStrStream; 60 | jStrStream << jsonstream.rdbuf(); 61 | std::string jsonStr = jStrStream.str(); 62 | nlohmann::json j; 63 | try 64 | { 65 | j = nlohmann::json::parse(jsonStr, nullptr, true, true); 66 | } 67 | catch (std::exception&) 68 | { 69 | j = nlohmann::json::parse(GB18030toUTF8(jsonStr), nullptr, true, true); 70 | } 71 | 72 | plugin.name = UTF8toGB18030(j["name"].get()); 73 | plugin.version = UTF8toGB18030(j["version"].get()); 74 | j["version_id"].get_to(plugin.version_id); 75 | plugin.author = UTF8toGB18030(j["author"].get()); 76 | plugin.description = UTF8toGB18030(j["description"].get()); 77 | for (const auto& it : j["event"]) 78 | { 79 | int type = it["type"].get(); 80 | int priority = it.count("priority") ? it["priority"].get() : 30000; 81 | FARPROC procAddress = nullptr; 82 | if (it.count("function")) 83 | { 84 | procAddress = GetProcAddress(dll, UTF8toGB18030(it["function"].get()).c_str()); 85 | } 86 | else if (it.count("offset")) 87 | { 88 | procAddress = FARPROC((BYTE*)dll + it["offset"].get()); 89 | } 90 | 91 | if (procAddress) 92 | { 93 | auto e = eventType{ plugin.id, priority, procAddress }; 94 | plugin.events[type] = e; 95 | plugins_events[type].push_back(e); 96 | } 97 | else 98 | { 99 | XQAPI::OutPutLog(("加载" + file.filename().string() + "的事件类型" + std::to_string(type) + "时失败! 请检查json文件是否正确!").c_str()); 100 | } 101 | } 102 | for (const auto& it : j["menu"]) 103 | { 104 | FARPROC procAddress = nullptr; 105 | if (it.count("function")) 106 | { 107 | procAddress = GetProcAddress(dll, UTF8toGB18030(it["function"].get()).c_str()); 108 | } 109 | else if (it.count("offset")) 110 | { 111 | procAddress = FARPROC((BYTE*)dll + it["offset"].get()); 112 | } 113 | if (procAddress) 114 | { 115 | plugin.menus.push_back({ UTF8toGB18030(it["name"].get()), procAddress }); 116 | } 117 | else 118 | { 119 | XQAPI::OutPutLog(("加载" + file.filename().string() + "的菜单" + UTF8toGB18030(it["name"].get()) + "时失败! 请检查json文件是否正确!").c_str()); 120 | } 121 | 122 | } 123 | if (j.count("init_offset")) init = FARPROC((BYTE*)dll + j["init_offset"].get()); 124 | } 125 | catch (std::exception& e) 126 | { 127 | XQAPI::OutPutLog(("加载"s + file.filename().string() + "失败!Json文件读取失败! " + e.what()).c_str()); 128 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 129 | { 130 | FreeLibrary(dll); 131 | } 132 | else 133 | { 134 | fakeMainThread.push([&dll](int) {FreeLibrary(dll); }).wait(); 135 | } 136 | return 0; 137 | } 138 | } 139 | else 140 | { 141 | XQAPI::OutPutLog(("加载"s + file.filename().string() + "失败!无法打开Json文件!").c_str()); 142 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 143 | { 144 | FreeLibrary(dll); 145 | } 146 | else 147 | { 148 | fakeMainThread.push([&dll](int) {FreeLibrary(dll); }).wait(); 149 | } 150 | return 0; 151 | } 152 | const auto initFunc = FuncInitialize(init ? init : GetProcAddress(dll, "Initialize")); 153 | if (initFunc) 154 | { 155 | initFunc(plugin.id); 156 | } 157 | else 158 | { 159 | XQAPI::OutPutLog(("加载"s + file.filename().string() + "失败!无公开的Initialize函数!").c_str()); 160 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 161 | { 162 | FreeLibrary(dll); 163 | } 164 | else 165 | { 166 | fakeMainThread.push([&dll](int) {FreeLibrary(dll); }).wait(); 167 | } 168 | return 0; 169 | } 170 | 171 | // 判断是否启用 172 | fileCopy.replace_extension(".disable"); 173 | if (std::filesystem::exists(fileCopy)) 174 | { 175 | plugin.enabled = false; 176 | } 177 | plugin.dll = dll; 178 | plugins.insert({ plugin.id, plugin }); 179 | XQAPI::OutPutLog(("加载"s + file.filename().string() + "成功!").c_str()); 180 | if (plugin.events.count(CQ_eventStartup)) 181 | { 182 | const auto startup = IntMethod(plugin.events[CQ_eventStartup].event); 183 | if (startup) 184 | { 185 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 186 | { 187 | ExceptionWrapper(startup)(); 188 | } 189 | else 190 | { 191 | fakeMainThread.push([startup](int) { ExceptionWrapper(startup)(); }).wait(); 192 | } 193 | } 194 | } 195 | if (EnabledEventCalled && plugin.enabled && plugin.events.count(CQ_eventEnable)) 196 | { 197 | const auto enable = IntMethod(plugin.events[CQ_eventEnable].event); 198 | if (enable) 199 | { 200 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 201 | { 202 | ExceptionWrapper(enable)(); 203 | } 204 | else 205 | { 206 | fakeMainThread.push([enable](int) { ExceptionWrapper(enable)(); }).wait(); 207 | } 208 | } 209 | } 210 | return 0; 211 | } 212 | 213 | void loadOneCQPlugin(const std::filesystem::path& file, int id = -1) 214 | { 215 | loadCQPlugin(file, id); 216 | sortEvents(); 217 | UpdateMainWindow(); 218 | } 219 | 220 | void unloadOneCQPlugin(int id) 221 | { 222 | if (plugins[id].events.count(CQ_eventExit)) 223 | { 224 | const auto exit = IntMethod(plugins[id].events[CQ_eventExit].event); 225 | if (exit) 226 | { 227 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 228 | { 229 | ExceptionWrapper(exit)(); 230 | } 231 | else 232 | { 233 | fakeMainThread.push([exit](int) { ExceptionWrapper(exit)(); }).wait(); 234 | } 235 | } 236 | } 237 | for (auto& event : plugins[id].events) 238 | { 239 | plugins_events[event.first].erase(std::find(plugins_events[event.first].begin(), plugins_events[event.first].end(), event.second)); 240 | } 241 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 242 | { 243 | FreeLibrary(plugins[id].dll); 244 | } 245 | else 246 | { 247 | fakeMainThread.push([&dll = plugins[id].dll](int) {FreeLibrary(dll); }).wait(); 248 | } 249 | 250 | std::error_code ec; 251 | filesystem::remove(plugins[id].newFile, ec); 252 | 253 | plugins.erase(id); 254 | UpdateMainWindow(); 255 | } 256 | 257 | void reloadOneCQPlugin(int id) 258 | { 259 | const string ppath = rootPath + "\\CQPlugins\\" + plugins[id].file; 260 | unloadOneCQPlugin(id); 261 | loadOneCQPlugin(ppath, id); 262 | } 263 | 264 | void loadAllCQPlugin() 265 | { 266 | std::string ppath = rootPath + "\\CQPlugins\\"; 267 | std::filesystem::create_directories(ppath); 268 | for (const auto& file : std::filesystem::directory_iterator(ppath)) 269 | { 270 | if (file.is_regular_file() && file.path().extension() == ".dll") 271 | { 272 | loadCQPlugin(file); 273 | } 274 | } 275 | sortEvents(); 276 | UpdateMainWindow(); 277 | } 278 | 279 | void unloadAllCQPlugin() 280 | { 281 | plugins_events.clear(); 282 | for (auto& plugin : plugins) 283 | { 284 | if (plugin.second.events.count(CQ_eventExit)) 285 | { 286 | const auto exit = IntMethod(plugin.second.events[CQ_eventExit].event); 287 | if (exit) 288 | { 289 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 290 | { 291 | ExceptionWrapper(exit)(); 292 | } 293 | else 294 | { 295 | fakeMainThread.push([exit](int) { ExceptionWrapper(exit)(); }).wait(); 296 | } 297 | } 298 | } 299 | FreeLibrary(plugin.second.dll); 300 | std::error_code ec; 301 | filesystem::remove(plugin.second.newFile, ec); 302 | } 303 | plugins.clear(); 304 | nextPluginId = 1; 305 | UpdateMainWindow(); 306 | } 307 | 308 | void reloadAllCQPlugin() 309 | { 310 | unloadAllCQPlugin(); 311 | loadAllCQPlugin(); 312 | } -------------------------------------------------------------------------------- /CQXQ/CQPluginLoader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | // 按照优先级排序 4 | void sortEvents(); 5 | int loadCQPlugin(const std::filesystem::path& file, int id = -1); 6 | void loadOneCQPlugin(const std::filesystem::path& file, int id = -1); 7 | void unloadOneCQPlugin(int id); 8 | void reloadOneCQPlugin(int id); 9 | void loadAllCQPlugin(); 10 | void unloadAllCQPlugin(); 11 | void reloadAllCQPlugin(); -------------------------------------------------------------------------------- /CQXQ/CQTools.cpp: -------------------------------------------------------------------------------- 1 | #include "CQTools.h" 2 | 3 | #include 4 | #define CP_GBK 936 5 | 6 | using namespace std; 7 | 8 | //代码来源于网络 9 | static const string base64_chars = 10 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 11 | "abcdefghijklmnopqrstuvwxyz" 12 | "0123456789+/"; 13 | 14 | static bool is_base64(const unsigned char c) noexcept 15 | { 16 | return isalnum(c) || c == '+' || c == '/'; 17 | } 18 | 19 | string base64_encode(const string& decode_string) 20 | { 21 | auto in_len = decode_string.size(); 22 | auto bytes_to_encode = decode_string.data(); 23 | string ret; 24 | auto i = 0; 25 | int j; 26 | unsigned char char_array_3[3]; 27 | unsigned char char_array_4[4]; 28 | 29 | while (in_len--) 30 | { 31 | char_array_3[i++] = *(bytes_to_encode++); 32 | if (i == 3) 33 | { 34 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 35 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 36 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 37 | char_array_4[3] = char_array_3[2] & 0x3f; 38 | 39 | for (i = 0; (i < 4); i++) 40 | ret += base64_chars[char_array_4[i]]; 41 | i = 0; 42 | } 43 | } 44 | 45 | if (i) 46 | { 47 | for (j = i; j < 3; j++) 48 | char_array_3[j] = '\0'; 49 | 50 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 51 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 52 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 53 | char_array_4[3] = char_array_3[2] & 0x3f; 54 | 55 | for (j = 0; (j < i + 1); j++) 56 | ret += base64_chars[char_array_4[j]]; 57 | 58 | while (i++ < 3) 59 | ret += '='; 60 | } 61 | 62 | return ret; 63 | } 64 | 65 | string base64_decode(const string& encoded_string) 66 | { 67 | int in_len = encoded_string.size(); 68 | auto i = 0; 69 | int j; 70 | auto in_ = 0; 71 | unsigned char char_array_4[4], char_array_3[3]; 72 | string ret; 73 | 74 | while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) 75 | { 76 | char_array_4[i++] = encoded_string[in_]; 77 | in_++; 78 | if (i == 4) 79 | { 80 | for (i = 0; i < 4; i++) 81 | char_array_4[i] = static_cast(base64_chars.find(char_array_4[i])); 82 | 83 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 84 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 85 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 86 | 87 | for (i = 0; (i < 3); i++) 88 | ret += char_array_3[i]; 89 | i = 0; 90 | } 91 | } 92 | 93 | if (i) 94 | { 95 | for (j = i; j < 4; j++) 96 | char_array_4[j] = 0; 97 | 98 | for (j = 0; j < 4; j++) 99 | char_array_4[j] = static_cast(base64_chars.find(char_array_4[j])); 100 | 101 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 102 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 103 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 104 | 105 | for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; 106 | } 107 | 108 | return ret; 109 | } 110 | 111 | std::string& msg_replace(std::string& s, const std::string& old, const std::string& n) 112 | { 113 | size_t st = 0; 114 | while ((st = s.find(old, st)) < s.size()) 115 | { 116 | s.replace(st, old.size(), n); 117 | st += n.size(); 118 | } 119 | return s; 120 | } 121 | 122 | std::string& msg_encode(std::string& s, const bool isCQ) 123 | { 124 | msg_replace(s, "&", "&"); 125 | msg_replace(s, "[", "["); 126 | msg_replace(s, "]", "]"); 127 | msg_replace(s, "\t", ","); 128 | if (isCQ) 129 | msg_replace(s, ",", ","); 130 | return s; 131 | } 132 | 133 | std::string& msg_decode(std::string& s, const bool isCQ) 134 | { 135 | if (isCQ) 136 | msg_replace(s, ",", ","); 137 | msg_replace(s, "[", "["); 138 | msg_replace(s, "]", "]"); 139 | msg_replace(s, ",", "\t"); 140 | msg_replace(s, "&", "&"); 141 | return s; 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /CQXQ/CQTools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | //base64编码 5 | std::string base64_encode(const std::string& decode_string); 6 | 7 | //base64解码 8 | std::string base64_decode(const std::string& encoded_string); 9 | 10 | //替换 11 | std::string& msg_replace(std::string& s, const std::string& old, const std::string& n); 12 | 13 | //CQcode编码 14 | std::string& msg_encode(std::string& s, bool isCQ = false); 15 | 16 | //CQcode解码 17 | std::string& msg_decode(std::string& s, bool isCQ = false); 18 | 19 | -------------------------------------------------------------------------------- /CQXQ/CQXQ.cppcheck: -------------------------------------------------------------------------------- 1 | 2 | 3 | CQXQ-cppcheck-build-dir 4 | Unspecified 5 | native.sln 6 | false 7 | true 8 | false 9 | 10 10 | 11 | Debug 12 | Release 13 | 14 | 15 | -------------------------------------------------------------------------------- /CQXQ/EncodingConvert.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "iconv.h" 3 | #include "EncodingConvert.h" 4 | 5 | std::string UTF8toGB18030(const std::string& strUTF8) 6 | { 7 | return ConvertEncoding(strUTF8, "utf-8", "gb18030"); 8 | } 9 | 10 | std::string GB18030toUTF8(const std::string& strGB18030) 11 | { 12 | return ConvertEncoding(strGB18030, "gb18030", "utf-8"); 13 | } 14 | 15 | std::wstring GB18030toUTF16(const std::string& strGB18030) 16 | { 17 | return ConvertEncoding(strGB18030, "gb18030", "utf-16le"); 18 | } 19 | 20 | std::string UTF16toGB18030(const std::wstring& strUTF16) 21 | { 22 | return ConvertEncoding(strUTF16, "utf-16le", "gb18030"); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /CQXQ/EncodingConvert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "iconv.h" 5 | 6 | template 7 | std::basic_string ConvertEncoding(const std::basic_string& in, const std::string& InEnc, const std::string& OutEnc, const double CapFac = 2.0) 8 | { 9 | const auto cd = iconv_open(OutEnc.c_str(), InEnc.c_str()); 10 | if (cd == (iconv_t)-1) 11 | { 12 | return std::basic_string(); 13 | } 14 | size_t in_len = in.size() * sizeof(Q); 15 | size_t out_len = size_t(in_len * CapFac + sizeof(T)); 16 | char* in_ptr = const_cast(reinterpret_cast (in.c_str())); 17 | char* out_ptr = new char[out_len](); 18 | 19 | // As out_ptr would be modified by iconv(), store a copy of it pointing to the beginning of the array 20 | char* out_ptr_copy = out_ptr; 21 | if (iconv(cd, &in_ptr, &in_len, &out_ptr, &out_len) == (size_t)-1) 22 | { 23 | delete[] out_ptr_copy; 24 | iconv_close(cd); 25 | return std::basic_string(); 26 | } 27 | memset(out_ptr, 0, sizeof(T)); 28 | std::basic_string ret(reinterpret_cast(out_ptr_copy)); 29 | delete[] out_ptr_copy; 30 | iconv_close(cd); 31 | return ret; 32 | } 33 | 34 | std::string UTF8toGB18030(const std::string& strUTF8); 35 | 36 | std::string GB18030toUTF8(const std::string& strGB18030); 37 | 38 | std::wstring GB18030toUTF16(const std::string& strGB18030); 39 | 40 | std::string UTF16toGB18030(const std::wstring& strUTF16); -------------------------------------------------------------------------------- /CQXQ/ErrorHandler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "EncodingConvert.h" 9 | #include 10 | #include "CQPluginLoader.h" 11 | using namespace std; 12 | 13 | // 错误处理 14 | #pragma comment(lib,"Dbghelp.lib") 15 | 16 | 17 | typedef unsigned int exception_code_t; 18 | 19 | const char* opDescription(const ULONG opcode) 20 | { 21 | switch (opcode) { 22 | case 0: return "read"; 23 | case 1: return "write"; 24 | case 8: return "user-mode data execution prevention (DEP) violation"; 25 | default: return "unknown"; 26 | } 27 | } 28 | #define EXCEPTION_UNCAUGHT_CXX_EXCEPTION 0xe06d7363 29 | 30 | void getCxxExceptionInfo(std::ostringstream& oss, ULONG_PTR expInfo) 31 | { 32 | __try 33 | { 34 | const char * info = reinterpret_cast(expInfo)->what(); 35 | oss << "CXX Exception Info: " << info; 36 | } 37 | __except (EXCEPTION_EXECUTE_HANDLER) 38 | { 39 | 40 | } 41 | } 42 | 43 | std::string seDescription(const exception_code_t& code) 44 | { 45 | switch (code) { 46 | case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; 47 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; 48 | case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT"; 49 | case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT"; 50 | case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND"; 51 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; 52 | case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT"; 53 | case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION"; 54 | case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW"; 55 | case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK"; 56 | case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW"; 57 | case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION"; 58 | case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR"; 59 | case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO"; 60 | case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW"; 61 | case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION"; 62 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; 63 | case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION"; 64 | case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP"; 65 | case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW"; 66 | case EXCEPTION_UNCAUGHT_CXX_EXCEPTION: return "EXCEPTION_UNCAUGHT_CXX_EXCEPTION"; 67 | default: return "UNKNOWN EXCEPTION " + std::to_string(code); 68 | } 69 | } 70 | 71 | std::string expInformation(struct _EXCEPTION_POINTERS* ep, bool has_exception_code = false, exception_code_t code = 0) 72 | { 73 | HMODULE hm; 74 | GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 75 | static_cast(ep->ExceptionRecord->ExceptionAddress), &hm); 76 | MODULEINFO mi; 77 | GetModuleInformation(GetCurrentProcess(), hm, &mi, sizeof(mi)); 78 | char fn[MAX_PATH]; 79 | GetModuleFileNameExA(GetCurrentProcess(), hm, fn, MAX_PATH); 80 | 81 | std::ostringstream oss; 82 | oss << "SE " << (has_exception_code ? seDescription(code) : "") << " at address 0x" << std::hex << ep->ExceptionRecord->ExceptionAddress << std::dec 83 | << " inside " << fn << " loaded at base address 0x" << std::hex << mi.lpBaseOfDll << "\n"; 84 | 85 | if (has_exception_code && ( 86 | code == EXCEPTION_ACCESS_VIOLATION || 87 | code == EXCEPTION_IN_PAGE_ERROR)) { 88 | oss << "Invalid operation: " << opDescription(ep->ExceptionRecord->ExceptionInformation[0]) << " at address 0x" << std::hex << ep->ExceptionRecord->ExceptionInformation[1] << std::dec << "\n"; 89 | } 90 | 91 | if (has_exception_code && code == EXCEPTION_IN_PAGE_ERROR) { 92 | oss << "Underlying NTSTATUS code that resulted in the exception " << ep->ExceptionRecord->ExceptionInformation[2] << "\n"; 93 | } 94 | 95 | if (has_exception_code && 96 | code == EXCEPTION_UNCAUGHT_CXX_EXCEPTION) 97 | { 98 | getCxxExceptionInfo(oss, ep->ExceptionRecord->ExceptionInformation[1]); 99 | } 100 | 101 | return oss.str(); 102 | } 103 | 104 | 105 | std::string formatStack(CONTEXT* ctx) //Prints stack trace based on context record 106 | { 107 | std::stringstream ret; 108 | const int MaxNameLen = 256; 109 | 110 | BOOL result; 111 | HANDLE process; 112 | HANDLE thread; 113 | HMODULE hModule; 114 | 115 | STACKFRAME64 stack; 116 | ULONG frame; 117 | DWORD64 displacement; 118 | 119 | DWORD disp; 120 | IMAGEHLP_LINE64* line; 121 | 122 | char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; 123 | char module[MaxNameLen]; 124 | PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; 125 | 126 | memset(&stack, 0, sizeof(STACKFRAME64)); 127 | 128 | process = GetCurrentProcess(); 129 | thread = GetCurrentThread(); 130 | displacement = 0; 131 | #if !defined(_M_AMD64) 132 | stack.AddrPC.Offset = (*ctx).Eip; 133 | stack.AddrPC.Mode = AddrModeFlat; 134 | stack.AddrStack.Offset = (*ctx).Esp; 135 | stack.AddrStack.Mode = AddrModeFlat; 136 | stack.AddrFrame.Offset = (*ctx).Ebp; 137 | stack.AddrFrame.Mode = AddrModeFlat; 138 | #endif 139 | 140 | for (frame = 0; ; frame++) 141 | { 142 | //get next call from stack 143 | result = StackWalk64 144 | ( 145 | #if defined(_M_AMD64) 146 | IMAGE_FILE_MACHINE_AMD64 147 | #else 148 | IMAGE_FILE_MACHINE_I386 149 | #endif 150 | , 151 | process, 152 | thread, 153 | &stack, 154 | ctx, 155 | NULL, 156 | SymFunctionTableAccess64, 157 | SymGetModuleBase64, 158 | NULL 159 | ); 160 | 161 | if (!result) break; 162 | 163 | //get symbol name for address 164 | pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); 165 | pSymbol->MaxNameLen = MAX_SYM_NAME; 166 | 167 | hModule = NULL; 168 | lstrcpyA(module, ""); 169 | GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 170 | (LPCTSTR)(stack.AddrPC.Offset), &hModule); 171 | 172 | if (SymFromAddr(process, (ULONG64)stack.AddrPC.Offset, &displacement, pSymbol)) 173 | { 174 | line = (IMAGEHLP_LINE64*)malloc(sizeof(IMAGEHLP_LINE64)); 175 | if (line) 176 | { 177 | line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); 178 | ret << "\tat " << pSymbol->Name; 179 | //try to get line 180 | if (SymGetLineFromAddr64(process, stack.AddrPC.Offset, &disp, line)) 181 | { 182 | ret << " in " << line->FileName << ": line: " << dec << line->LineNumber; 183 | } 184 | } 185 | ret << ", address 0x" << hex << (DWORD64)hModule << "+0x" << hex << pSymbol->Address - (DWORD64)hModule << "+0x" << hex << stack.AddrPC.Offset - pSymbol->Address; 186 | 187 | free(line); 188 | line = NULL; 189 | } 190 | else 191 | { 192 | ret << "\tat address 0x" << hex << (DWORD64)hModule << "+0x" << hex << stack.AddrPC.Offset - (DWORD64)hModule; 193 | } 194 | 195 | 196 | //at least print module name 197 | if (hModule != NULL) 198 | { 199 | if (GetModuleFileNameA(hModule, module, MaxNameLen)) 200 | { 201 | ret << " in " << module; 202 | } 203 | } 204 | 205 | ret << endl; 206 | 207 | } 208 | return ret.str(); 209 | } 210 | 211 | std::mutex HandlerMutex; 212 | 213 | LONG WINAPI CQXQUnhandledExceptionFilter( 214 | LPEXCEPTION_POINTERS ExceptionInfo 215 | ) 216 | { 217 | std::unique_lock lock(HandlerMutex); 218 | 219 | // 调试用-加载符号 220 | SymInitialize(GetCurrentProcess(), NULL, TRUE); 221 | SymSetOptions(SYMOPT_LOAD_LINES); 222 | 223 | INITCOMMONCONTROLSEX ex; 224 | ex.dwICC = ICC_STANDARD_CLASSES; 225 | ex.dwSize = sizeof(ex); 226 | InitCommonControlsEx(&ex); 227 | 228 | TASKDIALOGCONFIG config; 229 | config.cbSize = sizeof(config); 230 | config.hwndParent = nullptr; 231 | config.hInstance = nullptr; 232 | config.dwFlags = TDF_EXPAND_FOOTER_AREA | TDF_SIZE_TO_CONTENT | TDF_USE_COMMAND_LINKS; 233 | config.dwCommonButtons = 0; 234 | config.pszWindowTitle = L"CQXQ 错误处理"; 235 | config.pszMainIcon = TD_ERROR_ICON; 236 | config.pszMainInstruction = L"CQXQ 运行中遇到错误, 这可能是CQXQ本身或是某个插件导致的"; 237 | 238 | std::wstring expInfo = GB18030toUTF16(expInformation(ExceptionInfo, true, ExceptionInfo->ExceptionRecord->ExceptionCode)); 239 | config.pszContent = expInfo.c_str(); 240 | 241 | constexpr int NUM_OF_BUTTONS = 4; 242 | config.cButtons = NUM_OF_BUTTONS; 243 | 244 | TASKDIALOG_BUTTON buttons[NUM_OF_BUTTONS]; 245 | constexpr int IGNORE_BUTTON = 101; 246 | constexpr int RELOAD_BUTTON = 102; 247 | constexpr int RELOAD_EXCEPT_ERROR_BUTTON = 103; 248 | constexpr int EXIT_BUTTON = 104; 249 | 250 | buttons[0].nButtonID = IGNORE_BUTTON; 251 | buttons[0].pszButtonText = L"忽略\nCQXQ将忽略此异常, 但是应用程序可能表现异常"; 252 | buttons[1].nButtonID = RELOAD_BUTTON; 253 | buttons[1].pszButtonText = L"重载\nCQXQ将重载所有插件"; 254 | buttons[2].nButtonID = RELOAD_EXCEPT_ERROR_BUTTON; 255 | buttons[2].pszButtonText = L"重载(禁用出错应用)\nCQXQ将重载所有插件, 但将禁用出错应用(别点这个还没写完)"; 256 | buttons[3].nButtonID = EXIT_BUTTON; 257 | buttons[3].pszButtonText = L"退出\n程序将会退出"; 258 | 259 | config.pButtons = buttons; 260 | config.nDefaultButton = IGNORE_BUTTON; 261 | config.cRadioButtons = 0; 262 | config.pRadioButtons = nullptr; 263 | config.nDefaultRadioButton = 0; 264 | config.pszVerificationText = nullptr; 265 | std::wstring stkInfo = GB18030toUTF16(formatStack(ExceptionInfo->ContextRecord)); 266 | config.pszExpandedInformation = stkInfo.c_str(); 267 | config.pszExpandedControlText = L"隐藏"; 268 | config.pszCollapsedControlText = L"详细信息"; 269 | config.pszFooterIcon = nullptr; 270 | config.pszFooter = nullptr; 271 | config.pfCallback = nullptr; 272 | config.lpCallbackData = (LONG_PTR)nullptr; 273 | config.cxWidth = 0; 274 | 275 | int pnButton = 0; 276 | TaskDialogIndirect(&config, &pnButton, nullptr, nullptr); 277 | 278 | if (pnButton == EXIT_BUTTON) 279 | { 280 | SymCleanup(GetCurrentProcess()); 281 | exit(EXIT_FAILURE); 282 | } 283 | else if (pnButton == RELOAD_BUTTON) 284 | { 285 | reloadAllCQPlugin(); 286 | } 287 | SymCleanup(GetCurrentProcess()); 288 | return EXCEPTION_EXECUTE_HANDLER; 289 | } -------------------------------------------------------------------------------- /CQXQ/ErrorHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | LONG WINAPI CQXQUnhandledExceptionFilter( 6 | LPEXCEPTION_POINTERS ExceptionInfo 7 | ); 8 | 9 | template 10 | std::function __stdcall ExceptionWrapper(T(__stdcall* func)(Args...)) 11 | { 12 | return std::function([func](Args&&... args) -> T 13 | { 14 | __try 15 | { 16 | return func(std::forward(args)...); 17 | } 18 | __except (CQXQUnhandledExceptionFilter(GetExceptionInformation())) 19 | { 20 | ; 21 | } 22 | return T(); 23 | }); 24 | } -------------------------------------------------------------------------------- /CQXQ/GUI.cpp: -------------------------------------------------------------------------------- 1 | #include "GUI.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "CQPluginLoader.h" 16 | #include "GlobalVar.h" 17 | #include "native.h" 18 | #include 19 | 20 | using namespace std; 21 | 22 | #pragma comment(lib, "comctl32.lib") 23 | 24 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 25 | 26 | #define ID_MASTER_BUTTONENABLE 1001 27 | #define ID_MASTER_BUTTONRELOAD 1002 28 | #define ID_MASTER_BUTTONMENU 1003 29 | #define ID_MASTER_STATICDESC 1004 30 | #define ID_MASTER_LVPLUGIN 1005 31 | #define ID_MASTER_BUTTONRECVSELFMSG 1006 32 | #define ID_MASTER_BUTTONRELOADALL 1007 33 | #define ID_MASTER_BUTTONGITHUB 1008 34 | #define ID_MASTER_BUTTONADDGROUP 1009 35 | 36 | template 37 | class BaseWindow 38 | { 39 | public: 40 | virtual ~BaseWindow() = default; 41 | 42 | static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 43 | { 44 | T* pThis; 45 | 46 | if (uMsg == WM_NCCREATE) 47 | { 48 | auto pCreate = reinterpret_cast(lParam); 49 | pThis = static_cast(pCreate->lpCreateParams); 50 | SetWindowLongPtrA(hwnd, GWLP_USERDATA, reinterpret_cast(pThis)); 51 | 52 | pThis->m_hwnd = hwnd; 53 | } 54 | else 55 | { 56 | pThis = reinterpret_cast(GetWindowLongPtrA(hwnd, GWLP_USERDATA)); 57 | } 58 | 59 | if (pThis) 60 | { 61 | return pThis->HandleMessage(uMsg, wParam, lParam); 62 | } 63 | return DefWindowProcA(hwnd, uMsg, wParam, lParam); 64 | } 65 | 66 | BaseWindow() : m_hwnd(nullptr) 67 | { 68 | } 69 | 70 | BOOL Create( 71 | PCSTR lpWindowName, 72 | DWORD dwStyle, 73 | DWORD dwExStyle = 0, 74 | int x = CW_USEDEFAULT, 75 | int y = CW_USEDEFAULT, 76 | int nWidth = CW_USEDEFAULT, 77 | int nHeight = CW_USEDEFAULT, 78 | HWND hWndParent = nullptr, 79 | HMENU hMenu = nullptr 80 | ) 81 | { 82 | WNDCLASS wc{}; 83 | 84 | wc.lpfnWndProc = T::WindowProc; 85 | wc.hInstance = hDllModule; 86 | wc.lpszClassName = ClassName(); 87 | 88 | RegisterClassA(&wc); 89 | 90 | m_hwnd = CreateWindowExA( 91 | dwExStyle, ClassName(), lpWindowName, dwStyle, x, y, 92 | nWidth, nHeight, hWndParent, hMenu, hDllModule, this 93 | ); 94 | 95 | return (m_hwnd ? TRUE : FALSE); 96 | } 97 | 98 | [[nodiscard]] HWND Window() const { return m_hwnd; } 99 | 100 | protected: 101 | 102 | [[nodiscard]] virtual PCSTR ClassName() const = 0; 103 | virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0; 104 | 105 | HWND m_hwnd; 106 | }; 107 | 108 | // 基础ListView类 109 | // 使用之前必须先调用InitCommonControl/InitCommonControlEx 110 | class BasicListView 111 | { 112 | public: 113 | BasicListView() : hwnd(nullptr) 114 | { 115 | } 116 | 117 | [[nodiscard]] HWND Window() const { return hwnd; } 118 | 119 | BOOL Create( 120 | PCSTR lpWindowName, 121 | DWORD dwStyle, 122 | DWORD dwExStyle = 0, 123 | int x = CW_USEDEFAULT, 124 | int y = CW_USEDEFAULT, 125 | int nWidth = CW_USEDEFAULT, 126 | int nHeight = CW_USEDEFAULT, 127 | HWND hWndParent = nullptr, 128 | HMENU hMenu = nullptr 129 | ) 130 | { 131 | hwnd = CreateWindowExA( 132 | dwExStyle, WC_LISTVIEWA, lpWindowName, dwStyle, x, y, 133 | nWidth, nHeight, hWndParent, hMenu, hDllModule, this 134 | ); 135 | 136 | return (hwnd ? TRUE : FALSE); 137 | } 138 | 139 | int AddTextColumn(const char* pszText, int width = 150, int fmt = LVCFMT_LEFT, int isubItem = -1) 140 | { 141 | LVCOLUMNA lvC; 142 | lvC.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; 143 | lvC.pszText = const_cast(pszText); 144 | lvC.cx = width; 145 | lvC.fmt = fmt; 146 | if (isubItem == -1) 147 | { 148 | HWND header = ListView_GetHeader(hwnd); 149 | isubItem = Header_GetItemCount(header); 150 | } 151 | return ListView_InsertColumn(hwnd, isubItem, &lvC); 152 | } 153 | 154 | // 带宽度 155 | void AddAllTextColumn(const std::vector>& texts) 156 | { 157 | for (const auto& item : texts) 158 | { 159 | AddTextColumn(item.first.c_str(), item.second); 160 | } 161 | } 162 | 163 | void AddAllTextColumn(const std::initializer_list& texts) 164 | { 165 | for (const auto& item : texts) 166 | { 167 | AddTextColumn(item.c_str()); 168 | } 169 | } 170 | 171 | void AddTextRow(const std::initializer_list& texts, int index = -1) 172 | { 173 | if (texts.size() == 0) return; 174 | LVITEMA lvI; 175 | lvI.mask = LVIF_TEXT; 176 | lvI.pszText = const_cast(texts.begin()->c_str()); 177 | if (index == -1) 178 | { 179 | index = ListView_GetItemCount(hwnd); 180 | } 181 | lvI.iItem = index; 182 | lvI.iSubItem = 0; 183 | ListView_InsertItem(hwnd, &lvI); 184 | int curr = 1; 185 | for (auto s = texts.begin() + 1; s != texts.end(); s++) 186 | { 187 | ListView_SetItemText(hwnd, index, curr, const_cast(s->c_str())); 188 | curr++; 189 | } 190 | } 191 | 192 | DWORD SetExtendedListViewStyle(DWORD style) 193 | { 194 | return ListView_SetExtendedListViewStyle(hwnd, style); 195 | } 196 | 197 | [[nodiscard]] int GetItemIndexByText(const std::string& text, int iStart = -1) 198 | { 199 | LVFINDINFOA info; 200 | info.flags = LVFI_STRING; 201 | info.psz = const_cast(text.c_str()); 202 | return ListView_FindItem(hwnd, iStart, &info); 203 | } 204 | 205 | void SetItemText(const string& text, int index, int subindex = 0) 206 | { 207 | if (index < 0)return; 208 | ListView_SetItemText(hwnd, index, subindex, const_cast(text.c_str())); 209 | } 210 | 211 | // 长度最长为1000 212 | [[nodiscard]] std::string GetItemText(int index, int subindex = 0) 213 | { 214 | char buffer[1000]{}; 215 | ListView_GetItemText(hwnd, index, subindex, buffer, 1000); 216 | return buffer; 217 | } 218 | 219 | BOOL DeleteItemByIndex(int index) 220 | { 221 | return ListView_DeleteItem(hwnd, index); 222 | } 223 | 224 | BOOL DeleteAllItems() 225 | { 226 | return ListView_DeleteAllItems(hwnd); 227 | } 228 | 229 | BOOL DeleteColumn(int iCol = 0) 230 | { 231 | return ListView_DeleteColumn(hwnd, iCol); 232 | } 233 | 234 | BOOL Show(bool show = true) 235 | { 236 | return ShowWindow(hwnd, show ? SW_SHOW : SW_HIDE); 237 | } 238 | 239 | protected: 240 | HWND hwnd; 241 | }; 242 | 243 | // 基础Edit类 244 | class BasicEdit 245 | { 246 | public: 247 | BasicEdit() : hwnd(nullptr) 248 | { 249 | } 250 | 251 | [[nodiscard]] HWND Window() const { return hwnd; } 252 | 253 | BOOL Create( 254 | PCSTR lpWindowName, 255 | DWORD dwStyle, 256 | DWORD dwExStyle = 0, 257 | int x = CW_USEDEFAULT, 258 | int y = CW_USEDEFAULT, 259 | int nWidth = CW_USEDEFAULT, 260 | int nHeight = CW_USEDEFAULT, 261 | HWND hWndParent = nullptr, 262 | HMENU hMenu = nullptr 263 | ) 264 | { 265 | hwnd = CreateWindowExA( 266 | dwExStyle, "EDIT", lpWindowName, dwStyle, x, y, 267 | nWidth, nHeight, hWndParent, hMenu, hDllModule, this 268 | ); 269 | 270 | return (hwnd ? TRUE : FALSE); 271 | } 272 | 273 | LRESULT SetFont(HFONT hFont) 274 | { 275 | return SendMessageA(hwnd, WM_SETFONT, (WPARAM)hFont, 1); 276 | } 277 | 278 | void SetText(const std::string& text) 279 | { 280 | Edit_SetText(hwnd, text.c_str()); 281 | } 282 | 283 | [[nodiscard]] std::string GetText() 284 | { 285 | const int length = Edit_GetTextLength(hwnd) + 1; 286 | const std::unique_ptr uptr = std::make_unique(length); 287 | if (uptr) 288 | { 289 | Edit_GetText(hwnd, uptr.get(), length); 290 | return uptr.get(); 291 | } 292 | return ""; 293 | } 294 | 295 | BOOL Show(bool show = true) 296 | { 297 | return ShowWindow(hwnd, show ? SW_SHOW : SW_HIDE); 298 | } 299 | 300 | protected: 301 | HWND hwnd; 302 | }; 303 | 304 | // 基础Button类 305 | class BasicButton 306 | { 307 | public: 308 | BasicButton() : hwnd(nullptr) 309 | { 310 | } 311 | 312 | [[nodiscard]] HWND Window() const { return hwnd; } 313 | 314 | BOOL Create( 315 | PCSTR lpWindowName, 316 | DWORD dwStyle, 317 | DWORD dwExStyle = 0, 318 | int x = CW_USEDEFAULT, 319 | int y = CW_USEDEFAULT, 320 | int nWidth = CW_USEDEFAULT, 321 | int nHeight = CW_USEDEFAULT, 322 | HWND hWndParent = nullptr, 323 | HMENU hMenu = nullptr 324 | ) 325 | { 326 | hwnd = CreateWindowExA( 327 | dwExStyle, "BUTTON", lpWindowName, dwStyle, x, y, 328 | nWidth, nHeight, hWndParent, hMenu, hDllModule, this 329 | ); 330 | 331 | return (hwnd ? TRUE : FALSE); 332 | } 333 | 334 | LRESULT SetFont(HFONT hFont) 335 | { 336 | return SendMessageA(hwnd, WM_SETFONT, (WPARAM)hFont, 1); 337 | } 338 | 339 | BOOL Show(bool show = true) 340 | { 341 | return ShowWindow(hwnd, show ? SW_SHOW : SW_HIDE); 342 | } 343 | 344 | void SetText(const std::string& text) 345 | { 346 | Button_SetText(hwnd, text.c_str()); 347 | } 348 | 349 | protected: 350 | HWND hwnd; 351 | }; 352 | 353 | // 基础Static类 354 | class BasicStatic 355 | { 356 | public: 357 | BasicStatic() : hwnd(nullptr) 358 | { 359 | } 360 | 361 | [[nodiscard]] HWND Window() const { return hwnd; } 362 | 363 | BOOL Create( 364 | PCSTR lpWindowName, 365 | DWORD dwStyle, 366 | DWORD dwExStyle = 0, 367 | int x = CW_USEDEFAULT, 368 | int y = CW_USEDEFAULT, 369 | int nWidth = CW_USEDEFAULT, 370 | int nHeight = CW_USEDEFAULT, 371 | HWND hWndParent = nullptr, 372 | HMENU hMenu = nullptr 373 | ) 374 | { 375 | hwnd = CreateWindowExA( 376 | dwExStyle, "STATIC", lpWindowName, dwStyle, x, y, 377 | nWidth, nHeight, hWndParent, hMenu, hDllModule, this 378 | ); 379 | 380 | return (hwnd ? TRUE : FALSE); 381 | } 382 | 383 | LRESULT SetFont(HFONT hFont) 384 | { 385 | return SendMessageA(hwnd, WM_SETFONT, (WPARAM)hFont, 1); 386 | } 387 | 388 | BOOL Show(bool show = true) 389 | { 390 | return ShowWindow(hwnd, show ? SW_SHOW : SW_HIDE); 391 | } 392 | 393 | void SetText(const std::string& text) 394 | { 395 | Static_SetText(hwnd, text.c_str()); 396 | } 397 | 398 | [[nodiscard]] std::string GetText() 399 | { 400 | const int length = Static_GetTextLength(hwnd) + 1; 401 | const std::unique_ptr uptr = std::make_unique(length); 402 | if (uptr) 403 | { 404 | Static_GetText(hwnd, uptr.get(), length); 405 | return uptr.get(); 406 | } 407 | return ""; 408 | } 409 | 410 | void SetBitmap(HBITMAP hBitmap) 411 | { 412 | SendMessageA(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBitmap); 413 | } 414 | 415 | protected: 416 | HWND hwnd; 417 | }; 418 | 419 | class GUI final : public BaseWindow 420 | { 421 | public: 422 | // Master 423 | 424 | int SelectedIndex = -1; 425 | 426 | BasicListView ListViewPlugin; 427 | BasicStatic StaticDesc; 428 | BasicButton ButtonEnable; 429 | BasicButton ButtonReload; 430 | BasicButton ButtonMenu; 431 | BasicButton ButtonSwitchRecvSelfMsg; 432 | BasicButton ButtonReloadAll; 433 | BasicButton ButtonGitHub; 434 | BasicButton ButtonAddGroup; 435 | 436 | std::map Fonts; 437 | LRESULT CreateMainPage(); 438 | LRESULT UpdateGUI(); 439 | 440 | [[nodiscard]] PCSTR ClassName() const override { return "GUI"; } 441 | LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) override; 442 | 443 | GUI() = default; 444 | }; 445 | 446 | 447 | LRESULT GUI::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) 448 | { 449 | switch (uMsg) 450 | { 451 | case WM_CREATE: 452 | { 453 | RECT rcClient; // The parent window's client area. 454 | GetClientRect(m_hwnd, &rcClient); 455 | 456 | // 添加字体/图片等 457 | Fonts["Yahei14"] = CreateFontA(14, 0, 0, 0, FW_DONTCARE, FALSE, 458 | FALSE, FALSE, GB2312_CHARSET, OUT_DEFAULT_PRECIS, 459 | CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | DEFAULT_PITCH, "微软雅黑"); 460 | Fonts["Yahei18"] = CreateFontA(18, 0, 0, 0, FW_DONTCARE, FALSE, 461 | FALSE, FALSE, GB2312_CHARSET, OUT_DEFAULT_PRECIS, 462 | CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | DEFAULT_PITCH, "微软雅黑"); 463 | Fonts["Yahei22"] = CreateFontA(22, 0, 0, 0, FW_DONTCARE, FALSE, 464 | FALSE, FALSE, GB2312_CHARSET, OUT_DEFAULT_PRECIS, 465 | CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | DEFAULT_PITCH, "微软雅黑"); 466 | 467 | SendMessage(m_hwnd, WM_SETFONT, (WPARAM)Fonts["Yahei14"], 1); 468 | CreateMainPage(); 469 | 470 | return 0; 471 | } 472 | case WM_CLOSE: 473 | ShowWindow(m_hwnd, SW_HIDE); 474 | return 0; 475 | 476 | case WM_DESTROY: 477 | PostQuitMessage(0); 478 | for (const auto& font : Fonts) 479 | { 480 | DeleteObject(font.second); 481 | } 482 | Fonts.clear(); 483 | m_hwnd = nullptr; 484 | return 0; 485 | 486 | case WM_PAINT: 487 | { 488 | PAINTSTRUCT ps; 489 | HDC hdc = BeginPaint(m_hwnd, &ps); 490 | FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); 491 | EndPaint(m_hwnd, &ps); 492 | } 493 | return 0; 494 | case WM_CTLCOLORSTATIC: 495 | { 496 | HDC hdcStatic = (HDC)wParam; 497 | //SetTextColor(hdcStatic, RGB(255, 255, 255)); 498 | SetBkMode(hdcStatic, TRANSPARENT); 499 | return (INT_PTR)static_cast(GetStockObject(COLOR_WINDOW + 1)); 500 | } 501 | case WM_COMMAND: 502 | { 503 | switch (LOWORD(wParam)) 504 | { 505 | case ID_MASTER_BUTTONENABLE: 506 | { 507 | if (SelectedIndex == -1) 508 | { 509 | MessageBoxA(m_hwnd, "请先单击左侧列表选择一个插件!", "CQXQ", MB_OK); 510 | return 0; 511 | } 512 | if (plugins[SelectedIndex].enabled) 513 | { 514 | plugins[SelectedIndex].enabled = false; 515 | ButtonEnable.SetText("启用"); 516 | if (!plugins[SelectedIndex].events.count(CQ_eventDisable)) return 0; 517 | const auto disable = IntMethod(plugins[SelectedIndex].events.at(CQ_eventDisable).event); 518 | if (disable && EnabledEventCalled) 519 | { 520 | disable(); 521 | } 522 | std::filesystem::path p(rootPath); 523 | p.append("CQPlugins").append(plugins[SelectedIndex].file).replace_extension(".disable"); 524 | if (!std::filesystem::exists(p)) 525 | { 526 | ofstream fstream(p); // 创建文件 527 | fstream << "This file is used to disable the corresponding plugin"; 528 | fstream.close(); 529 | } 530 | } 531 | else 532 | { 533 | plugins[SelectedIndex].enabled = true; 534 | ButtonEnable.SetText("停用"); 535 | if (!plugins[SelectedIndex].events.count(CQ_eventEnable)) return 0; 536 | const auto enable = IntMethod(plugins[SelectedIndex].events.at(CQ_eventEnable).event); 537 | if (enable && EnabledEventCalled) 538 | { 539 | enable(); 540 | } 541 | std::filesystem::path p(rootPath); 542 | p.append("CQPlugins").append(plugins[SelectedIndex].file).replace_extension(".disable"); 543 | if (std::filesystem::exists(p)) 544 | { 545 | std::filesystem::remove(p); 546 | } 547 | } 548 | } 549 | return 0; 550 | case ID_MASTER_BUTTONMENU: 551 | { 552 | if (SelectedIndex == -1) 553 | { 554 | MessageBoxA(m_hwnd, "请先单击左侧列表选择一个插件!", "CQXQ", MB_OK); 555 | return 0; 556 | } 557 | if (!plugins[SelectedIndex].enabled) 558 | { 559 | MessageBoxA(m_hwnd, "插件尚未启用,请先启用插件!", "CQXQ", MB_OK); 560 | return 0; 561 | } 562 | if (!EnabledEventCalled) 563 | { 564 | MessageBoxA(m_hwnd, "插件尚未初始化完毕,等待QQ登陆完成后插件进行初始化!", "CQXQ", MB_OK); 565 | return 0; 566 | } 567 | if (plugins[SelectedIndex].menus.empty()) return 0; 568 | HMENU hMenu = CreatePopupMenu(); 569 | POINT curpos; 570 | GetCursorPos(&curpos); 571 | int count = 1; 572 | for (const auto& menu : plugins[SelectedIndex].menus) 573 | { 574 | AppendMenuA(hMenu, MF_STRING, count, menu.first.c_str()); 575 | count++; 576 | } 577 | BOOL ret = TrackPopupMenuEx( 578 | hMenu, 579 | TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON, 580 | curpos.x, 581 | curpos.y, 582 | m_hwnd, 583 | nullptr 584 | ); 585 | DestroyMenu(hMenu); 586 | if (ret) 587 | { 588 | const auto m = IntMethod(plugins[SelectedIndex].menus[ret - 1].second); 589 | if (m) 590 | { 591 | m(); 592 | } 593 | } 594 | } 595 | return 0; 596 | case ID_MASTER_BUTTONRELOAD: 597 | { 598 | if (SelectedIndex == -1) 599 | { 600 | MessageBoxA(m_hwnd, "请先单击左侧列表选择一个插件!", "CQXQ", MB_OK); 601 | return 0; 602 | } 603 | if (!EnabledEventCalled) 604 | { 605 | MessageBoxA(m_hwnd, "插件尚未初始化完毕,等待QQ登陆完成后插件进行初始化!", "CQXQ", MB_OK); 606 | return 0; 607 | } 608 | reloadOneCQPlugin(SelectedIndex); 609 | } 610 | return 0; 611 | case ID_MASTER_BUTTONRECVSELFMSG: 612 | { 613 | RecvSelfEvent = !RecvSelfEvent; 614 | ButtonSwitchRecvSelfMsg.SetText(RecvSelfEvent ? "停止接收来自自己的事件" : "开始接收来自自己的事件"); 615 | std::filesystem::path p(rootPath); 616 | p.append("CQPlugins").append(".cqxq_recv_self_event.enable"); 617 | if (RecvSelfEvent) 618 | { 619 | if (!std::filesystem::exists(p)) 620 | { 621 | ofstream fstream(p); // 创建文件 622 | fstream << "This file is used to enable CQXQ to receive message from the robot itself"; 623 | fstream.close(); 624 | } 625 | } 626 | else 627 | { 628 | if (std::filesystem::exists(p)) 629 | { 630 | std::filesystem::remove(p); 631 | } 632 | } 633 | } 634 | return 0; 635 | case ID_MASTER_BUTTONADDGROUP: 636 | { 637 | ShellExecuteA(nullptr, "open", "https://jq.qq.com/?_wv=1027&k=GoSXrbRc", nullptr, nullptr, SW_SHOWNORMAL); 638 | } 639 | return 0; 640 | case ID_MASTER_BUTTONGITHUB: 641 | { 642 | ShellExecuteA(nullptr, "open", "https://github.com/w4123/CQXQ", nullptr, nullptr, SW_SHOWNORMAL); 643 | } 644 | return 0; 645 | case ID_MASTER_BUTTONRELOADALL: 646 | { 647 | if (!EnabledEventCalled) 648 | { 649 | MessageBoxA(m_hwnd, "插件尚未初始化完毕,等待QQ登陆完成后插件进行初始化!", "CQXQ", MB_OK); 650 | return 0; 651 | } 652 | reloadAllCQPlugin(); 653 | } 654 | return 0; 655 | default: 656 | return DefWindowProc(m_hwnd, uMsg, wParam, lParam); 657 | } 658 | } 659 | case WM_NOTIFY: 660 | { 661 | switch (reinterpret_cast(lParam)->code) 662 | { 663 | case LVN_ITEMCHANGED: 664 | { 665 | if (reinterpret_cast(lParam)->idFrom == ID_MASTER_LVPLUGIN) 666 | { 667 | LPNMLISTVIEW pnmv = reinterpret_cast(lParam); 668 | if (pnmv->iItem != -1 && !(pnmv->uOldState & LVIS_SELECTED) && (pnmv->uNewState & LVIS_SELECTED)) 669 | { 670 | std::string text = ListViewPlugin.GetItemText(pnmv->iItem); 671 | SelectedIndex = std::stoi(text); 672 | StaticDesc.SetText(plugins[SelectedIndex].description); 673 | if (plugins[SelectedIndex].enabled) 674 | { 675 | ButtonEnable.SetText("停用"); 676 | } 677 | else 678 | { 679 | ButtonEnable.SetText("启用"); 680 | } 681 | } 682 | return 0; 683 | } 684 | } 685 | return 0; 686 | default: 687 | return DefWindowProc(m_hwnd, uMsg, wParam, lParam); 688 | } 689 | } 690 | default: 691 | return DefWindowProc(m_hwnd, uMsg, wParam, lParam); 692 | } 693 | } 694 | 695 | LRESULT GUI::CreateMainPage() 696 | { 697 | RECT rcClient; 698 | GetClientRect(m_hwnd, &rcClient); 699 | 700 | ButtonEnable.Create("启用", WS_CHILD | WS_VISIBLE, 0, 701 | 400, 280, 70, 30, m_hwnd, reinterpret_cast(ID_MASTER_BUTTONENABLE)); 702 | ButtonReload.Create("重载", WS_CHILD | WS_VISIBLE, 0, 703 | 500, 280, 70, 30, m_hwnd, reinterpret_cast(ID_MASTER_BUTTONRELOAD)); 704 | ButtonMenu.Create("菜单", WS_CHILD | WS_VISIBLE, 0, 705 | 600, 280, 70, 30, m_hwnd, reinterpret_cast(ID_MASTER_BUTTONMENU)); 706 | ButtonSwitchRecvSelfMsg.Create(RecvSelfEvent ? "停止接收来自自己的事件" : "开始接收来自自己的事件", WS_CHILD | WS_VISIBLE, 0, 707 | 400, 320, 270, 30, m_hwnd, reinterpret_cast(ID_MASTER_BUTTONRECVSELFMSG)); 708 | ButtonReloadAll.Create("全部重载", WS_CHILD | WS_VISIBLE, 0, 709 | 12, 430, 90, 30, m_hwnd, reinterpret_cast(ID_MASTER_BUTTONRELOADALL)); 710 | ButtonGitHub.Create("查看源码", WS_CHILD | WS_VISIBLE, 0, 711 | 144, 430, 90, 30, m_hwnd, reinterpret_cast(ID_MASTER_BUTTONGITHUB)); 712 | ButtonAddGroup.Create("加开发群", WS_CHILD | WS_VISIBLE, 0, 713 | 277, 430, 90, 30, m_hwnd, reinterpret_cast(ID_MASTER_BUTTONADDGROUP)); 714 | 715 | StaticDesc.Create("单击左侧列表选择一个插件", 716 | WS_CHILD | WS_VISIBLE, 0, 717 | 400, 30, 270, 200, m_hwnd, reinterpret_cast(ID_MASTER_STATICDESC)); 718 | 719 | ListViewPlugin.Create("", 720 | WS_CHILD | LVS_REPORT | WS_VISIBLE | WS_BORDER | LVS_SINGLESEL, 721 | 0, 722 | 12, 12, 723 | 355, 410, 724 | m_hwnd, 725 | reinterpret_cast(ID_MASTER_LVPLUGIN)); 726 | ListViewPlugin.SetExtendedListViewStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_AUTOSIZECOLUMNS | LVS_EX_FULLROWSELECT); 727 | 728 | ListViewPlugin.AddAllTextColumn(std::vector>{ {"ID", 50}, { "名称", 300 }, { "作者", 200 }, { "版本", 200 }}); 729 | int index = 0; 730 | for (const auto& item : plugins) 731 | { 732 | ListViewPlugin.AddTextRow({ std::to_string(item.second.id), item.second.name, item.second.author, item.second.version }, index); 733 | index++; 734 | } 735 | 736 | HFONT Yahei18 = Fonts["Yahei18"]; 737 | ButtonEnable.SetFont(Yahei18); 738 | ButtonReload.SetFont(Yahei18); 739 | ButtonMenu.SetFont(Yahei18); 740 | StaticDesc.SetFont(Yahei18); 741 | ButtonSwitchRecvSelfMsg.SetFont(Yahei18); 742 | ButtonReloadAll.SetFont(Yahei18); 743 | ButtonGitHub.SetFont(Yahei18); 744 | ButtonAddGroup.SetFont(Yahei18); 745 | return 0; 746 | } 747 | 748 | // GUI 749 | GUI MainWindow; 750 | 751 | int __stdcall InitGUI() 752 | { 753 | // hDllModule不应为空 754 | assert(hDllModule); 755 | 756 | // 初始化CommonControl 757 | INITCOMMONCONTROLSEX icex; // Structure for control initialization. 758 | icex.dwSize = sizeof(INITCOMMONCONTROLSEX); 759 | icex.dwICC = ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES; 760 | InitCommonControlsEx(&icex); 761 | 762 | if (!MainWindow.Create("CQXQ GUI", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_CLIPSIBLINGS, 0, 763 | CW_USEDEFAULT, CW_USEDEFAULT, 710, 500)) 764 | { 765 | return 0; 766 | } 767 | 768 | ShowWindow(MainWindow.Window(), SW_HIDE); 769 | return 0; 770 | } 771 | 772 | LRESULT GUI::UpdateGUI() 773 | { 774 | ListViewPlugin.DeleteAllItems(); 775 | int index = 0; 776 | for (const auto& item : plugins) 777 | { 778 | ListViewPlugin.AddTextRow({ std::to_string(item.second.id), item.second.name, item.second.author, item.second.version }, index); 779 | index++; 780 | } 781 | StaticDesc.SetText("单击左侧列表选择一个插件"); 782 | SelectedIndex = -1; 783 | ButtonEnable.SetText("启用"); 784 | return 0; 785 | } 786 | 787 | void __stdcall ShowMainWindow() 788 | { 789 | ShowWindowAsync(MainWindow.Window(), SW_SHOW); 790 | SetForegroundWindow(MainWindow.Window()); 791 | } 792 | 793 | void __stdcall DestroyMainWindow() 794 | { 795 | DestroyWindow(MainWindow.Window()); 796 | } 797 | 798 | void __stdcall UpdateMainWindow() 799 | { 800 | if (this_thread::get_id() == fakeMainThread.get_thread(0).get_id()) 801 | { 802 | MainWindow.UpdateGUI(); 803 | } 804 | else 805 | { 806 | fakeMainThread.push([](int) { MainWindow.UpdateGUI(); }).wait(); 807 | } 808 | } -------------------------------------------------------------------------------- /CQXQ/GUI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | int __stdcall InitGUI(); 3 | void __stdcall ShowMainWindow(); 4 | void __stdcall DestroyMainWindow(); 5 | void __stdcall UpdateMainWindow(); 6 | 7 | // GUI 8 | class GUI; 9 | extern GUI MainWindow; -------------------------------------------------------------------------------- /CQXQ/GlobalVar.cpp: -------------------------------------------------------------------------------- 1 | #include "GlobalVar.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "ctpl_stl.h" 8 | #include "GUI.h" 9 | 10 | HMODULE hDllModule; 11 | 12 | // 所有插件 13 | std::map plugins; 14 | 15 | // 排序后的所有插件事件 16 | std::map> plugins_events; 17 | 18 | // 下一个插件的ID 19 | int nextPluginId = 1; 20 | 21 | // XQ根目录, 结尾不带斜杠 22 | std::string rootPath; 23 | 24 | // 启用事件是否已经被调用,用于在QQ登陆成功以后再调用启用事件 25 | bool EnabledEventCalled = false; 26 | 27 | // 是否接收来自自己的事件 28 | bool RecvSelfEvent = false; 29 | 30 | // 是否在运行 31 | std::atomic running = false; 32 | 33 | // 伪主线程 34 | ctpl::thread_pool fakeMainThread(1); 35 | 36 | // API调用线程 37 | ctpl::thread_pool p(4); 38 | 39 | // 总内存释放线程 40 | std::unique_ptr memFreeThread; 41 | 42 | // 用于释放字符串内存 43 | std::priority_queue> memFreeQueue; 44 | 45 | std::mutex memFreeMutex; 46 | 47 | // 消息ID以及消息ID内存释放 48 | std::priority_queue> memFreeMsgIdQueue; 49 | 50 | std::mutex memFreeMsgIdMutex; 51 | 52 | std::map msgIdMap; 53 | 54 | std::atomic msgIdMapId = 1; 55 | 56 | size_t newMsgId(const FakeMsgId& msgId) 57 | { 58 | size_t id = msgIdMapId++; 59 | std::unique_lock lock(memFreeMsgIdMutex); 60 | msgIdMap[id] = msgId; 61 | memFreeMsgIdQueue.push(std::make_pair(time(nullptr), id)); 62 | return id; 63 | } 64 | 65 | // 是否已经初始化完毕 66 | std::atomic Init = false; 67 | 68 | // 复制字符串, 返回复制后的字符串指针,字符串内存5分钟后释放 69 | const char* delayMemFreeCStr(const std::string& str) 70 | { 71 | const char* s = _strdup(str.c_str()); 72 | { 73 | std::unique_lock lock(memFreeMutex); 74 | memFreeQueue.push({ time(nullptr), (void*)s }); 75 | } 76 | return s; 77 | } 78 | 79 | std::atomic robotQQ; 80 | 81 | unsigned char* AuthCode = nullptr; -------------------------------------------------------------------------------- /CQXQ/GlobalVar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "ctpl_stl.h" 8 | 9 | extern HMODULE hDllModule; 10 | 11 | struct eventType 12 | { 13 | int plugin_id = -1; 14 | int priority = 30000; 15 | FARPROC event = nullptr; 16 | bool operator<(const eventType& that) const 17 | { 18 | return this->priority < that.priority; 19 | } 20 | bool operator==(const eventType& that) const 21 | { 22 | return this->event == that.event; 23 | } 24 | }; 25 | 26 | struct native_plugin 27 | { 28 | int id = -1; 29 | std::string file; 30 | std::string newFile; 31 | std::string name; 32 | std::string version; 33 | int version_id = -1; 34 | std::string author; 35 | std::string description; 36 | std::map events; 37 | std::vector> menus; 38 | HMODULE dll = nullptr; 39 | bool enabled = false; 40 | 41 | native_plugin(int i, const std::string& f, const std::string& nf) 42 | { 43 | id = i; 44 | file = f; 45 | newFile = nf; 46 | dll = nullptr; 47 | enabled = true; 48 | } 49 | 50 | native_plugin() = default; 51 | ~native_plugin() = default; 52 | }; 53 | 54 | // 存XQ消息ID 55 | struct FakeMsgId 56 | { 57 | int type; //1好友, 2群聊, 4群临时会话 58 | long long sourceId; // 参考来源:群/讨论组号, 好友为-1 59 | long long QQ; // 参考来源: QQ号, 非私聊消息为-1 60 | long long msgNum; 61 | long long msgId; 62 | long long msgTime; // 群消息其实不需要这个 63 | }; 64 | 65 | // 存储所有插件 66 | extern std::map plugins; 67 | 68 | // 存储排序后的所有插件事件 69 | extern std::map> plugins_events; 70 | 71 | // 下一个插件的id 72 | extern int nextPluginId; 73 | 74 | // XQ根目录, 结尾不带斜杠 75 | extern std::string rootPath; 76 | 77 | // 启用事件是否已经被调用,用于在QQ登陆成功以后再调用启用事件 78 | extern bool EnabledEventCalled; 79 | 80 | // 是否接收来自自己的事件 81 | extern bool RecvSelfEvent; 82 | 83 | // 是否在运行 84 | extern std::atomic running; 85 | 86 | // 伪主线程 87 | extern ctpl::thread_pool fakeMainThread; 88 | 89 | // API调用线程 90 | extern ctpl::thread_pool p; 91 | 92 | // 总内存释放线程 93 | extern std::unique_ptr memFreeThread; 94 | 95 | // 用于释放字符串内存 96 | extern std::priority_queue> memFreeQueue; 97 | 98 | extern std::mutex memFreeMutex; 99 | 100 | // 消息ID以及消息ID内存释放 101 | extern std::priority_queue> memFreeMsgIdQueue; 102 | 103 | extern std::mutex memFreeMsgIdMutex; 104 | 105 | extern std::map msgIdMap; 106 | 107 | extern std::atomic msgIdMapId; 108 | 109 | size_t newMsgId(const FakeMsgId& msgId); 110 | 111 | // 是否已经初始化完毕 112 | extern std::atomic Init; 113 | 114 | // 复制字符串, 返回复制后的字符串指针,字符串内存5分钟后释放 115 | const char* delayMemFreeCStr(const std::string& str); 116 | 117 | extern std::atomic robotQQ; 118 | 119 | extern unsigned char* AuthCode; -------------------------------------------------------------------------------- /CQXQ/Resource.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "winres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // 中文(简体,中国) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) 19 | LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED 20 | #pragma code_page(936) 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // CQP 51 | // 52 | 53 | IDR_CQP1 CQP "CQP.dll" 54 | 55 | #endif // 中文(简体,中国) resources 56 | ///////////////////////////////////////////////////////////////////////////// 57 | 58 | 59 | 60 | #ifndef APSTUDIO_INVOKED 61 | ///////////////////////////////////////////////////////////////////////////// 62 | // 63 | // Generated from the TEXTINCLUDE 3 resource. 64 | // 65 | 66 | 67 | ///////////////////////////////////////////////////////////////////////////// 68 | #endif // not APSTUDIO_INVOKED 69 | 70 | -------------------------------------------------------------------------------- /CQXQ/RichMessage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RichMessage.h" 3 | using namespace std; 4 | 5 | string constructXMLShareMsg(const string& url, 6 | const string& title, 7 | const string& content, 8 | const string& picUrl) 9 | { 10 | std::string ret = R"()"; 13 | ret += R"()"; 14 | if (!picUrl.empty()) 15 | { 16 | ret += R"()"; 17 | } 18 | if (!title.empty()) 19 | { 20 | ret += R"()" + title + R"()"; 21 | } 22 | if (!content.empty()) 23 | { 24 | ret += R"()" + content + R"()"; 25 | } 26 | ret += R"()"; 27 | return ret; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /CQXQ/RichMessage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | std::string constructXMLShareMsg(const std::string& url, 5 | const std::string& title, 6 | const std::string& content, 7 | const std::string& picUrl); -------------------------------------------------------------------------------- /CQXQ/Unpack.cpp: -------------------------------------------------------------------------------- 1 | #include "Unpack.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | Unpack& Unpack::setData(const char* i, const int len) 11 | { 12 | buff.assign(i, i + len); 13 | return *this; 14 | } 15 | 16 | Unpack::Unpack() noexcept = default; 17 | 18 | Unpack::Unpack(const char* data) 19 | { 20 | setData(data, strlen(data)); 21 | } 22 | 23 | Unpack::Unpack(std::vector data) noexcept : buff(std::move(data)) 24 | { 25 | } 26 | 27 | Unpack::Unpack(const std::string& data) 28 | { 29 | setData(data.data(), data.size()); 30 | } 31 | 32 | Unpack& Unpack::clear() noexcept 33 | { 34 | buff.clear(); 35 | return *this; 36 | } 37 | 38 | int Unpack::len() const noexcept 39 | { 40 | return buff.size(); 41 | } 42 | 43 | Unpack& Unpack::add(int i) 44 | { 45 | const auto t = toBin(i); 46 | buff.insert(buff.end(), t, t + sizeof(int)); 47 | return *this; 48 | } 49 | 50 | int Unpack::getInt() noexcept 51 | { 52 | const auto len = sizeof(int); 53 | if (buff.size() < len)return 0; 54 | 55 | const auto ret = *reinterpret_cast(Flip(&(buff[0]), len)); 56 | buff.erase(buff.begin(), buff.begin() + len); 57 | return ret; 58 | } 59 | 60 | Unpack& Unpack::add(long long i) 61 | { 62 | const auto t = toBin(i); 63 | buff.insert(buff.end(), t, t + sizeof(long long)); 64 | return *this; 65 | } 66 | 67 | long long Unpack::getLong() noexcept 68 | { 69 | const auto len = sizeof(long long); 70 | if (buff.size() < len)return 0; 71 | 72 | const auto ret = *reinterpret_cast(Flip(&(buff[0]), len)); 73 | buff.erase(buff.begin(), buff.begin() + len); 74 | return ret; 75 | } 76 | 77 | Unpack& Unpack::add(short i) 78 | { 79 | const auto t = toBin(i); 80 | buff.insert(buff.end(), t, t + sizeof(short)); 81 | return *this; 82 | } 83 | 84 | short Unpack::getshort() noexcept 85 | { 86 | const auto len = sizeof(short); 87 | if (buff.size() < len)return 0; 88 | 89 | const auto ret = *reinterpret_cast(Flip(&(buff[0]), len)); 90 | buff.erase(buff.begin(), buff.begin() + len); 91 | return ret; 92 | } 93 | 94 | Unpack& Unpack::add(const unsigned char* i, const short len) 95 | { 96 | if (len < 0) 97 | return *this; 98 | add(len); 99 | if (len) 100 | buff.insert(buff.end(), i, i + len); 101 | return *this; 102 | } 103 | 104 | std::vector Unpack::getchars() noexcept 105 | { 106 | const auto len = getshort(); 107 | if (buff.size() < static_cast(len))return vector(); 108 | 109 | auto tep = vector(buff.begin(), buff.begin() + len); 110 | buff.erase(buff.begin(), buff.begin() + len); 111 | return tep; 112 | } 113 | 114 | Unpack& Unpack::add(string i) 115 | { 116 | if (i.empty()) //字符串长度为0,直接放入长度0 117 | { 118 | add(static_cast(0)); 119 | return *this; 120 | } 121 | if (i.size() > 32767) //字符串长度超出限制, 122 | { 123 | i = i.substr(0, 32767); 124 | } 125 | 126 | add(reinterpret_cast(i.data()), static_cast(i.size())); 127 | 128 | return *this; 129 | } 130 | 131 | string Unpack::getstring() 132 | { 133 | auto tep = getchars(); 134 | if (tep.empty())return ""; 135 | 136 | tep.push_back(static_cast(0)); 137 | return string(reinterpret_cast(&tep[0])); 138 | } 139 | 140 | Unpack& Unpack::add(Unpack& i) 141 | { 142 | add(i.getAll()); 143 | return *this; 144 | } 145 | 146 | Unpack Unpack::getUnpack() noexcept { return Unpack(getchars()); } 147 | 148 | std::string Unpack::getAll() noexcept 149 | { 150 | string ret(buff.begin(), buff.end()); 151 | return ret; 152 | } 153 | 154 | void Unpack::show() 155 | { 156 | string out; 157 | auto len = 0; 158 | for (auto c : buff) 159 | { 160 | out.append(to_string(static_cast(c))).append(", "); 161 | ++len; 162 | } 163 | out = to_string(len).append("{").append(out.substr(0, out.size() - 2)).append("}"); 164 | cout << out.data() << endl; 165 | } 166 | -------------------------------------------------------------------------------- /CQXQ/Unpack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | //打印内存数据 7 | static void show(void* t, const int len) 8 | { 9 | auto* const p = static_cast(t); 10 | std::cout << "{"; 11 | for (auto i = 0; i < len; ++i) 12 | { 13 | std::cout << static_cast(p[i]); 14 | if (i != len - 1)std::cout << ", "; 15 | } 16 | std::cout << "}" << std::endl; 17 | } 18 | 19 | //内存翻转 20 | static unsigned char* Flip(unsigned char* const str, int len) noexcept 21 | { 22 | auto f = 0; 23 | --len; 24 | while (f < len) 25 | { 26 | const auto p = str[len]; 27 | str[len] = str[f]; 28 | str[f] = p; 29 | ++f; 30 | --len; 31 | } 32 | return str; 33 | } 34 | 35 | //到字节集... 36 | //在原有的数据基础上操作 37 | template 38 | unsigned char* toBin(ClassType& i) noexcept 39 | { 40 | return Flip(reinterpret_cast(&i), sizeof(ClassType)); 41 | } 42 | 43 | 44 | class Unpack final 45 | { 46 | std::vector buff; 47 | public: 48 | Unpack() noexcept; 49 | explicit Unpack(const char*); 50 | explicit Unpack(std::vector) noexcept; 51 | explicit Unpack(const std::string&); 52 | 53 | Unpack& setData(const char* i, int len); 54 | Unpack& clear() noexcept; 55 | [[nodiscard]] int len() const noexcept; 56 | 57 | Unpack& add(int i); //添加一个整数 58 | int getInt() noexcept; //弹出一个整数 59 | 60 | Unpack& add(long long i); //添加一个长整数 61 | long long getLong() noexcept; //弹出一个长整数 62 | 63 | Unpack& add(short i); //添加一个短整数 64 | short getshort() noexcept; //弹出一个短整数 65 | 66 | Unpack& add(const unsigned char* i, short len); //添加一个字节集(请用add(std::string i);) 67 | std::vector getchars() noexcept; //弹出一个字节集(请用getstring();) 68 | 69 | Unpack& add(std::string i); //添加一个字符串 70 | std::string getstring(); //弹出一个字符串 71 | 72 | Unpack& add(Unpack& i); //添加一个Unpack 73 | Unpack getUnpack() noexcept; //弹出一个Unpack 74 | 75 | std::string getAll() noexcept; //返回本包数据 76 | 77 | void show(); 78 | }; 79 | -------------------------------------------------------------------------------- /CQXQ/XQAPI.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "ErrorHandler.h" 8 | #include "GlobalVar.h" 9 | 10 | using namespace std; 11 | 12 | namespace XQAPI 13 | { 14 | 15 | #ifdef XQAPI_IMPLEMENTATION 16 | static vector> apiFuncInitializers; 17 | 18 | static bool addFuncInit(const function& initializer) { 19 | apiFuncInitializers.push_back(initializer); 20 | return true; 21 | } 22 | 23 | void initFuncs(const HMODULE& hModule) 24 | { 25 | __try 26 | { 27 | for (const auto& initializer : apiFuncInitializers) { 28 | initializer(hModule); 29 | } 30 | apiFuncInitializers.clear(); 31 | } 32 | __except (CQXQUnhandledExceptionFilter(GetExceptionInformation())) 33 | { 34 | ; 35 | } 36 | } 37 | 38 | #define XQAPI(Name, ReturnType, ...) using Name##_FUNC = std::function; \ 39 | Name##_FUNC _##Name; \ 40 | using Name##_TYPE = ReturnType (__stdcall*)(unsigned char *, __VA_ARGS__); \ 41 | template \ 42 | ReturnType Name(Args&&... args) \ 43 | { \ 44 | if constexpr (std::is_same_v) \ 45 | { \ 46 | const char* ret = p.push([&args...](int iThread){ return _##Name(AuthCode, std::forward(args)...); }).get(); \ 47 | if (!ret) return ""; \ 48 | size_t size = *reinterpret_cast(const_cast(ret)); \ 49 | std::string retStr(ret + 4, size); \ 50 | HeapFree(GetProcessHeap(), 0, reinterpret_cast(const_cast(ret))); \ 51 | return delayMemFreeCStr(retStr); \ 52 | } \ 53 | else \ 54 | { \ 55 | return p.push([&args...](int iThread){ return _##Name(AuthCode, std::forward(args)...); }).get(); \ 56 | } \ 57 | } \ 58 | static bool _init_##Name = addFuncInit( [] (const auto& hModule) -> void { \ 59 | _##Name = reinterpret_cast(GetProcAddress(hModule, "S3_Api_" #Name)); \ 60 | if (!_##Name) throw std::runtime_error("Unable to initialize API Function " #Name); \ 61 | }); 62 | 63 | #else 64 | void initFuncs(const HMODULE& hModule); 65 | #define XQAPI(Name, ReturnType, ...) template \ 66 | ReturnType Name (Args&&... args); \ 67 | 68 | #endif 69 | 70 | XQAPI(SendMsg, void, const char* botQQ, int32_t msgType, const char* groupId, const char* QQ, const char* content, int32_t bubbleId) 71 | 72 | XQAPI(SendMsgEX, void, const char* botQQ, int32_t msgType, const char* groupId, const char* QQ, const char* content, int32_t bubbleId, BOOL isAnon) 73 | 74 | #ifdef ASYNC_MSG 75 | 76 | #ifdef XQAPI_IMPLEMENTATION 77 | const char* SendMsgEX_V2(const char* botQQ, int32_t msgType, const char* groupId, const char* QQ, const char* content, int32_t bubbleId, BOOL isAnon, const char* json) 78 | { 79 | SendMsgEX(botQQ, msgType, groupId, QQ, content, bubbleId, isAnon); 80 | return "FORCESUC"; 81 | } 82 | #else 83 | const char* SendMsgEX_V2(const char* botQQ, int32_t msgType, const char* groupId, const char* QQ, const char* content, int32_t bubbleId, BOOL isAnon, const char* json); 84 | #endif 85 | 86 | #else 87 | 88 | #ifdef XQAPI_IMPLEMENTATION 89 | // 特殊 -> 单独创建线程 90 | using SendMsgEX_V2_FUNC = std::function; \ 91 | SendMsgEX_V2_FUNC _SendMsgEX_V2; 92 | using SendMsgEX_V2_TYPE = const char*(__stdcall*)(unsigned char*, const char* botQQ, int32_t msgType, const char* groupId, const char* QQ, const char* content, int32_t bubbleId, BOOL isAnon, const char* json); \ 93 | struct SendMsgEX_V2_Struct 94 | { 95 | const char* botQQ; 96 | int32_t msgType; 97 | const char* groupId; 98 | const char* QQ; 99 | const char* content; 100 | int32_t bubbleId; 101 | BOOL isAnon; 102 | const char* json; 103 | const char* ret; 104 | }; 105 | DWORD WINAPI SendMsgEX_V2_ThreadProc( 106 | _In_ LPVOID lpParameter 107 | ) 108 | { 109 | SendMsgEX_V2_Struct* para = (SendMsgEX_V2_Struct*)lpParameter; 110 | para->ret = _SendMsgEX_V2(AuthCode, para->botQQ, para->msgType, para->groupId, para->QQ, para->content, para->bubbleId, para->isAnon, para->json); 111 | return 0; 112 | } 113 | template 114 | const char* SendMsgEX_V2(Args&&... args) 115 | { 116 | SendMsgEX_V2_Struct para = { args..., nullptr }; 117 | HANDLE t = CreateThread(nullptr, 0, SendMsgEX_V2_ThreadProc, (void*)(¶), 0, nullptr); 118 | // 保险措施 -> 30秒后强制终止 119 | if (WaitForSingleObject(t, 30000) == WAIT_OBJECT_0) 120 | { 121 | CloseHandle(t); 122 | if (!para.ret) return ""; 123 | size_t size = *reinterpret_cast(const_cast(para.ret)); 124 | std::string retStr(para.ret + 4, size); 125 | HeapFree(GetProcessHeap(), 0, reinterpret_cast(const_cast(para.ret))); 126 | return delayMemFreeCStr(retStr); 127 | } 128 | else 129 | { 130 | TerminateThread(t, 0); 131 | CloseHandle(t); 132 | return ""; 133 | } 134 | } 135 | static bool _init_SendMsgEX_V2 = addFuncInit( [] (const auto& hModule) { 136 | _SendMsgEX_V2 = reinterpret_cast(GetProcAddress(hModule, "S3_Api_SendMsgEX_V2")); 137 | if (!_SendMsgEX_V2) throw std::exception("Unable to initialize API Function SendMsgEX_V2"); 138 | }); 139 | #else 140 | template 141 | const char* SendMsgEX_V2(Args&&... args); 142 | #endif 143 | 144 | #endif 145 | 146 | XQAPI(OutPutLog, void, const char* content) 147 | 148 | XQAPI(GetNick, const char*, const char* botQQ, const char* QQ) 149 | 150 | XQAPI(GetGroupAdmin, const char*, const char* botQQ, const char* groupId) 151 | 152 | XQAPI(GetGroupCard, const char*, const char* botQQ, const char* groupId, const char* QQ) 153 | 154 | XQAPI(GetGroupList, const char*, const char* botQQ) 155 | 156 | XQAPI(GetGroupList_B, const char*, const char* botQQ) 157 | 158 | XQAPI(GetGroupName, const char*, const char* botQQ, const char* groupId) 159 | 160 | XQAPI(GetFriendList, const char*, const char* botQQ) 161 | 162 | XQAPI(GetFriendList_B, const char*, const char* botQQ) 163 | 164 | XQAPI(GetFriendsRemark, const char*, const char* botQQ, const char* QQ) 165 | 166 | XQAPI(GetGroupMemberList, const char*, const char* botQQ, const char* groupId) 167 | 168 | XQAPI(GetGroupMemberList_B, const char*, const char* botQQ, const char* groupId) 169 | 170 | XQAPI(GetGroupMemberList_C, const char*, const char* botQQ, const char* groupId) 171 | 172 | XQAPI(HandleGroupEvent, void, const char* botQQ, int32_t reqType, const char* QQ, const char* groupId, const char* seq, int32_t rspType, const char* msg) 173 | 174 | XQAPI(HandleFriendEvent, void, const char* botQQ, const char* QQ, int32_t rspType, const char* msg) 175 | 176 | XQAPI(QuitGroup, void, const char* botQQ, const char* groupId) 177 | 178 | XQAPI(GetGroupMemberNum, const char*, const char* botQQ, const char* groupId) 179 | 180 | XQAPI(SetAnon, BOOL, const char* botQQ, const char* groupId, BOOL enable) 181 | 182 | XQAPI(ShutUP, void, const char* botQQ, const char* groupId, const char* QQ, int32_t duration) 183 | 184 | XQAPI(SetGroupCard, BOOL, const char* botQQ, const char* groupId, const char* QQ, const char* card) 185 | 186 | XQAPI(KickGroupMBR, void, const char* botQQ, const char* groupId, const char* QQ, BOOL refuseForever) 187 | 188 | XQAPI(UpVote, const char*, const char* botQQ, const char* QQ) 189 | 190 | XQAPI(IsEnable, BOOL) 191 | 192 | XQAPI(GetOnLineList, const char*) 193 | 194 | XQAPI(UpLoadPic, const char*, const char* botQQ, int32_t uploadType, const char* targetId, const char* image) 195 | 196 | XQAPI(IfFriend, BOOL, const char* botQQ, const char* QQ) 197 | 198 | XQAPI(GetCookies, const char*, const char* botQQ) 199 | 200 | XQAPI(GetBkn, const char*, const char* botQQ) 201 | 202 | XQAPI(GetVoiLink, const char*, const char* botQQ, const char* GUID) 203 | 204 | XQAPI(WithdrawMsg, const char*, const char* botQQ, const char* groupId, const char* msgNum, const char* msgId) 205 | 206 | XQAPI(WithdrawMsgEX, const char*, const char* botQQ, int32_t type, const char* sourceId, const char* QQ, const char* msgNum, const char* msgId, const char* msgTime) 207 | 208 | XQAPI(GetPicLink, const char*, const char* botQQ, int32_t picType, const char* sourceId, const char* GUID) 209 | 210 | XQAPI(SendXML, void, const char* botQQ, int32_t sendType, int32_t msgType, const char* groupId, const char* QQ, const char* objectMsg, int32_t subType) 211 | 212 | XQAPI(ShakeWindow, BOOL, const char* botQQ, const char* QQ) 213 | 214 | XQAPI(GetAnon, BOOL, const char* botQQ, const char* groupId) 215 | 216 | XQAPI(IsShutUp, BOOL, const char* botQQ, const char* groupId, const char* QQ) 217 | 218 | //XQAPI(GetAge, int32_t, const char* botQQ, const char* QQ) 219 | 220 | //XQAPI(GetGender, int32_t, const char* botQQ, const char* QQ) 221 | #undef XQAPI 222 | } 223 | -------------------------------------------------------------------------------- /CQXQ/ctpl_stl.h: -------------------------------------------------------------------------------- 1 | /********************************************************* 2 | * 3 | * Copyright (C) 2014 by Vitaliy Vitsentiy 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *********************************************************/ 18 | 19 | 20 | #ifndef __ctpl_stl_thread_pool_H__ 21 | #define __ctpl_stl_thread_pool_H__ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | 35 | // thread pool to run user's functors with signature 36 | // ret func(int id, other_params) 37 | // where id is the index of the thread that runs the functor 38 | // ret is some return type 39 | 40 | 41 | namespace ctpl { 42 | 43 | namespace detail { 44 | template 45 | class Queue { 46 | public: 47 | bool push(T const & value) { 48 | std::unique_lock lock(this->mutex); 49 | this->q.push(value); 50 | return true; 51 | } 52 | // deletes the retrieved element, do not use for non integral types 53 | bool pop(T & v) { 54 | std::unique_lock lock(this->mutex); 55 | if (this->q.empty()) 56 | return false; 57 | v = this->q.front(); 58 | this->q.pop(); 59 | return true; 60 | } 61 | bool empty() { 62 | std::unique_lock lock(this->mutex); 63 | return this->q.empty(); 64 | } 65 | private: 66 | std::queue q; 67 | std::mutex mutex; 68 | }; 69 | } 70 | 71 | class thread_pool { 72 | 73 | public: 74 | 75 | thread_pool() { this->init(); } 76 | thread_pool(int nThreads) { this->init(); this->resize(nThreads); } 77 | 78 | // the destructor waits for all the functions in the queue to be finished 79 | ~thread_pool() { 80 | this->stop(true); 81 | } 82 | 83 | // get the number of running threads in the pool 84 | int size() { return static_cast(this->threads.size()); } 85 | 86 | // number of idle threads 87 | int n_idle() { return this->nWaiting; } 88 | std::thread & get_thread(int i) { return *this->threads[i]; } 89 | 90 | // change the number of threads in the pool 91 | // should be called from one thread, otherwise be careful to not interleave, also with this->stop() 92 | // nThreads must be >= 0 93 | void resize(int nThreads) { 94 | if (!this->isStop && !this->isDone) { 95 | int oldNThreads = static_cast(this->threads.size()); 96 | if (oldNThreads <= nThreads) { // if the number of threads is increased 97 | this->threads.resize(nThreads); 98 | this->flags.resize(nThreads); 99 | 100 | for (int i = oldNThreads; i < nThreads; ++i) { 101 | this->flags[i] = std::make_shared>(false); 102 | this->set_thread(i); 103 | } 104 | } 105 | else { // the number of threads is decreased 106 | for (int i = oldNThreads - 1; i >= nThreads; --i) { 107 | *this->flags[i] = true; // this thread will finish 108 | this->threads[i]->detach(); 109 | } 110 | { 111 | // stop the detached threads that were waiting 112 | std::unique_lock lock(this->mutex); 113 | this->cv.notify_all(); 114 | } 115 | this->threads.resize(nThreads); // safe to delete because the threads are detached 116 | this->flags.resize(nThreads); // safe to delete because the threads have copies of shared_ptr of the flags, not originals 117 | } 118 | } 119 | } 120 | 121 | // empty the queue 122 | void clear_queue() { 123 | std::function * _f; 124 | while (this->q.pop(_f)) 125 | delete _f; // empty the queue 126 | } 127 | 128 | // pops a functional wrapper to the original function 129 | std::function pop() { 130 | std::function * _f = nullptr; 131 | this->q.pop(_f); 132 | std::unique_ptr> func(_f); // at return, delete the function even if an exception occurred 133 | std::function f; 134 | if (_f) 135 | f = *_f; 136 | return f; 137 | } 138 | 139 | // wait for all computing threads to finish and stop all threads 140 | // may be called asynchronously to not pause the calling thread while waiting 141 | // if isWait == true, all the functions in the queue are run, otherwise the queue is cleared without running the functions 142 | void stop(bool isWait = false) { 143 | if (!isWait) { 144 | if (this->isStop) 145 | return; 146 | this->isStop = true; 147 | for (int i = 0, n = this->size(); i < n; ++i) { 148 | *this->flags[i] = true; // command the threads to stop 149 | } 150 | this->clear_queue(); // empty the queue 151 | } 152 | else { 153 | if (this->isDone || this->isStop) 154 | return; 155 | this->isDone = true; // give the waiting threads a command to finish 156 | } 157 | { 158 | std::unique_lock lock(this->mutex); 159 | this->cv.notify_all(); // stop all waiting threads 160 | } 161 | for (int i = 0; i < static_cast(this->threads.size()); ++i) { // wait for the computing threads to finish 162 | if (this->threads[i]->joinable()) 163 | this->threads[i]->join(); 164 | } 165 | // if there were no threads in the pool but some functors in the queue, the functors are not deleted by the threads 166 | // therefore delete them here 167 | this->clear_queue(); 168 | this->threads.clear(); 169 | this->flags.clear(); 170 | } 171 | 172 | template 173 | auto push(F && f, Rest&&... rest) ->std::future { 174 | auto pck = std::make_shared>( 175 | std::bind(std::forward(f), std::placeholders::_1, std::forward(rest)...) 176 | ); 177 | auto _f = new std::function([pck](int id) { 178 | (*pck)(id); 179 | }); 180 | this->q.push(_f); 181 | std::unique_lock lock(this->mutex); 182 | this->cv.notify_one(); 183 | return pck->get_future(); 184 | } 185 | 186 | // run the user's function that excepts argument int - id of the running thread. returned value is templatized 187 | // operator returns std::future, where the user can get the result and rethrow the catched exceptins 188 | template 189 | auto push(F && f) ->std::future { 190 | auto pck = std::make_shared>(std::forward(f)); 191 | auto _f = new std::function([pck](int id) { 192 | (*pck)(id); 193 | }); 194 | this->q.push(_f); 195 | std::unique_lock lock(this->mutex); 196 | this->cv.notify_one(); 197 | return pck->get_future(); 198 | } 199 | 200 | 201 | private: 202 | 203 | // deleted 204 | thread_pool(const thread_pool &);// = delete; 205 | thread_pool(thread_pool &&);// = delete; 206 | thread_pool & operator=(const thread_pool &);// = delete; 207 | thread_pool & operator=(thread_pool &&);// = delete; 208 | 209 | void set_thread(int i) { 210 | std::shared_ptr> flag(this->flags[i]); // a copy of the shared ptr to the flag 211 | auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() { 212 | std::atomic & _flag = *flag; 213 | std::function * _f; 214 | bool isPop = this->q.pop(_f); 215 | while (true) { 216 | while (isPop) { // if there is anything in the queue 217 | std::unique_ptr> func(_f); // at return, delete the function even if an exception occurred 218 | (*_f)(i); 219 | if (_flag) 220 | return; // the thread is wanted to stop, return even if the queue is not empty yet 221 | else 222 | isPop = this->q.pop(_f); 223 | } 224 | // the queue is empty here, wait for the next command 225 | std::unique_lock lock(this->mutex); 226 | ++this->nWaiting; 227 | this->cv.wait(lock, [this, &_f, &isPop, &_flag](){ isPop = this->q.pop(_f); return isPop || this->isDone || _flag; }); 228 | --this->nWaiting; 229 | if (!isPop) 230 | return; // if the queue is empty and this->isDone == true or *flag then return 231 | } 232 | }; 233 | this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique() 234 | } 235 | 236 | void init() { this->nWaiting = 0; this->isStop = false; this->isDone = false; } 237 | 238 | std::vector> threads; 239 | std::vector>> flags; 240 | detail::Queue *> q; 241 | std::atomic isDone; 242 | std::atomic isStop; 243 | std::atomic nWaiting; // how many threads are waiting 244 | 245 | std::mutex mutex; 246 | std::condition_variable cv; 247 | }; 248 | 249 | } 250 | 251 | #endif // __ctpl_stl_thread_pool_H__ 252 | -------------------------------------------------------------------------------- /CQXQ/native.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "GlobalVar.h" 13 | #include "native.h" 14 | #include "CQTools.h" 15 | #include "nlohmann/json.hpp" 16 | #include "ctpl_stl.h" 17 | #include "Unpack.h" 18 | #include "GlobalVar.h" 19 | #include "GUI.h" 20 | #include "resource.h" 21 | #include "EncodingConvert.h" 22 | #include 23 | #include "RichMessage.h" 24 | #include "ErrorHandler.h" 25 | #include "CQPluginLoader.h" 26 | #include 27 | #include 28 | #include 29 | 30 | #pragma comment(lib, "urlmon.lib") 31 | 32 | // 包含一次实现 33 | #define XQAPI_IMPLEMENTATION 34 | #include "XQAPI.h" 35 | 36 | using namespace std; 37 | 38 | #define XQ 39 | 40 | class Cominit 41 | { 42 | public: 43 | HRESULT hr; 44 | Cominit() 45 | { 46 | hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 47 | } 48 | ~Cominit() 49 | { 50 | if (SUCCEEDED(hr)) CoUninitialize(); 51 | } 52 | }; 53 | 54 | HMODULE XQHModule = nullptr; 55 | HMODULE CQPHModule = nullptr; 56 | 57 | // 获取CQ码某部分的信息 58 | std::string retrieveSectionData(const std::string& CQCode, const std::string& section) 59 | { 60 | string ret; 61 | std::string sectionFull = section + "="; 62 | size_t Loc = CQCode.find(sectionFull); 63 | if (Loc != string::npos) 64 | { 65 | ret = CQCode.substr(Loc + sectionFull.size(), CQCode.find(',', Loc + sectionFull.size()) - Loc - sectionFull.size()); 66 | } 67 | return ret; 68 | } 69 | 70 | 71 | // XQ码到CQ码 72 | std::string parseToCQCode(const char* msg) 73 | { 74 | if (!msg) return ""; 75 | std::string_view msgStr(msg); 76 | std::string ret; 77 | size_t l = 0, r = 0, last = 0; 78 | l = msgStr.find("["); 79 | r = msgStr.find("]", l); 80 | while (l != string::npos && r != string::npos) 81 | { 82 | ret += msgStr.substr(last, l - last); 83 | if (msgStr.substr(l, 2) == "[@") 84 | { 85 | ret += "[CQ:at,qq="; 86 | ret += msgStr.substr(l + 2, r - l - 2); 87 | ret += "]"; 88 | } 89 | else if (msgStr.substr(l, 5) == "[pic=") 90 | { 91 | size_t commaLoc = msgStr.find(',', l); 92 | ret += "[CQ:image,file="; 93 | if (commaLoc < r) 94 | { 95 | ret += msgStr.substr(l + 5, commaLoc - l - 5); 96 | } 97 | else 98 | { 99 | ret += msgStr.substr(l + 5, r - l - 5); 100 | } 101 | ret += "]"; 102 | } 103 | else if (msgStr.substr(l, 5) == "[Voi=") 104 | { 105 | ret += "[CQ:record,file="; 106 | ret += msgStr.substr(l + 5, r - l - 5); 107 | ret += "]"; 108 | } 109 | else if (msgStr.substr(l, 5) == "[Face") 110 | { 111 | ret += "[CQ:face,id="; 112 | ret += msgStr.substr(l + 5, r - l - 5 - 4); 113 | ret += "]"; 114 | } 115 | else 116 | { 117 | ret += msgStr.substr(l, r - l + 1); 118 | } 119 | last = r + 1; 120 | l = msgStr.find("[", r); 121 | r = msgStr.find(']', l); 122 | } 123 | ret += msgStr.substr(last); 124 | return ret; 125 | } 126 | 127 | 128 | // CQ码到XQ码 129 | std::string parseCQCodeAndSend(int32_t msgType, const char* targetId, const char* QQ, const std::string& msg, int32_t bubbleID, BOOL isAnon, BOOL AnonIgnore, const char* json) 130 | { 131 | if (msg.empty()) return ""; 132 | std::string_view msgStr(msg); 133 | std::string ret; 134 | size_t l = 0, r = 0, last = 0; 135 | l = msgStr.find("[CQ"); 136 | r = msgStr.find("]", l); 137 | while (l != string::npos && r != string::npos) 138 | { 139 | ret += msgStr.substr(last, l - last); 140 | if (msgStr.substr(l, 10) == "[CQ:at,qq=") 141 | { 142 | ret += "[@"; 143 | ret += msgStr.substr(l + 10, r - l - 10); 144 | ret += "]"; 145 | } 146 | else if (msgStr.substr(l, 10) == "[CQ:image,") 147 | { 148 | 149 | std::string imageStr; 150 | imageStr += msgStr.substr(l + 10, r - l - 10); 151 | // 现在的状态是file=xxx.jpg/png/gif/...(,cache=xxx) 152 | std::string fileStr; 153 | fileStr = retrieveSectionData(imageStr, "file"); 154 | if (fileStr.empty()) 155 | { 156 | fileStr = retrieveSectionData(imageStr, "url"); 157 | } 158 | 159 | // 判断是已有图片,本地图片,网络URL还是Base64 160 | if (!fileStr.empty()) 161 | { 162 | // 已有图片 163 | if (fileStr[0] == '{') 164 | { 165 | regex groupPic("\\{([0-9A-Fa-f]{8})[-]([0-9A-Fa-f]{4})[-]([0-9A-Fa-f]{4})[-]([0-9A-Fa-f]{4})[-]([0-9A-Fa-f]{12})\\}\\.(jpg|png|gif|bmp|jpeg).*", regex::ECMAScript | regex::icase); 166 | regex privatePic("\\{[0-9]{5,15}[-][0-9]{5,15}[-]([0-9A-Fa-f]{32})\\}\\.(jpg|png|gif|bmp|jpeg).*", regex::ECMAScript | regex::icase); 167 | smatch m; 168 | // 转换群聊和好友图片 169 | if ((msgType == 1 || msgType == 4 || msgType == 5) && regex_match(fileStr, m, groupPic)) 170 | { 171 | fileStr = "{"s + to_string(robotQQ).c_str() + "-" + "1234567879" + "-" + m[1].str() + m[2].str() + m[3].str() + m[4].str() + m[5].str() + "}" + "." + m[6].str(); 172 | } 173 | else if ((msgType == 2 || msgType == 3)&& regex_match(fileStr, m, privatePic)) 174 | { 175 | std::string guid = m[1].str(); 176 | fileStr = "{"s + guid.substr(0, 8) + "-" + guid.substr(8, 4) + "-" + guid.substr(12, 4) + "-" + guid.substr(16, 4) + "-" + guid.substr(20) + "}" + "." + m[2].str(); 177 | } 178 | ret += "[pic="; 179 | ret += fileStr; 180 | ret += "]"; 181 | } 182 | else if (fileStr.substr(0, 4) == "http" || fileStr.substr(0, 3) == "ftp" || fileStr.substr(0, 2) == "ww") 183 | { 184 | ret += "[pic="; 185 | ret += fileStr; 186 | ret += "]"; 187 | } 188 | else if (fileStr.substr(0, 6) == "base64") 189 | { 190 | // 格式应该是base64://... 191 | std::string imageData = base64_decode(fileStr.substr(9)); 192 | int header = 1, length = imageData.size(); 193 | imageData = std::string(reinterpret_cast(&header), 4) + std::string(reinterpret_cast(&length), 4) + imageData; 194 | const char* pic = XQAPI::UpLoadPic(to_string(robotQQ).c_str(), (msgType == 2 || msgType == 3) ? 2 : 1, targetId, imageData.c_str() + 8); 195 | if (!pic || strlen(pic) == 0) 196 | { 197 | ret += "空图片"; 198 | } 199 | else 200 | { 201 | ret += pic; 202 | } 203 | } 204 | else 205 | { 206 | // file:///... 207 | if (fileStr.substr(0, 4) == "file") 208 | { 209 | fileStr = fileStr.substr(8); 210 | } 211 | else 212 | { 213 | fileStr = rootPath + "\\data\\image\\" + fileStr; 214 | } 215 | ret += "[pic="; 216 | ret += fileStr; 217 | ret += "]"; 218 | } 219 | } 220 | } 221 | else if (msgStr.substr(l, 16) == "[CQ:record,file=") 222 | { 223 | if (msgStr[l + 16] == '{') 224 | { 225 | ret += "[Voi="; 226 | ret += msgStr.substr(l + 16, r - l - 16); 227 | ret += "]"; 228 | } 229 | else 230 | { 231 | std::string ppath = rootPath + "\\data\\record\\"; 232 | ppath += msgStr.substr(l + 16, r - l - 16); 233 | ret += "[Voi="; 234 | ret += ppath; 235 | ret += "]"; 236 | } 237 | } 238 | else if (msgStr.substr(l, 12) == "[CQ:face,id=") 239 | { 240 | ret += "[Face"; 241 | ret += msgStr.substr(l + 12, r - l - 12); 242 | ret += ".gif]"; 243 | } 244 | else if (msgStr.substr(l, 13) == "[CQ:emoji,id=") 245 | { 246 | try 247 | { 248 | std::string idStr(msgStr.substr(l + 13, r - l - 13)); 249 | u32string u32_str; 250 | if (idStr.substr(0, 6) == "100000") 251 | { 252 | u32_str.append({ static_cast(std::stoul(idStr.substr(6))), 0xFE0F, 0x20E3 }); 253 | } 254 | else 255 | { 256 | u32_str.append({ static_cast(std::stoul(idStr)) }); 257 | } 258 | 259 | std::string utf8 = ConvertEncoding(u32_str, "utf-32le", "utf-8"); 260 | std::stringstream stream; 261 | for (char c : utf8) 262 | { 263 | stream << setfill('0') << hex << uppercase << setw(2) << (0xff & (unsigned int)c); 264 | } 265 | ret += "[emoji="; 266 | ret += stream.str(); 267 | ret += "]"; 268 | } 269 | catch (...) 270 | { 271 | ret += msgStr.substr(l, r - l + 1); 272 | } 273 | } 274 | else if (msgStr.substr(l, 10) == "[CQ:share,") 275 | { 276 | std::string shareStr; 277 | shareStr += msgStr.substr(l + 10, r - l - 10); 278 | //title 279 | string title = retrieveSectionData(shareStr, "title"); 280 | //url 281 | string url = retrieveSectionData(shareStr, "url"); 282 | //content 283 | string content = retrieveSectionData(shareStr, "content"); 284 | //image 285 | string image = retrieveSectionData(shareStr, "image"); 286 | 287 | XQAPI::SendXML(std::to_string(robotQQ).c_str(), 0, msgType, targetId, QQ, constructXMLShareMsg(url, title, content, image).c_str(), 0); 288 | } 289 | else if (msgType == 1 && msgStr.substr(l, 9) == "[CQ:shake") 290 | { 291 | //好友&抖动 292 | XQAPI::ShakeWindow(std::to_string(robotQQ).c_str(), QQ); 293 | } 294 | else 295 | { 296 | ret += msgStr.substr(l, r - l + 1); 297 | } 298 | last = r + 1; 299 | l = msgStr.find("[CQ", r); 300 | r = msgStr.find(']', l); 301 | } 302 | ret += msgStr.substr(last); 303 | if (!ret.empty()) 304 | { 305 | const char* rret; 306 | if (msgType == 2 && isAnon && AnonIgnore) 307 | { 308 | if (XQAPI::GetAnon(std::to_string(robotQQ).c_str(), targetId)) 309 | { 310 | // 尝试发送 311 | rret = XQAPI::SendMsgEX_V2(std::to_string(robotQQ).c_str(), msgType, targetId, QQ, ret.c_str(), bubbleID, isAnon, json); 312 | try 313 | { 314 | if (!rret || strcmp(rret, "") == 0) 315 | { 316 | throw std::exception(); 317 | } 318 | nlohmann::json j = nlohmann::json::parse(rret); 319 | if (!j["sendok"].get()) 320 | { 321 | throw std::exception(); 322 | } 323 | } 324 | catch (std::exception&) 325 | { 326 | rret = XQAPI::SendMsgEX_V2(std::to_string(robotQQ).c_str(), msgType, targetId, QQ, ret.c_str(), bubbleID, FALSE, json); 327 | } 328 | } 329 | else 330 | { 331 | // 没开启匿名 332 | rret = XQAPI::SendMsgEX_V2(std::to_string(robotQQ).c_str(), msgType, targetId, QQ, ret.c_str(), bubbleID, FALSE, json); 333 | } 334 | } 335 | else 336 | { 337 | rret = XQAPI::SendMsgEX_V2(std::to_string(robotQQ).c_str(), msgType, targetId, QQ, ret.c_str(), bubbleID, isAnon, json); 338 | } 339 | 340 | return rret ? rret : ""; 341 | } 342 | // 最开始非空但是解析CQ码以后为空说明其中的内容(卡片之类的)在前面被发送出去了,这个时候不应该返回失败,强制返回成功 343 | return "FORCESUC"; 344 | } 345 | 346 | std::string nickToCQCode(const std::string& msg) 347 | { 348 | std::string ret; 349 | std::regex match("( |\\[em\\](e[0-9]{1,6})\\[\\/em\\])", std::regex::ECMAScript | std::regex::icase); 350 | std::smatch m; 351 | int last = 0; 352 | while (regex_search(msg.begin() + last, msg.end(), m, match)) 353 | { 354 | ret.append(m.prefix()); 355 | if (m[0].str()[0] == '&') 356 | { 357 | ret.append(" "); 358 | } 359 | else 360 | { 361 | int codepoint = std::stoi(m[2].str().substr(1)); 362 | if (codepoint > 200000) 363 | { 364 | u32string u32_str; 365 | u32_str.append({ static_cast(codepoint - 200000) }); 366 | 367 | std::string utf8 = ConvertEncoding(u32_str, "utf-32le", "utf-8"); 368 | std::stringstream stream; 369 | for (char c : utf8) 370 | { 371 | stream << setfill('0') << hex << uppercase << setw(2) << (0xff & (unsigned int)c); 372 | } 373 | ret += "[emoji="; 374 | ret += stream.str(); 375 | ret += "]"; 376 | } 377 | else if (codepoint >= 100000) 378 | { 379 | ret.append("[Face" + std::to_string(codepoint - 100000) + ".gif]"); 380 | } 381 | else 382 | { 383 | ret.append("[CQ:image,url=http://qzonestyle.gtimg.cn/qzone/em/" + m[2].str() + ".gif]"); 384 | } 385 | } 386 | last += m.position() + m.length(); 387 | } 388 | ret.append(msg.substr(last)); 389 | return ret; 390 | } 391 | 392 | BOOL APIENTRY DllMain(HMODULE hModule, 393 | DWORD ul_reason_for_call, 394 | LPVOID lpReserved) 395 | { 396 | switch (ul_reason_for_call) 397 | { 398 | case DLL_PROCESS_ATTACH: 399 | hDllModule = hModule; 400 | break; 401 | } 402 | 403 | return TRUE; 404 | } 405 | 406 | 407 | 408 | 409 | void __stdcall MsgLoop() 410 | { 411 | MSG msg{}; 412 | while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE)) 413 | { 414 | TranslateMessage(&msg); 415 | DispatchMessageA(&msg); 416 | } 417 | this_thread::sleep_for(5ms); 418 | if (running) fakeMainThread.push([](int) {ExceptionWrapper(MsgLoop)(); }); 419 | } 420 | 421 | void __stdcall CQXQ_init() 422 | { 423 | // 获取文件目录 424 | char path[MAX_PATH]; 425 | GetModuleFileNameA(nullptr, path, MAX_PATH); 426 | std::string pathStr(path); 427 | rootPath = pathStr.substr(0, pathStr.rfind("\\")); 428 | 429 | running = true; 430 | 431 | // 初始化伪主线程 432 | fakeMainThread.push([](int) { 433 | HRESULT hr = OleInitialize(nullptr); 434 | INITCOMMONCONTROLSEX ex; 435 | ex.dwSize = sizeof(ex); 436 | ex.dwICC = ICC_ANIMATE_CLASS | ICC_BAR_CLASSES | ICC_COOL_CLASSES | ICC_DATE_CLASSES | ICC_HOTKEY_CLASS | ICC_INTERNET_CLASSES | 437 | ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_NATIVEFNTCTL_CLASS | ICC_PAGESCROLLER_CLASS | ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | 438 | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES; 439 | InitCommonControlsEx(&ex); 440 | ExceptionWrapper(InitGUI)(); 441 | }).wait(); 442 | 443 | fakeMainThread.push([](int) { ExceptionWrapper(MsgLoop)(); }); 444 | 445 | // 载入配置 446 | filesystem::path p(rootPath); 447 | p.append("CQPlugins").append(".cqxq_recv_self_event.enable"); 448 | RecvSelfEvent = filesystem::exists(p); 449 | 450 | // 载入API DLL并加载API函数 451 | #ifdef XQ 452 | XQHModule = LoadLibraryA("xqapi.dll"); 453 | #else 454 | XQHModule = LoadLibraryA("OQapi.dll"); 455 | #endif 456 | XQAPI::initFuncs(XQHModule); 457 | 458 | // 写出CQP.dll 459 | HRSRC rscInfo = FindResourceA(hDllModule, MAKEINTRESOURCEA(IDR_CQP1), "CQP"); 460 | HGLOBAL rsc = nullptr; 461 | char* rscPtr = nullptr; 462 | DWORD size = 0; 463 | if (rscInfo) 464 | { 465 | rsc = LoadResource(hDllModule, rscInfo); 466 | size = SizeofResource(hDllModule, rscInfo); 467 | if (rsc) rscPtr = (char*)LockResource(rsc); 468 | } 469 | 470 | if (rscPtr && size) 471 | { 472 | std::string rscStr(rscPtr, size); 473 | ofstream ofCQP(rootPath + "\\CQP.dll", ios::out | ios::trunc | ios::binary); 474 | if (ofCQP) 475 | { 476 | ofCQP << rscStr; 477 | XQAPI::OutPutLog("写出CQP.dll成功"); 478 | } 479 | else 480 | { 481 | XQAPI::OutPutLog("写出CQP.dll失败!无法创建文件输出流!"); 482 | } 483 | } 484 | else 485 | { 486 | XQAPI::OutPutLog("写出CQP.dll失败!获取资源失败!"); 487 | } 488 | 489 | // 加载CQP.dll 490 | CQPHModule = LoadLibraryA("CQP.dll"); 491 | 492 | // 创建必要文件夹 493 | std::filesystem::create_directories(rootPath + "\\data\\image\\"); 494 | std::filesystem::create_directories(rootPath + "\\data\\record\\"); 495 | std::filesystem::remove_all(rootPath + "\\CQPlugins\\tmp\\"); 496 | std::filesystem::create_directories(rootPath + "\\CQPlugins\\tmp\\"); 497 | 498 | // 加载CQ插件 499 | loadAllCQPlugin(); 500 | 501 | // 延迟字符串内存释放 502 | memFreeThread = std::make_unique([] 503 | { 504 | while (running) 505 | { 506 | { 507 | std::unique_lock lock(memFreeMutex); 508 | // 延迟5分钟释放字符串内存 509 | while (!memFreeQueue.empty() && time(nullptr) - memFreeQueue.top().first > 300) 510 | { 511 | free((void*)memFreeQueue.top().second); 512 | memFreeQueue.pop(); 513 | } 514 | } 515 | { 516 | std::unique_lock lock(memFreeMsgIdMutex); 517 | // 延迟5分钟释放MsgId内存 518 | while (!memFreeMsgIdQueue.empty() && time(nullptr) - memFreeMsgIdQueue.top().first > 300) 519 | { 520 | if (msgIdMap.count(memFreeMsgIdQueue.top().second)) 521 | { 522 | msgIdMap.erase(memFreeMsgIdQueue.top().second); 523 | } 524 | memFreeMsgIdQueue.pop(); 525 | } 526 | } 527 | std::this_thread::sleep_for(1s); 528 | } 529 | // 在线程退出时释放掉所有内存 530 | std::unique_lock lock(memFreeMutex); 531 | while (!memFreeQueue.empty()) 532 | { 533 | free((void*)memFreeQueue.top().second); 534 | memFreeQueue.pop(); 535 | } 536 | }); 537 | Init = true; 538 | } 539 | 540 | #ifdef XQ 541 | CQAPI(void, XQ_AuthId, 8)(int ID, int IMAddr) 542 | { 543 | AuthCode = new unsigned char[16]; 544 | *((int*)AuthCode) = 1; 545 | *((int*)(AuthCode + 4)) = 8; 546 | *((int*)(AuthCode + 8)) = ID; 547 | *((int*)(AuthCode + 12)) = IMAddr; 548 | AuthCode += 8; 549 | } 550 | CQAPI(void, XQ_AutoId, 8)(int ID, int IMAddr) 551 | { 552 | AuthCode = new unsigned char[16]; 553 | *((int*)AuthCode) = 1; 554 | *((int*)(AuthCode + 4)) = 8; 555 | *((int*)(AuthCode + 8)) = ID; 556 | *((int*)(AuthCode + 12)) = IMAddr; 557 | AuthCode += 8; 558 | } 559 | #endif 560 | 561 | #ifdef XQ 562 | CQAPI(const char*, XQ_Create, 4)(const char* ver) 563 | #else 564 | CQAPI(const char*, OQ_Create, 0)() 565 | #endif 566 | { 567 | #ifdef XQ 568 | return "{\"name\":\"CQXQ\", \"pver\":\"1.1.0beta\", \"sver\":3, \"author\":\"Suhui\", \"desc\":\"A simple compatibility layer between CQ and XQ\"}"; 569 | #else 570 | return "插件名称{CQOQ}\r\n插件版本{1.1.0beta}\r\n插件作者{Suhui}\r\n插件说明{A simple compatibility layer between CQ and OQ}\r\n插件skey{8956RTEWDFG3216598WERDF3}\r\n插件sdk{S3}"; 571 | #endif 572 | } 573 | 574 | void __stdcall CQXQ_Uninit() 575 | { 576 | for (auto& plugin : plugins) 577 | { 578 | FreeLibrary(plugin.second.dll); 579 | } 580 | filesystem::remove_all(rootPath + "\\CQPlugins\\tmp\\"); 581 | FreeLibrary(XQHModule); 582 | FreeLibrary(CQPHModule); 583 | running = false; 584 | memFreeThread->join(); 585 | memFreeThread.reset(nullptr); 586 | fakeMainThread.push([](int) { 587 | DestroyMainWindow(); 588 | int bRet; 589 | MSG msg{}; 590 | while ((bRet = GetMessageA(&msg, nullptr, 0, 0)) != 0) 591 | { 592 | if (bRet == -1) 593 | { 594 | break; 595 | } 596 | else 597 | { 598 | TranslateMessage(&msg); 599 | DispatchMessageA(&msg); 600 | } 601 | } 602 | OleUninitialize(); 603 | }).wait(); 604 | fakeMainThread.stop(); 605 | p.stop(); 606 | if (AuthCode) 607 | { 608 | AuthCode -= 8; 609 | delete[] AuthCode; 610 | } 611 | } 612 | 613 | #ifdef XQ 614 | CQAPI(int32_t, XQ_DestroyPlugin, 0)() 615 | #else 616 | CQAPI(int32_t, OQ_DestroyPlugin, 0)() 617 | #endif 618 | { 619 | ExceptionWrapper(CQXQ_Uninit)(); 620 | return 0; 621 | } 622 | 623 | #ifdef XQ 624 | CQAPI(int32_t, XQ_SetUp, 0)() 625 | #else 626 | CQAPI(int32_t, OQ_SetUp, 0)() 627 | #endif 628 | { 629 | ExceptionWrapper(ShowMainWindow)(); 630 | return 0; 631 | } 632 | 633 | // QQ-群号 缓存 用于发送消息 634 | std::map UserGroupCache; 635 | 636 | // QQ-讨论组号 缓存 用于发送消息 637 | std::map UserDiscussCache; 638 | 639 | // 群-群成员json字符串缓存 用于获取群成员列表,群成员信息,缓存时间1小时,遇到群成员变动事件/群名片更改事件刷新 640 | std::map> GroupMemberCache; 641 | 642 | // 群列表缓存 用于获取群列表,缓存时间1小时,遇到群添加/退出等事件刷新 643 | std::pair GroupListCache; 644 | 645 | int __stdcall CQXQ_process(const char* botQQ, int32_t msgType, int32_t subType, const char* sourceId, const char* activeQQ, const char* passiveQQ, const char* msg, const char* msgNum, const char* msgId, const char* rawMsg, const char* timeStamp, char* retText) 646 | { 647 | botQQ = botQQ ? botQQ : ""; 648 | sourceId = sourceId ? sourceId : ""; 649 | activeQQ = activeQQ ? activeQQ : ""; 650 | passiveQQ = passiveQQ ? passiveQQ : ""; 651 | msg = msg ? msg : ""; 652 | msgNum = msgNum ? msgNum : ""; 653 | msgId = msgId ? msgId : ""; 654 | rawMsg = rawMsg ? rawMsg : ""; 655 | timeStamp = timeStamp ? timeStamp : ""; 656 | 657 | std::string botQQStr = botQQ; 658 | 659 | if (robotQQ == 0) robotQQ = atoll(botQQ); 660 | if (!botQQStr.empty() && robotQQ != atoll(botQQ)) return 0; 661 | if (msgType == XQ_Load) 662 | { 663 | p.push([](int) { ExceptionWrapper(CQXQ_init)(); }); 664 | return 0; 665 | } 666 | while (!Init) 667 | { 668 | this_thread::sleep_for(100ms); 669 | } 670 | if (msgType == XQ_Exit) 671 | { 672 | for (const auto& plugin : plugins_events[CQ_eventExit]) 673 | { 674 | const auto exit = IntMethod(plugin.event); 675 | if (exit) 676 | { 677 | fakeMainThread.push([&exit](int) { ExceptionWrapper(exit)(); }).wait(); 678 | } 679 | } 680 | return 0; 681 | } 682 | if (msgType == XQ_Enable) 683 | { 684 | fakeMainThread.push([](int) 685 | { 686 | this_thread::sleep_for(1s); 687 | const char* onlineList = XQAPI::GetOnLineList(); 688 | std::string onlineListStr = onlineList ? onlineList : ""; 689 | 690 | if (!onlineListStr.empty() && !(onlineListStr[0] == '\r' || onlineListStr[0] == '\n') && !EnabledEventCalled) 691 | { 692 | if (robotQQ == 0) 693 | { 694 | robotQQ = atoll(onlineList); 695 | } 696 | // 先返回此函数,让先驱认为插件已经开启再调用插件的启用函数 697 | 698 | for (const auto& plugin : plugins_events[CQ_eventEnable]) 699 | { 700 | if (!plugins[plugin.plugin_id].enabled) continue; 701 | const auto enable = IntMethod(plugin.event); 702 | if (enable) 703 | { 704 | ExceptionWrapper(enable)(); 705 | } 706 | } 707 | EnabledEventCalled = true; 708 | } 709 | }); 710 | 711 | return 0; 712 | } 713 | if (msgType == XQ_LogInComplete) 714 | { 715 | fakeMainThread.push([](int) { 716 | if (!EnabledEventCalled && XQAPI::IsEnable()) 717 | { 718 | for (const auto& plugin : plugins_events[CQ_eventEnable]) 719 | { 720 | if (!plugins[plugin.plugin_id].enabled) continue; 721 | const auto enable = IntMethod(plugin.event); 722 | if (enable) 723 | { 724 | ExceptionWrapper(enable)(); 725 | } 726 | } 727 | EnabledEventCalled = true; 728 | } 729 | }); 730 | return 0; 731 | } 732 | if (msgType == XQ_Disable) 733 | { 734 | if (!EnabledEventCalled) return 0; 735 | EnabledEventCalled = false; 736 | for (const auto& plugin : plugins_events[CQ_eventDisable]) 737 | { 738 | if (!plugins[plugin.plugin_id].enabled) continue; 739 | const auto disable = IntMethod(plugin.event); 740 | if (disable) 741 | { 742 | fakeMainThread.push([&disable](int) { ExceptionWrapper(disable)(); }).wait(); 743 | } 744 | } 745 | return 0; 746 | } 747 | if (msgType == XQ_GroupInviteReqEvent) 748 | { 749 | Unpack p; 750 | p.add(XQ_GroupInviteReqEvent); 751 | p.add(sourceId); 752 | p.add(activeQQ); 753 | p.add(rawMsg); 754 | const std::string data = base64_encode(p.getAll()); 755 | for (const auto& plugin : plugins_events[CQ_eventRequest_AddGroup]) 756 | { 757 | if (!plugins[plugin.plugin_id].enabled) continue; 758 | const auto invited = EvRequestAddGroup(plugin.event); 759 | if (invited) 760 | { 761 | if (invited(2, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), msg, data.c_str())) break; 762 | } 763 | } 764 | return 0; 765 | } 766 | if (msgType == XQ_GroupAddReqEvent) 767 | { 768 | Unpack p; 769 | p.add(XQ_GroupAddReqEvent); 770 | p.add(sourceId); 771 | p.add(activeQQ); 772 | p.add(rawMsg); 773 | const std::string data = base64_encode(p.getAll()); 774 | for (const auto& plugin : plugins_events[CQ_eventRequest_AddGroup]) 775 | { 776 | if (!plugins[plugin.plugin_id].enabled) continue; 777 | const auto addReq = EvRequestAddGroup(plugin.event); 778 | if (addReq) 779 | { 780 | if (addReq(1, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), msg, data.c_str())) break; 781 | } 782 | } 783 | return 0; 784 | } 785 | if (msgType == XQ_GroupInviteOtherReqEvent) 786 | { 787 | Unpack p; 788 | p.add(XQ_GroupInviteOtherReqEvent); 789 | p.add(sourceId); 790 | p.add(activeQQ); 791 | p.add(rawMsg); 792 | const std::string data = base64_encode(p.getAll()); 793 | const string CQInviteMsg = "邀请人:[CQ:at,qq="s + activeQQ + "]" + ((strcmp(msg, "") != 0) ? (" 附言:"s + msg) : ""); 794 | for (const auto& plugin : plugins_events[CQ_eventRequest_AddGroup]) 795 | { 796 | if (!plugins[plugin.plugin_id].enabled) continue; 797 | const auto addReq = EvRequestAddGroup(plugin.event); 798 | if (addReq) 799 | { 800 | if (addReq(1, atoi(timeStamp), atoll(sourceId), atoll(passiveQQ), CQInviteMsg.c_str(), data.c_str())) break; 801 | } 802 | } 803 | return 0; 804 | } 805 | if (msgType == XQ_FriendAddReqEvent) 806 | { 807 | for (const auto& plugin : plugins_events[CQ_eventRequest_AddFriend]) 808 | { 809 | if (!plugins[plugin.plugin_id].enabled) continue; 810 | const auto addReq = EvRequestAddFriend(plugin.event); 811 | if (addReq) 812 | { 813 | if (addReq(1, atoi(timeStamp), atoll(activeQQ), msg, activeQQ)) break; 814 | } 815 | } 816 | return 0; 817 | } 818 | if (msgType == XQ_GroupBanEvent) 819 | { 820 | int banTime = 0; 821 | std::string banTimeStr = msg; 822 | regex banTimeRegex("([0-9]+)天([0-9]+)时([0-9]+)分([0-9]+)秒", regex::ECMAScript); 823 | smatch m; 824 | if (regex_search(banTimeStr, m, banTimeRegex)) 825 | { 826 | banTime = std::stoi(m[1]) * 86400 + std::stoi(m[2]) * 3600 + std::stoi(m[3]) * 60 + std::stoi(m[4]); 827 | } 828 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupBan]) 829 | { 830 | if (!plugins[plugin.plugin_id].enabled) continue; 831 | const auto ban = EvGroupBan(plugin.event); 832 | if (ban) 833 | { 834 | if (ban(2, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), atoll(passiveQQ), banTime)) break; 835 | } 836 | } 837 | return 0; 838 | } 839 | if (msgType == XQ_GroupUnbanEvent) 840 | { 841 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupBan]) 842 | { 843 | if (!plugins[plugin.plugin_id].enabled) continue; 844 | const auto ban = EvGroupBan(plugin.event); 845 | if (ban) 846 | { 847 | if (ban(1, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), atoll(passiveQQ), 0)) break; 848 | } 849 | } 850 | return 0; 851 | } 852 | if (msgType == XQ_GroupWholeBanEvent) 853 | { 854 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupBan]) 855 | { 856 | if (!plugins[plugin.plugin_id].enabled) continue; 857 | const auto ban = EvGroupBan(plugin.event); 858 | if (ban) 859 | { 860 | if (ban(2, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), 0, 0)) break; 861 | } 862 | } 863 | return 0; 864 | } 865 | if (msgType == XQ_GroupWholeUnbanEvent) 866 | { 867 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupBan]) 868 | { 869 | if (!plugins[plugin.plugin_id].enabled) continue; 870 | const auto ban = EvGroupBan(plugin.event); 871 | if (ban) 872 | { 873 | if (ban(1, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), 0, 0)) break; 874 | } 875 | } 876 | return 0; 877 | } 878 | if (msgType == XQ_GroupMemberIncreaseByApply) 879 | { 880 | GroupMemberCache.erase(atoll(sourceId)); 881 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupMemberIncrease]) 882 | { 883 | if (!plugins[plugin.plugin_id].enabled) continue; 884 | const auto MbrInc = EvGroupMember(plugin.event); 885 | if (MbrInc) 886 | { 887 | if (MbrInc(1, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), atoll(passiveQQ))) break; 888 | } 889 | } 890 | return 0; 891 | } 892 | if (msgType == XQ_GroupMemberIncreaseByInvite) 893 | { 894 | GroupMemberCache.erase(atoll(sourceId)); 895 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupMemberIncrease]) 896 | { 897 | if (!plugins[plugin.plugin_id].enabled) continue; 898 | const auto MbrInc = EvGroupMember(plugin.event); 899 | if (MbrInc) 900 | { 901 | if (MbrInc(2, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), atoll(passiveQQ))) break; 902 | } 903 | } 904 | return 0; 905 | } 906 | if (msgType == XQ_GroupMemberDecreaseByExit) 907 | { 908 | GroupMemberCache.erase(atoll(sourceId)); 909 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupMemberDecrease]) 910 | { 911 | if (!plugins[plugin.plugin_id].enabled) continue; 912 | const auto event = EvGroupMember(plugin.event); 913 | if (event) 914 | { 915 | if (event(1, atoi(timeStamp), atoll(sourceId), 0, atoll(passiveQQ))) break; 916 | } 917 | } 918 | return 0; 919 | } 920 | if (msgType == XQ_GroupMemberDecreaseByKick) 921 | { 922 | GroupMemberCache.erase(atoll(sourceId)); 923 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupMemberDecrease]) 924 | { 925 | if (!plugins[plugin.plugin_id].enabled) continue; 926 | const auto event = EvGroupMember(plugin.event); 927 | if (event) 928 | { 929 | if (robotQQ == atoll(passiveQQ)) 930 | { 931 | if (event(3, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), atoll(passiveQQ))) break; 932 | } 933 | else 934 | { 935 | if (event(2, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), atoll(passiveQQ))) break; 936 | } 937 | } 938 | } 939 | return 0; 940 | } 941 | if (msgType == XQ_GroupAdminSet) 942 | { 943 | GroupMemberCache.erase(atoll(sourceId)); 944 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupAdmin]) 945 | { 946 | if (!plugins[plugin.plugin_id].enabled) continue; 947 | const auto event = EvGroupAdmin(plugin.event); 948 | if (event) 949 | { 950 | if (event(2, atoi(timeStamp), atoll(sourceId), atoll(passiveQQ))) break; 951 | } 952 | } 953 | return 0; 954 | } 955 | if (msgType == XQ_GroupAdminUnset) 956 | { 957 | GroupMemberCache.erase(atoll(sourceId)); 958 | for (const auto& plugin : plugins_events[CQ_eventSystem_GroupAdmin]) 959 | { 960 | if (!plugins[plugin.plugin_id].enabled) continue; 961 | const auto event = EvGroupAdmin(plugin.event); 962 | if (event) 963 | { 964 | if (event(1, atoi(timeStamp), atoll(sourceId), atoll(passiveQQ))) break; 965 | } 966 | } 967 | return 0; 968 | } 969 | // 根据酷Q逻辑,只有不经过酷Q处理的好友添加事件(即比如用户设置了同意一切好友请求)才会调用好友已添加事件 970 | if (msgType == XQ_FriendAddedEvent) 971 | { 972 | for (const auto& plugin : plugins_events[CQ_eventFriend_Add]) 973 | { 974 | if (!plugins[plugin.plugin_id].enabled) continue; 975 | const auto event = EvFriendAdd(plugin.event); 976 | if (event) 977 | { 978 | if (event(1, atoi(timeStamp), atoll(activeQQ))) break; 979 | } 980 | } 981 | return 0; 982 | } 983 | 984 | if (msgType == XQ_GroupFileUploadEvent) 985 | { 986 | std::string msgStr = msg; 987 | std::string sections = msgStr.substr(1, msgStr.size() - 2); 988 | 989 | // 转换为ID 990 | std::string id = retrieveSectionData(sections, "File"); 991 | id = "/" + id.substr(1, id.size() - 2); 992 | 993 | // 文件名称 994 | std::string rawName = retrieveSectionData(sections, "name"); 995 | std::string name; 996 | for (size_t i = 0; i < rawName.length(); i += 2) 997 | { 998 | string byte = rawName.substr(i, 2); 999 | name.push_back(static_cast (strtol(byte.c_str(), nullptr, 16))); 1000 | } 1001 | name = UTF8toGB18030(name); 1002 | 1003 | // 文件大小 1004 | long long size = 0; 1005 | std::string rawMsgStr = rawMsg; 1006 | int ByteLoc = rawMsgStr.find("42 79 74 65"); 1007 | if (ByteLoc != string::npos) 1008 | { 1009 | long long place = 1; 1010 | while (ByteLoc >= 0) 1011 | { 1012 | ByteLoc -= 3; 1013 | char c = static_cast(strtol(rawMsgStr.substr(ByteLoc, 2).c_str(), nullptr, 16)); 1014 | if (isdigit(static_cast(c))) 1015 | { 1016 | size += place * (c - 48LL); 1017 | } 1018 | else 1019 | { 1020 | break; 1021 | } 1022 | place *= 10; 1023 | } 1024 | } 1025 | Unpack p; 1026 | p.add(id); 1027 | p.add(name); 1028 | p.add(size); 1029 | p.add(0LL); 1030 | 1031 | const std::string file = base64_encode(p.getAll()); 1032 | for (const auto& plugin : plugins_events[CQ_eventGroupUpload]) 1033 | { 1034 | if (!plugins[plugin.plugin_id].enabled) continue; 1035 | const auto event = EvGroupUpload(plugin.event); 1036 | if (event) 1037 | { 1038 | if (event(1, atoi(timeStamp), atoll(sourceId), atoll(activeQQ), file.c_str())) break; 1039 | } 1040 | } 1041 | return 0; 1042 | } 1043 | if (msgType == XQ_groupCardChange) 1044 | { 1045 | GroupMemberCache.erase(atoll(sourceId)); 1046 | } 1047 | 1048 | // 如果不接收来自自己的事件, 直接退出函数 1049 | if (activeQQ && !RecvSelfEvent && robotQQ == atoll(activeQQ)) 1050 | { 1051 | return 0; 1052 | } 1053 | 1054 | if (msgType == XQ_FriendMsgEvent || msgType == XQ_ShakeEvent) 1055 | { 1056 | size_t id = newMsgId({ 1, -1, atoll(activeQQ), atoll(msgNum), atoll(msgId), atoll(timeStamp) }); 1057 | for (const auto& plugin : plugins_events[CQ_eventPrivateMsg]) 1058 | { 1059 | if (!plugins[plugin.plugin_id].enabled) continue; 1060 | const auto privMsg = EvPriMsg(plugin.event); 1061 | if (privMsg) 1062 | { 1063 | if (privMsg(11, id, atoll(activeQQ), (msgType == XQ_FriendMsgEvent) ? parseToCQCode(msg).c_str() : "[CQ:shake]", 0)) break; 1064 | } 1065 | } 1066 | return 0; 1067 | } 1068 | if (msgType == XQ_GroupTmpMsgEvent) 1069 | { 1070 | if (activeQQ && sourceId) UserGroupCache[atoll(activeQQ)] = atoll(sourceId); 1071 | size_t id = newMsgId({ 4, atoll(sourceId), atoll(activeQQ), atoll(msgNum), atoll(msgId), atoll(timeStamp) }); 1072 | for (const auto& plugin : plugins_events[CQ_eventPrivateMsg]) 1073 | { 1074 | if (!plugins[plugin.plugin_id].enabled) continue; 1075 | const auto privMsg = EvPriMsg(plugin.event); 1076 | if (privMsg) 1077 | { 1078 | if (privMsg(2, id, atoll(activeQQ), parseToCQCode(msg).c_str(), 0)) break; 1079 | } 1080 | } 1081 | } 1082 | if (msgType == XQ_GroupMsgEvent || (RecvSelfEvent && msgType == XQ_GroupSelfMsgEvent)) 1083 | { 1084 | if (activeQQ && sourceId) UserGroupCache[atoll(activeQQ)] = atoll(sourceId); 1085 | size_t id = newMsgId({ 2, atoll(sourceId), -1, atoll(msgNum), atoll(msgId), -1 }); 1086 | for (const auto& plugin : plugins_events[CQ_eventGroupMsg]) 1087 | { 1088 | if (!plugins[plugin.plugin_id].enabled) continue; 1089 | const auto groupMsg = EvGroupMsg(plugin.event); 1090 | if (groupMsg) 1091 | { 1092 | if (groupMsg(1, id, atoll(sourceId), atoll(activeQQ), "", parseToCQCode(msg).c_str(), 0)) break; 1093 | } 1094 | } 1095 | return 0; 1096 | } 1097 | if (msgType == XQ_DiscussTmpMsgEvent) 1098 | { 1099 | if (activeQQ && sourceId) UserDiscussCache[atoll(activeQQ)] = atoll(sourceId); 1100 | size_t id = newMsgId({ 5, atoll(sourceId), atoll(activeQQ), atoll(msgNum), atoll(msgId), atoll(timeStamp) }); 1101 | for (const auto& plugin : plugins_events[CQ_eventPrivateMsg]) 1102 | { 1103 | if (!plugins[plugin.plugin_id].enabled) continue; 1104 | const auto privMsg = EvPriMsg(plugin.event); 1105 | if (privMsg) 1106 | { 1107 | if (privMsg(3, id, atoll(activeQQ), parseToCQCode(msg).c_str(), 0)) break; 1108 | } 1109 | } 1110 | } 1111 | if (msgType == XQ_DiscussMsgEvent) 1112 | { 1113 | if (activeQQ && sourceId) UserDiscussCache[atoll(activeQQ)] = atoll(sourceId); 1114 | size_t id = newMsgId({ 3, atoll(sourceId), -1, atoll(msgNum), atoll(msgId), -1 }); 1115 | for (const auto& plugin : plugins_events[CQ_eventDiscussMsg]) 1116 | { 1117 | if (!plugins[plugin.plugin_id].enabled) continue; 1118 | const auto event = EvDiscussMsg(plugin.event); 1119 | if (event) 1120 | { 1121 | if (event(1, id, atoll(sourceId), atoll(activeQQ), parseToCQCode(msg).c_str(), 0)) break; 1122 | } 1123 | } 1124 | return 0; 1125 | } 1126 | return 0; 1127 | } 1128 | 1129 | #ifdef XQ 1130 | CQAPI(int32_t, XQ_Event, 48)(const char* botQQ, int32_t msgType, int32_t subType, const char* sourceId, const char* activeQQ, const char* passiveQQ, const char* msg, const char* msgNum, const char* msgId, const char* rawMsg, const char* timeStamp, char* retText) 1131 | #else 1132 | CQAPI(int32_t, OQ_Event, 48)(const char* botQQ, int32_t msgType, int32_t subType, const char* sourceId, const char* activeQQ, const char* passiveQQ, const char* msg, const char* msgNum, const char* msgId, const char* rawMsg, const char* timeStamp, char* retText) 1133 | #endif 1134 | { 1135 | return ExceptionWrapper(CQXQ_process)(botQQ, msgType, subType, sourceId, activeQQ, passiveQQ, msg, msgNum, msgId, rawMsg, timeStamp, retText); 1136 | } 1137 | 1138 | 1139 | CQAPI(int32_t, CQ_canSendImage, 4)(int32_t) 1140 | { 1141 | return 1; 1142 | } 1143 | 1144 | CQAPI(int32_t, CQ_canSendRecord, 4)(int32_t) 1145 | { 1146 | return 1; 1147 | } 1148 | 1149 | 1150 | CQAPI(int32_t, CQ_sendPrivateMsg, 16)(int32_t plugin_id, int64_t account, const char* msg) 1151 | { 1152 | if (robotQQ == 0) return -1; 1153 | if (!msg) return -1; 1154 | std::string accStr = std::to_string(account); 1155 | 1156 | std::string ret; 1157 | int type = 0; 1158 | long long sourceId = 0; 1159 | 1160 | if (XQAPI::IfFriend(to_string(robotQQ).c_str(), accStr.c_str())) 1161 | { 1162 | type = 1; 1163 | sourceId = -1; 1164 | } 1165 | else if (UserGroupCache.count(account)) 1166 | { 1167 | type = 4; 1168 | sourceId = UserGroupCache[account]; 1169 | } 1170 | else if (UserDiscussCache.count(account)) 1171 | { 1172 | type = 5; 1173 | sourceId = UserDiscussCache[account]; 1174 | } 1175 | else 1176 | { 1177 | XQAPI::OutPutLog(("无法发送消息给QQ" + accStr + ": 找不到可用的发送路径").c_str()); 1178 | return -1; 1179 | } 1180 | ret = parseCQCodeAndSend(type, std::to_string(sourceId).c_str(), accStr.c_str(), msg, 0, FALSE, FALSE, ""); 1181 | // 无法获取消息ID的强制成功,返回10e9 1182 | if (ret == "FORCESUC") 1183 | { 1184 | return 1000000000; 1185 | } 1186 | try 1187 | { 1188 | nlohmann::json j = nlohmann::json::parse(ret); 1189 | if (!j["sendok"].get()) 1190 | { 1191 | return -1; 1192 | } 1193 | size_t msgId = newMsgId({ type, sourceId, account, j["msgno"].get(), j["msgid"].get(), j["msgtime"].get() }); 1194 | return msgId; 1195 | } 1196 | catch (std::exception&) 1197 | { 1198 | return -1; 1199 | } 1200 | } 1201 | 1202 | CQAPI(int32_t, CQ_sendGroupMsg, 16)(int32_t plugin_id, int64_t group, const char* msg) 1203 | { 1204 | if (robotQQ == 0) return -1; 1205 | if (!msg) return -1; 1206 | if (XQAPI::IsShutUp(std::to_string(robotQQ).c_str(), std::to_string(group).c_str(), std::to_string(robotQQ).c_str()) || 1207 | XQAPI::IsShutUp(std::to_string(robotQQ).c_str(), std::to_string(group).c_str(), "")) 1208 | { 1209 | return -1; 1210 | } 1211 | 1212 | // 匿名判断 1213 | BOOL isAnon = FALSE; 1214 | BOOL AnonIgnore = FALSE; 1215 | std::string msgStr = msg; 1216 | if (msgStr.substr(0, 13) == "[CQ:anonymous") 1217 | { 1218 | size_t r = msgStr.find(']'); 1219 | if (r != string::npos) 1220 | { 1221 | isAnon = TRUE; 1222 | std::string anonOptions = msgStr.substr(13, r - 13); 1223 | std::string ignore = retrieveSectionData(anonOptions, "ignore"); 1224 | if (ignore == "true") 1225 | { 1226 | AnonIgnore = TRUE; 1227 | } 1228 | msgStr = msgStr.substr(r + 1); 1229 | } 1230 | } 1231 | std::string grpStr = std::to_string(group); 1232 | std::string ret = parseCQCodeAndSend(2, grpStr.c_str(), to_string(robotQQ).c_str(), msgStr.c_str(), 0, isAnon, AnonIgnore, ""); 1233 | // 无法获取消息ID的强制成功,返回10e9 1234 | if (ret == "FORCESUC") 1235 | { 1236 | return 1000000000; 1237 | } 1238 | try 1239 | { 1240 | nlohmann::json j = nlohmann::json::parse(ret); 1241 | if (!j["sendok"].get()) 1242 | { 1243 | return -1; 1244 | } 1245 | size_t msgId = newMsgId({ 2, group, -1, j["msgno"].get(), j["msgid"].get(), -1 }); 1246 | return msgId; 1247 | } 1248 | catch (std::exception&) 1249 | { 1250 | return -1; 1251 | } 1252 | } 1253 | 1254 | CQAPI(int32_t, CQ_setFatal, 8)(int32_t plugin_id, const char* info) 1255 | { 1256 | XQAPI::OutPutLog((plugins[plugin_id].file + ": [FATAL] " + info).c_str()); 1257 | return 0; 1258 | } 1259 | 1260 | CQAPI(const char*, CQ_getAppDirectory, 4)(int32_t plugin_id) 1261 | { 1262 | std::string ppath; 1263 | char path[MAX_PATH]; 1264 | GetModuleFileNameA(nullptr, path, MAX_PATH); 1265 | std::string pathStr(path); 1266 | ppath = pathStr.substr(0, pathStr.rfind('\\')) + "\\CQPlugins\\config\\" + plugins[plugin_id].file + "\\"; 1267 | std::filesystem::create_directories(ppath); 1268 | return delayMemFreeCStr(ppath.c_str()); 1269 | } 1270 | 1271 | CQAPI(int64_t, CQ_getLoginQQ, 4)(int32_t plugin_id) 1272 | { 1273 | return robotQQ; 1274 | } 1275 | 1276 | CQAPI(const char*, CQ_getLoginNick, 4)(int32_t plugin_id) 1277 | { 1278 | const char* nick = XQAPI::GetNick(to_string(robotQQ).c_str(), to_string(robotQQ).c_str()); 1279 | return nick ? delayMemFreeCStr(nickToCQCode(nick)) : ""; 1280 | } 1281 | 1282 | CQAPI(int32_t, CQ_setGroupAnonymous, 16)(int32_t plugin_id, int64_t group, BOOL enable) 1283 | { 1284 | XQAPI::SetAnon(to_string(robotQQ).c_str(), std::to_string(group).c_str(), enable); 1285 | return 0; 1286 | } 1287 | 1288 | CQAPI(int32_t, CQ_setGroupBan, 28)(int32_t plugin_id, int64_t group, int64_t member, int64_t duration) 1289 | { 1290 | XQAPI::ShutUP(to_string(robotQQ).c_str(), std::to_string(group).c_str(), std::to_string(member).c_str(), static_cast(duration)); 1291 | return 0; 1292 | } 1293 | 1294 | CQAPI(int32_t, CQ_setGroupCard, 24)(int32_t plugin_id, int64_t group, int64_t member, const char* card) 1295 | { 1296 | XQAPI::SetGroupCard(to_string(robotQQ).c_str(), std::to_string(group).c_str(), std::to_string(member).c_str(), card); 1297 | return 0; 1298 | } 1299 | 1300 | CQAPI(int32_t, CQ_setGroupKick, 24)(int32_t plugin_id, int64_t group, int64_t member, BOOL reject) 1301 | { 1302 | XQAPI::KickGroupMBR(to_string(robotQQ).c_str(), std::to_string(group).c_str(), std::to_string(member).c_str(), reject); 1303 | return 0; 1304 | } 1305 | 1306 | CQAPI(int32_t, CQ_setGroupLeave, 16)(int32_t plugin_id, int64_t group, BOOL dismiss) 1307 | { 1308 | XQAPI::QuitGroup(to_string(robotQQ).c_str(), std::to_string(group).c_str()); 1309 | return 0; 1310 | } 1311 | 1312 | CQAPI(int32_t, CQ_setGroupSpecialTitle, 32)(int32_t plugin_id, int64_t group, int64_t member, 1313 | const char* title, int64_t duration) 1314 | { 1315 | XQAPI::OutPutLog((plugins[plugin_id].file + "调用了不支持的API CQ_setGroupSpecialTitle").c_str()); 1316 | return 0; 1317 | } 1318 | 1319 | CQAPI(int32_t, CQ_setGroupWholeBan, 16)(int32_t plugin_id, int64_t group, BOOL enable) 1320 | { 1321 | XQAPI::ShutUP(to_string(robotQQ).c_str(), std::to_string(group).c_str(), "", enable); 1322 | return 0; 1323 | } 1324 | 1325 | CQAPI(int32_t, CQ_deleteMsg, 12)(int32_t plugin_id, int64_t msg_id) 1326 | { 1327 | size_t id = static_cast(msg_id); 1328 | FakeMsgId msgId; 1329 | { 1330 | std::unique_lock lock(memFreeMsgIdMutex); 1331 | if (msgIdMap.count(id)) 1332 | { 1333 | msgId = msgIdMap[id]; 1334 | msgIdMap.erase(id); 1335 | } 1336 | else 1337 | { 1338 | return -1; 1339 | } 1340 | } 1341 | XQAPI::WithdrawMsgEX(to_string(robotQQ).c_str(), 1342 | msgId.type, 1343 | msgId.sourceId == -1 ? "" : std::to_string(msgId.sourceId).c_str(), 1344 | msgId.QQ == -1 ? "" : std::to_string(msgId.QQ).c_str(), 1345 | std::to_string(msgId.msgNum).c_str(), 1346 | std::to_string(msgId.msgId).c_str(), 1347 | msgId.msgTime == -1 ? "" : std::to_string(msgId.msgTime).c_str() 1348 | ); 1349 | return 0; 1350 | } 1351 | 1352 | CQAPI(const char*, CQ_getFriendList, 8)(int32_t plugin_id, BOOL reserved) 1353 | { 1354 | std::string ret; 1355 | const char* frdLst = XQAPI::GetFriendList(to_string(robotQQ).c_str()); 1356 | if (!frdLst)return ""; 1357 | std::string friendList = frdLst; 1358 | Unpack p; 1359 | std::vector Friends; 1360 | int count = 0; 1361 | try 1362 | { 1363 | nlohmann::json j = nlohmann::json::parse(friendList); 1364 | for (const auto& item : j["result"]) 1365 | { 1366 | for (const auto& member : item["mems"]) 1367 | { 1368 | Unpack t; 1369 | t.add(member["uin"].get()); 1370 | t.add(UTF8toGB18030(member["name"].get())); 1371 | t.add(""); 1372 | Friends.push_back(t); 1373 | count++; 1374 | } 1375 | } 1376 | p.add(count); 1377 | for (auto& g : Friends) 1378 | { 1379 | p.add(g); 1380 | } 1381 | ret = base64_encode(p.getAll()); 1382 | return delayMemFreeCStr(ret.c_str()); 1383 | } 1384 | catch(std::exception&) 1385 | { 1386 | return ""; 1387 | } 1388 | return ""; 1389 | 1390 | 1391 | /* 1392 | std::string ret; 1393 | const char* frdLst = XQAPI::GetFriendList_B(to_string(robotQQ).c_str()); 1394 | if (!frdLst) return ""; 1395 | std::string friendList = frdLst; 1396 | Unpack p; 1397 | std::vector Friends; 1398 | int count = 0; 1399 | while (!friendList.empty()) 1400 | { 1401 | size_t endline = friendList.find('\n'); 1402 | std::string item = friendList.substr(0, endline); 1403 | while (!item.empty() && (item[item.length() - 1] == '\r' || item[item.length() - 1] == '\n')) item.erase(item.end() - 1); 1404 | if(!item.empty()) 1405 | { 1406 | Unpack tmp; 1407 | tmp.add(atoll(item.c_str())); 1408 | const char* nick = XQAPI::GetNick(to_string(robotQQ).c_str(), item.c_str()); 1409 | tmp.add(nick ? nick : ""); 1410 | const char* remarks = XQAPI::GetFriendsRemark(to_string(robotQQ).c_str(), item.c_str()); 1411 | tmp.add(remarks ? remarks : ""); 1412 | Friends.push_back(tmp); 1413 | count++; 1414 | } 1415 | if (endline == string::npos) friendList = ""; 1416 | else friendList = friendList.substr(endline + 1); 1417 | } 1418 | p.add(count); 1419 | for (auto& g : Friends) 1420 | { 1421 | p.add(g); 1422 | } 1423 | ret = base64_encode(p.getAll()); 1424 | return delayMemFreeCStr(ret.c_str()); 1425 | */ 1426 | } 1427 | 1428 | CQAPI(const char*, CQ_getGroupInfo, 16)(int32_t plugin_id, int64_t group, BOOL disableCache) 1429 | { 1430 | std::string ret; 1431 | 1432 | // 判断是否是新获取的列表 1433 | bool newRetrieved = false; 1434 | std::string memberListStr; 1435 | 1436 | // 判断是否要使用缓存 1437 | if (disableCache || !GroupMemberCache.count(group) || (time(nullptr) - GroupMemberCache[group].second) > 3600) 1438 | { 1439 | newRetrieved = true; 1440 | const char* memberList = XQAPI::GetGroupMemberList_B(to_string(robotQQ).c_str(), std::to_string(group).c_str()); 1441 | memberListStr = memberList ? memberList : ""; 1442 | } 1443 | else 1444 | { 1445 | memberListStr = GroupMemberCache[group].first; 1446 | } 1447 | 1448 | try 1449 | { 1450 | if (memberListStr.empty()) 1451 | { 1452 | throw std::runtime_error("GetGroupMemberList Failed"); 1453 | } 1454 | nlohmann::json j = nlohmann::json::parse(memberListStr); 1455 | std::string groupStr = std::to_string(group); 1456 | const char* groupName = XQAPI::GetGroupName(to_string(robotQQ).c_str(), groupStr.c_str()); 1457 | std::string groupNameStr = groupName ? nickToCQCode(groupName) : ""; 1458 | int currentNum = j["mem_num"].get(); 1459 | int maxNum = j["max_num"].get(); 1460 | int friendNum = 0; 1461 | for (const auto& member : j["members"].items()) 1462 | { 1463 | if (member.value().count("fr") && member.value()["fr"].get() == 1) 1464 | { 1465 | friendNum += 1; 1466 | } 1467 | } 1468 | Unpack p; 1469 | p.add(group); 1470 | p.add(groupNameStr); 1471 | p.add(currentNum); 1472 | p.add(maxNum); 1473 | p.add(friendNum); 1474 | ret = base64_encode(p.getAll()); 1475 | if (newRetrieved) GroupMemberCache[group] = { memberListStr, time(nullptr) }; 1476 | return delayMemFreeCStr(ret.c_str()); 1477 | } 1478 | catch (std::exception&) 1479 | { 1480 | XQAPI::OutPutLog(("警告, 获取群信息失败, 正在使用更慢的另一种方法尝试: "s + memberListStr).c_str()); 1481 | std::string groupStr = std::to_string(group); 1482 | const char* groupName = XQAPI::GetGroupName(to_string(robotQQ).c_str(), groupStr.c_str()); 1483 | std::string groupNameStr = groupName ? nickToCQCode(groupName) : ""; 1484 | const char* groupNum = XQAPI::GetGroupMemberNum(to_string(robotQQ).c_str(), groupStr.c_str()); 1485 | int currentNum = 0, maxNum = 0; 1486 | if (groupNum) 1487 | { 1488 | std::string groupNumStr = groupNum; 1489 | size_t newline = groupNumStr.find('\n'); 1490 | if (newline != string::npos) 1491 | { 1492 | currentNum = atoi(groupNumStr.substr(0, newline).c_str()); 1493 | maxNum = atoi(groupNumStr.substr(newline + 1).c_str()); 1494 | } 1495 | } 1496 | Unpack p; 1497 | p.add(group); 1498 | p.add(groupNameStr); 1499 | p.add(currentNum); 1500 | p.add(maxNum); 1501 | p.add(0); // 这种方式暂不支持好友人数 1502 | ret = base64_encode(p.getAll()); 1503 | return delayMemFreeCStr(ret.c_str()); 1504 | } 1505 | } 1506 | 1507 | CQAPI(const char*, CQ_getGroupList, 4)(int32_t plugin_id) 1508 | { 1509 | std::string ret; 1510 | 1511 | const char* groupList = XQAPI::GetGroupList(to_string(robotQQ).c_str()); 1512 | std::string groupListStr = groupList ? groupList : ""; 1513 | if (groupListStr.empty()) return ""; 1514 | try 1515 | { 1516 | Unpack p; 1517 | std::vector Groups; 1518 | nlohmann::json j = nlohmann::json::parse(groupListStr); 1519 | for (const auto& group : j["create"]) 1520 | { 1521 | Unpack t; 1522 | t.add(group["gc"].get()); 1523 | t.add(UTF8toGB18030(group["gn"].get())); 1524 | Groups.push_back(t); 1525 | } 1526 | for (const auto& group : j["join"]) 1527 | { 1528 | Unpack t; 1529 | t.add(group["gc"].get()); 1530 | t.add(UTF8toGB18030(group["gn"].get())); 1531 | Groups.push_back(t); 1532 | } 1533 | for (const auto& group : j["manage"]) 1534 | { 1535 | Unpack t; 1536 | t.add(group["gc"].get()); 1537 | t.add(UTF8toGB18030(group["gn"].get())); 1538 | Groups.push_back(t); 1539 | } 1540 | p.add(static_cast(Groups.size())); 1541 | for (auto& group : Groups) 1542 | { 1543 | p.add(group); 1544 | } 1545 | ret = base64_encode(p.getAll()); 1546 | return delayMemFreeCStr(ret.c_str()); 1547 | } 1548 | catch (std::exception&) 1549 | { 1550 | XQAPI::OutPutLog(("警告, 获取群列表失败: "s + groupListStr).c_str()); 1551 | return ""; 1552 | } 1553 | } 1554 | 1555 | CQAPI(const char*, CQ_getGroupMemberInfoV2, 24)(int32_t plugin_id, int64_t group, int64_t account, BOOL disableCache) 1556 | { 1557 | std::string ret; 1558 | // 判断是否是新获取的列表 1559 | bool newRetrieved = false; 1560 | std::string memberListStr; 1561 | 1562 | // 判断是否要使用缓存 1563 | if (disableCache || !GroupMemberCache.count(group) || (time(nullptr) - GroupMemberCache[group].second) > 3600) 1564 | { 1565 | newRetrieved = true; 1566 | const char* memberList = XQAPI::GetGroupMemberList_B(to_string(robotQQ).c_str(), std::to_string(group).c_str()); 1567 | memberListStr = memberList ? memberList : ""; 1568 | } 1569 | else 1570 | { 1571 | memberListStr = GroupMemberCache[group].first; 1572 | } 1573 | 1574 | try 1575 | { 1576 | std::string accStr = std::to_string(account); 1577 | if (memberListStr.empty()) 1578 | { 1579 | throw std::runtime_error("GetGroupMemberList Failed"); 1580 | } 1581 | nlohmann::json j = nlohmann::json::parse(memberListStr); 1582 | long long owner = j["owner"].get(); 1583 | std::set admin; 1584 | if (j.count("adm")) j["adm"].get_to(admin); 1585 | std::map lvlName = j["levelname"].get>(); 1586 | for (auto& item : lvlName) 1587 | { 1588 | lvlName[item.first] = UTF8toGB18030(item.second); 1589 | } 1590 | if (!j["members"].count(accStr)) return ""; 1591 | Unpack t; 1592 | t.add(group); 1593 | t.add(account); 1594 | t.add(j["members"][accStr].count("nk") ? UTF8toGB18030(j["members"][accStr]["nk"].get()) : ""); 1595 | t.add(j["members"][accStr].count("cd") ? UTF8toGB18030(j["members"][accStr]["cd"].get()) : ""); 1596 | t.add(255); 1597 | t.add(-1); 1598 | /* 1599 | int gender = XQAPI::GetGender(to_string(robotQQ).c_str(), accStr.c_str()); 1600 | t.add(gender == -1 ? 255 : -1); 1601 | t.add(XQAPI::GetAge(to_string(robotQQ).c_str(), accStr.c_str())); 1602 | */ 1603 | t.add(""); 1604 | t.add(j["members"][accStr].count("jt") ? j["members"][accStr]["jt"].get() : 0); 1605 | t.add(j["members"][accStr].count("lst") ? j["members"][accStr]["lst"].get() : 0); 1606 | t.add(j["members"][accStr].count("ll") ? (lvlName.count("lvln" + std::to_string(j["members"][accStr]["ll"].get())) ? lvlName["lvln" + std::to_string(j["members"][accStr]["ll"].get())] : "") : ""); 1607 | t.add(account == owner ? 3 : (admin.count(account) ? 2 : 1)); 1608 | t.add(FALSE); 1609 | t.add(""); 1610 | t.add(-1); 1611 | t.add(TRUE); 1612 | ret = base64_encode(t.getAll()); 1613 | if (newRetrieved) GroupMemberCache[group] = { memberListStr, time(nullptr) }; 1614 | return delayMemFreeCStr(ret.c_str()); 1615 | } 1616 | catch (std::exception&) 1617 | { 1618 | XQAPI::OutPutLog(("警告, 获取群成员信息失败, 正在使用更慢的另一种方法尝试: "s + memberListStr).c_str()); 1619 | std::string grpStr = std::to_string(group); 1620 | std::string accStr = std::to_string(account); 1621 | Unpack p; 1622 | p.add(group); 1623 | p.add(account); 1624 | const char* nick = XQAPI::GetNick(to_string(robotQQ).c_str(), accStr.c_str()); 1625 | p.add(nick ? nickToCQCode(nick) : ""); 1626 | const char* groupCard = XQAPI::GetGroupCard(to_string(robotQQ).c_str(), grpStr.c_str(), accStr.c_str()); 1627 | p.add(groupCard ? nickToCQCode(groupCard) : ""); 1628 | p.add(255); 1629 | p.add(-1); 1630 | /* 1631 | int gender = XQAPI::GetGender(to_string(robotQQ).c_str(), accStr.c_str()); 1632 | p.add(gender == -1 ? 255 : -1); 1633 | p.add(XQAPI::GetAge(to_string(robotQQ).c_str(), accStr.c_str())); 1634 | */ 1635 | p.add(""); 1636 | p.add(0); 1637 | p.add(0); 1638 | p.add(""); 1639 | const char* admin = XQAPI::GetGroupAdmin(to_string(robotQQ).c_str(), std::to_string(group).c_str()); 1640 | std::string adminList = admin ? admin : ""; 1641 | int count = 0; 1642 | int permissions = 1; 1643 | while (!adminList.empty()) 1644 | { 1645 | size_t endline = adminList.find('\n'); 1646 | std::string item = adminList.substr(0, endline); 1647 | while (!item.empty() && (item[item.length() - 1] == '\r' || item[item.length() - 1] == '\n')) item.erase(item.end() - 1); 1648 | if (item == accStr) 1649 | { 1650 | if (count == 0)permissions = 3; 1651 | else permissions = 2; 1652 | break; 1653 | } 1654 | if (endline == string::npos) adminList = ""; 1655 | else adminList = adminList.substr(endline + 1); 1656 | count++; 1657 | } 1658 | p.add(permissions); 1659 | p.add(FALSE); 1660 | p.add(""); 1661 | p.add(-1); 1662 | p.add(TRUE); 1663 | ret = base64_encode(p.getAll()); 1664 | return delayMemFreeCStr(ret.c_str()); 1665 | } 1666 | } 1667 | 1668 | CQAPI(const char*, CQ_getGroupMemberList, 12)(int32_t plugin_id, int64_t group) 1669 | { 1670 | std::string ret; 1671 | const char* memberList = XQAPI::GetGroupMemberList_B(to_string(robotQQ).c_str(), std::to_string(group).c_str()); 1672 | std::string memberListStr = memberList ? memberList : ""; 1673 | try 1674 | { 1675 | if (memberListStr.empty()) 1676 | { 1677 | throw std::runtime_error("GetGroupMemberList Failed"); 1678 | } 1679 | Unpack p; 1680 | nlohmann::json j = nlohmann::json::parse(memberListStr); 1681 | long long owner = j["owner"].get(); 1682 | std::set admin; 1683 | if (j.count("adm")) j["adm"].get_to(admin); 1684 | int mem_num = j["mem_num"].get(); 1685 | std::map lvlName = j["levelname"].get>(); 1686 | for (auto& item : lvlName) 1687 | { 1688 | lvlName[item.first] = UTF8toGB18030(item.second); 1689 | } 1690 | p.add(mem_num); 1691 | for (const auto& member : j["members"].items()) 1692 | { 1693 | long long qq = std::stoll(member.key()); 1694 | Unpack t; 1695 | t.add(group); 1696 | t.add(qq); 1697 | t.add(member.value().count("nk") ? UTF8toGB18030(member.value()["nk"].get()) : ""); 1698 | t.add(member.value().count("cd") ? UTF8toGB18030(member.value()["cd"].get()) : ""); 1699 | t.add(255); 1700 | t.add(-1); 1701 | /* 1702 | int gender = XQAPI::GetGender(to_string(robotQQ).c_str(), member.key().c_str()); 1703 | t.add(gender == -1 ? 255 : -1); 1704 | t.add(XQAPI::GetAge(to_string(robotQQ).c_str(), member.key().c_str())); 1705 | */ 1706 | t.add(""); 1707 | t.add(member.value().count("jt") ? member.value()["jt"].get() : 0); 1708 | t.add(member.value().count("lst") ? member.value()["lst"].get() : 0); 1709 | t.add(member.value().count("ll") ? (lvlName.count("lvln" + std::to_string(member.value()["ll"].get())) ? lvlName["lvln" + std::to_string(member.value()["ll"].get())]: "") : ""); 1710 | t.add(qq == owner ? 3 : (admin.count(qq) ? 2 : 1)); 1711 | t.add(FALSE); 1712 | t.add(""); 1713 | t.add(-1); 1714 | t.add(TRUE); 1715 | p.add(t); 1716 | } 1717 | GroupMemberCache[group] = { memberListStr, time(nullptr) }; 1718 | ret = base64_encode(p.getAll()); 1719 | return delayMemFreeCStr(ret.c_str()); 1720 | } 1721 | catch (std::exception&) 1722 | { 1723 | XQAPI::OutPutLog(("警告, 获取群成员列表失败: "s + memberListStr).c_str()); 1724 | return ""; 1725 | } 1726 | return ""; 1727 | } 1728 | 1729 | CQAPI(const char*, CQ_getCookiesV2, 8)(int32_t plugin_id, const char* domain) 1730 | { 1731 | return XQAPI::GetCookies(to_string(robotQQ).c_str()); 1732 | } 1733 | 1734 | CQAPI(const char*, CQ_getCsrfToken, 4)(int32_t plugin_id) 1735 | { 1736 | return XQAPI::GetBkn(to_string(robotQQ).c_str()); 1737 | } 1738 | 1739 | CQAPI(const char*, CQ_getImage, 8)(int32_t plugin_id, const char* file) 1740 | { 1741 | if (!file) return ""; 1742 | std::string fileStr(file); 1743 | if (fileStr.empty()) return ""; 1744 | if (fileStr.substr(0, 10) == "[CQ:image," && fileStr[fileStr.length() - 1] == ']') 1745 | { 1746 | fileStr = fileStr.substr(10, fileStr.length() - 10 - 1); 1747 | // 现在的状态是file=xxx.jpg/png/gif/...(,cache=xxx) 1748 | size_t file_loc = fileStr.find("file="); 1749 | if (file_loc != string::npos) 1750 | { 1751 | fileStr = fileStr.substr(file_loc + 5, fileStr.find(',', file_loc) - file_loc - 5); 1752 | } 1753 | } 1754 | else if (fileStr.substr(0, 5) == "[pic=" && fileStr[fileStr.length() - 1] == ']') 1755 | { 1756 | fileStr = fileStr.substr(5, fileStr.length() - 5 - 1); 1757 | } 1758 | 1759 | const char* picLink; 1760 | std::string picFileName; 1761 | // 现在是图片名本身,判断是否符合格式, 并判断是好友图片还是群聊图片 1762 | regex groupPic("\\{([0-9A-Fa-f]{8})[-]([0-9A-Fa-f]{4})[-]([0-9A-Fa-f]{4})[-]([0-9A-Fa-f]{4})[-]([0-9A-Fa-f]{12})\\}\\.(jpg|png|gif|bmp|jpeg).*", regex::ECMAScript | regex::icase); 1763 | regex privatePic("\\{[0-9]{5,15}[-][0-9]{5,15}[-]([0-9A-Fa-f]{32})\\}\\.(jpg|png|gif|bmp|jpeg).*", regex::ECMAScript | regex::icase); 1764 | smatch m; 1765 | if (regex_match(fileStr, m, groupPic)) 1766 | { 1767 | fileStr = "[pic=" + fileStr + "]"; 1768 | picFileName = m[1].str() + m[2].str() + m[3].str() + m[4].str() + m[5].str() + "." + m[6].str(); 1769 | // 群号其实并没有用,随便写一个 1770 | picLink = XQAPI::GetPicLink(to_string(robotQQ).c_str(), 2, "173528463", fileStr.c_str()); 1771 | } 1772 | else if (regex_match(fileStr, m, privatePic)) 1773 | { 1774 | fileStr = "[pic=" + fileStr + "]"; 1775 | picFileName = m[1].str() + "." + m[2].str(); 1776 | picLink = XQAPI::GetPicLink(to_string(robotQQ).c_str(), 1, "", fileStr.c_str()); 1777 | } 1778 | else 1779 | { 1780 | return ""; 1781 | } 1782 | 1783 | if (!picLink || strcmp(picLink, "") == 0) 1784 | { 1785 | return ""; 1786 | } 1787 | std::string path = rootPath + "\\data\\image\\" + picFileName; 1788 | Cominit init; 1789 | if (filesystem::exists(path) || URLDownloadToFileA(nullptr, picLink, (path).c_str(), 0, nullptr) == S_OK) 1790 | { 1791 | return delayMemFreeCStr(path); 1792 | } 1793 | return ""; 1794 | } 1795 | 1796 | CQAPI(const char*, CQ_getRecordV2, 12)(int32_t plugin_id, const char* file, const char* format) 1797 | { 1798 | if (!file) return ""; 1799 | std::string fileStr(file); 1800 | std::string recordName; 1801 | if (fileStr.empty()) return ""; 1802 | if (fileStr.substr(0, 16) == "[CQ:record,file=") 1803 | { 1804 | fileStr = "[Voi=" + fileStr.substr(16, fileStr.length() - 1 - 16) + "]"; 1805 | recordName = fileStr.substr(16, fileStr.length() - 1 - 16); 1806 | } 1807 | else if (fileStr.substr(0, 5) == "[Voi=") 1808 | { 1809 | recordName = fileStr.substr(5, fileStr.length() - 1 - 5); 1810 | } 1811 | else 1812 | { 1813 | return ""; 1814 | } 1815 | 1816 | const char* recordLink = XQAPI::GetVoiLink(to_string(robotQQ).c_str(), fileStr.c_str()); 1817 | if (!recordLink || strcmp(recordLink, "") == 0) 1818 | { 1819 | return ""; 1820 | } 1821 | std::string path = rootPath + "\\data\\record\\" + recordName; 1822 | Cominit init; 1823 | if (filesystem::exists(path) || URLDownloadToFileA(nullptr, recordLink, path.c_str(), 0, nullptr) == S_OK) 1824 | { 1825 | return delayMemFreeCStr(path); 1826 | } 1827 | 1828 | return ""; 1829 | } 1830 | 1831 | CQAPI(const char*, CQ_getStrangerInfo, 16)(int32_t plugin_id, int64_t account, BOOL disableCache) 1832 | { 1833 | std::string ret; 1834 | std::string accStr = std::to_string(account); 1835 | Unpack p; 1836 | p.add(account); 1837 | const char* nick = XQAPI::GetNick(to_string(robotQQ).c_str(), accStr.c_str()); 1838 | p.add(nick ? nickToCQCode(nick) : ""); 1839 | p.add(255); 1840 | p.add(-1); 1841 | /* 1842 | int gender = XQAPI::GetGender(to_string(robotQQ).c_str(), accStr.c_str()); 1843 | p.add(gender == -1 ? 255 : gender); 1844 | p.add(XQAPI::GetAge(to_string(robotQQ).c_str(), accStr.c_str())); 1845 | */ 1846 | ret = base64_encode(p.getAll()); 1847 | return delayMemFreeCStr(ret.c_str()); 1848 | } 1849 | 1850 | CQAPI(int32_t, CQ_sendDiscussMsg, 16)(int32_t plugin_id, int64_t discuss, const char* msg) 1851 | { 1852 | if (robotQQ == 0) return -1; 1853 | if (!msg) return -1; 1854 | std::string discussStr = std::to_string(discuss); 1855 | std::string ret = parseCQCodeAndSend(3, discussStr.c_str(), to_string(robotQQ).c_str(), msg, 0, FALSE, FALSE, ""); 1856 | // 无法获取消息ID的强制成功,返回10e9 1857 | if (ret == "FORCESUC") 1858 | { 1859 | return 1000000000; 1860 | } 1861 | try 1862 | { 1863 | nlohmann::json j = nlohmann::json::parse(ret); 1864 | if (!j["sendok"].get()) 1865 | { 1866 | return -1; 1867 | } 1868 | size_t msgId = newMsgId({ 3, discuss, -1, j["msgno"].get(), j["msgid"].get(), -1 }); 1869 | return msgId; 1870 | } 1871 | catch (std::exception&) 1872 | { 1873 | return -1; 1874 | } 1875 | } 1876 | 1877 | CQAPI(int32_t, CQ_sendLikeV2, 16)(int32_t plugin_id, int64_t account, int32_t times) 1878 | { 1879 | std::string accStr = std::to_string(account); 1880 | for (int i=0;i!=times;i++) 1881 | { 1882 | XQAPI::UpVote(to_string(robotQQ).c_str(), accStr.c_str()); 1883 | } 1884 | return 0; 1885 | } 1886 | 1887 | CQAPI(int32_t, CQ_setDiscussLeave, 12)(int32_t plugin_id, int64_t discuss) 1888 | { 1889 | XQAPI::OutPutLog((plugins[plugin_id].file + "调用了不支持的API CQ_setDiscussLeave").c_str()); 1890 | return 0; 1891 | } 1892 | 1893 | CQAPI(int32_t, CQ_setFriendAddRequest, 16)(int32_t plugin_id, const char* id, int32_t type, const char* remark) 1894 | { 1895 | XQAPI::HandleFriendEvent(to_string(robotQQ).c_str(), id, type == 1 ? 10 : 20, remark); 1896 | return 0; 1897 | } 1898 | 1899 | CQAPI(int32_t, CQ_setGroupAddRequestV2, 20)(int32_t plugin_id, const char* id, int32_t req_type, int32_t fb_type, 1900 | const char* reason) 1901 | { 1902 | Unpack p(base64_decode(id)); 1903 | int eventType = p.getInt(); 1904 | std::string group = p.getstring(); 1905 | std::string qq = p.getstring(); 1906 | std::string raw = p.getstring(); 1907 | XQAPI::HandleGroupEvent(to_string(robotQQ).c_str(), eventType, qq.c_str(), group.c_str(), raw.c_str(), fb_type == 1 ? 10 : 20, reason); 1908 | return 0; 1909 | } 1910 | 1911 | CQAPI(int32_t, CQ_setGroupAdmin, 24)(int32_t plugin_id, int64_t group, int64_t account, BOOL admin) 1912 | { 1913 | // https://qinfo.clt.qq.com/cgi-bin/qun_info/set_group_admin 1914 | XQAPI::OutPutLog((plugins[plugin_id].file + "调用了不支持的API CQ_setAdmin").c_str()); 1915 | // XQAPI::SetAdmin(to_string(robotQQ).c_str(), std::to_string(group).c_str(), std::to_string(account).c_str(), admin); 1916 | return 0; 1917 | } 1918 | 1919 | CQAPI(int32_t, CQ_setGroupAnonymousBan, 24)(int32_t plugin_id, int64_t group, const char* id, int64_t duration) 1920 | { 1921 | XQAPI::OutPutLog((plugins[plugin_id].file + "调用了不支持的API CQ_setGroupAnonymousBan").c_str()); 1922 | return 0; 1923 | } 1924 | 1925 | CQAPI(int32_t, CQ_addLog, 16)(int32_t plugin_id, int32_t priority, const char* type, const char* content) 1926 | { 1927 | string level; 1928 | switch(priority) 1929 | { 1930 | case 0: 1931 | level = "DEBUG"; 1932 | break; 1933 | case 10: 1934 | level = "INFO"; 1935 | break; 1936 | case 11: 1937 | level = "INFOSUCCESS"; 1938 | break; 1939 | case 12: 1940 | level = "INFORECV"; 1941 | break; 1942 | case 13: 1943 | level = "INFOSEND"; 1944 | break; 1945 | case 20: 1946 | level = "WARNING"; 1947 | break; 1948 | case 30: 1949 | level = "ERROR"; 1950 | break; 1951 | case 40: 1952 | level = "FATAL"; 1953 | break; 1954 | default: 1955 | level = "UNKNOWN"; 1956 | break; 1957 | } 1958 | XQAPI::OutPutLog((plugins[plugin_id].file + ": [" + level + "] [" + type + "] " + content).c_str()); 1959 | return 0; 1960 | } 1961 | 1962 | // Legacy 1963 | 1964 | CQAPI(const char*, CQ_getCookies, 4)(int32_t plugin_id) 1965 | { 1966 | return CQ_getCookiesV2(plugin_id, ""); 1967 | } 1968 | 1969 | CQAPI(int32_t, CQ_setGroupAddRequest, 16)(int32_t plugin_id, const char* id, int32_t req_type, int32_t fb_type) 1970 | { 1971 | return CQ_setGroupAddRequestV2(plugin_id, id, req_type, fb_type, ""); 1972 | } 1973 | 1974 | CQAPI(int32_t, CQ_sendLike, 12)(int32_t plugin_id, int64_t account) 1975 | { 1976 | return CQ_sendLikeV2(plugin_id, account, 1); 1977 | } 1978 | 1979 | CQAPI(int32_t, CQ_reload, 4)(int32_t plugin_id) 1980 | { 1981 | reloadOneCQPlugin(plugin_id); 1982 | return 0; 1983 | } 1984 | 1985 | -------------------------------------------------------------------------------- /CQXQ/native.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define CQAPI(ReturnType, Name, Size) __pragma(comment(linker, "/EXPORT:" #Name "=_" #Name "@" #Size))\ 4 | extern "C" __declspec(dllexport) ReturnType __stdcall Name 5 | 6 | typedef int32_t (__stdcall* IntMethod)(); 7 | typedef const char* (__stdcall* StringMethod)(); 8 | typedef int32_t (__stdcall* FuncInitialize)(int32_t); 9 | 10 | typedef int32_t (__stdcall* EvPriMsg)(int32_t, int32_t, int64_t, const char*, int32_t); 11 | typedef int32_t (__stdcall* EvGroupMsg)(int32_t, int32_t, int64_t, int64_t, const char*, const char*, int32_t); 12 | typedef int32_t (__stdcall* EvDiscussMsg)(int32_t, int32_t, int64_t, int64_t, const char*, int32_t); 13 | typedef int32_t (__stdcall* EvGroupAdmin)(int32_t, int32_t, int64_t, int64_t); 14 | typedef int32_t (__stdcall* EvGroupMember)(int32_t, int32_t, int64_t, int64_t, int64_t); 15 | typedef int32_t (__stdcall* EvGroupBan)(int32_t, int32_t, int64_t, int64_t, int64_t, int64_t); 16 | typedef int32_t (__stdcall* EvGroupUpload)(int32_t, int32_t, int64_t, int64_t, const char*); 17 | typedef int32_t (__stdcall* EvRequestAddGroup)(int32_t, int32_t, int64_t, int64_t, const char*, const char*); 18 | typedef int32_t (__stdcall* EvRequestAddFriend)(int32_t, int32_t, int64_t, const char*, const char*); 19 | typedef int32_t (__stdcall* EvFriendAdd)(int32_t, int32_t, int64_t); 20 | 21 | 22 | #define XQ_StartupComplete 10000 23 | #define XQ_Exit 10001 24 | #define XQ_Load 12000 25 | #define XQ_Enable 12001 26 | #define XQ_Disable 12002 27 | 28 | 29 | #define XQ_FriendMsgEvent 1 30 | #define XQ_GroupMsgEvent 2 31 | #define XQ_DiscussMsgEvent 3 32 | #define XQ_GroupTmpMsgEvent 4 33 | #define XQ_DiscussTmpMsgEvent 5 34 | #define XQ_GroupSelfMsgEvent 10 35 | #define XQ_ShakeEvent 109 36 | 37 | #define XQ_FriendAddReqEvent 101 38 | #define XQ_FriendAddedEvent 100 39 | 40 | #define XQ_GroupInviteReqEvent 214 41 | #define XQ_GroupAddReqEvent 213 42 | #define XQ_GroupInviteOtherReqEvent 215 43 | #define XQ_GroupFileUploadEvent 218 44 | 45 | #define XQ_GroupMemberIncreaseByApply 212 46 | #define XQ_GroupMemberIncreaseByInvite 219 47 | #define XQ_GroupMemberDecreaseByExit 201 48 | #define XQ_GroupMemberDecreaseByKick 202 49 | 50 | #define XQ_GroupBanEvent 203 51 | #define XQ_GroupUnbanEvent 204 52 | #define XQ_GroupWholeBanEvent 205 53 | #define XQ_GroupWholeUnbanEvent 206 54 | 55 | #define XQ_GroupAdminSet 210 56 | #define XQ_GroupAdminUnset 211 57 | 58 | #define XQ_LogInComplete 1101 59 | 60 | #define XQ_groupCardChange 217 61 | 62 | #define CQ_eventPrivateMsg 21 63 | #define CQ_eventGroupMsg 2 64 | #define CQ_eventDiscussMsg 4 65 | #define CQ_eventGroupUpload 11 66 | #define CQ_eventSystem_GroupAdmin 101 67 | #define CQ_eventSystem_GroupMemberDecrease 102 68 | #define CQ_eventSystem_GroupMemberIncrease 103 69 | #define CQ_eventSystem_GroupBan 104 70 | #define CQ_eventFriend_Add 201 71 | #define CQ_eventRequest_AddFriend 301 72 | #define CQ_eventRequest_AddGroup 302 73 | #define CQ_eventStartup 1001 74 | #define CQ_eventExit 1002 75 | #define CQ_eventEnable 1003 76 | #define CQ_eventDisable 1004 -------------------------------------------------------------------------------- /CQXQ/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ 生成的包含文件。 3 | // 供 Resource.rc 使用 4 | // 5 | #define IDR_CQP1 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CQXQ 2 | 让先驱QQ机器人支持酷Q插件 3 | 4 | ## 使用方法: 5 | 1. 将CQXQ.XQ.dll放入Plugin文件夹 6 | 2. 创建CQPlugins文件夹,将CQ插件的dll和json放入 7 | 3. 启动机器人,在插件管理启动CQXQ,在CQXQ管理页面配置插件 8 | 4. 开始使用 9 | 10 | ## 如何获取酷Q插件 11 | **联系作者提供DLL和JSON文件** 12 | (或者你可以提供通过CPK解包后的DLL文件让溯洄恢复……(纯粹看溯洄心情)) 13 | 14 | ## 为什么酷Q插件不在XQ插件列表中显示 15 | 因为就是不会显示,管理CQ插件请右键CQXQ点设置插件 16 | 17 | ## 联系开发者: 18 | ~你可以选择加入开发群 610272090 获取最新资讯(这是开发群,不是你什么都不会然后进去问来问去的群)~ 19 | **没有群** 20 | 21 | ## 开发进度: 22 | 事件 23 | - [x] 加载 24 | - [x] 退出 25 | - [x] 启用 26 | - [x] 禁用 27 | - [x] 群消息 28 | - [x] 私聊消息(好友, 群临时会话, 讨论组临时会话) 29 | - [x] 讨论组消息 30 | - [x] 邀请机器人加入群/其他人被邀请进群/其他人申请进群 31 | - [x] 群成员增加 32 | - [x] 群成员减少 33 | - [x] 群管理变动 34 | - [x] 群文件上传(不支持获取busid) 35 | - [x] 好友已添加 36 | - [x] 禁言/全局禁言 37 | 38 | API 39 | - [x] 获取应用目录 40 | - [x] 发送群消息 41 | - [x] 发送私聊消息(好友, 群临时会话, 讨论组临时会话) 42 | - [x] 发送讨论组消息 43 | - [x] 退出群 44 | - [ ] 退出讨论组(XQAPI未找到对应函数) 45 | - [x] 设置好友添加处理 46 | - [x] 设置群邀请处理/有人加群处理 47 | - [x] 设置群名片 48 | - [ ] 设置群管理员(XQAPI未找到对应函数,但是理论上可以用qinfo.clt.qq.com的接口实现) 49 | - [x] 设置群匿名 50 | - [x] 获取陌生人信息(只支持昵称) 51 | - [x] 获取群成员信息(待优化)(只支持昵称,群名片,最后发言时间,入群时间,群等级名称,权限) 52 | - [x] 获取群信息(待优化) 53 | - [x] 获取好友列表(获取到的昵称一栏实际上是备注或者昵称(有备注就是备注,否则是昵称),获取到的备注一栏为空) 54 | - [x] 获取群列表 55 | - [x] 获取群成员列表(只支持每个群成员的昵称,群名片,最后发言时间,入群时间,群等级名称,权限) 56 | - [x] 获取Cookie 57 | - [x] 获取CsrfToken 58 | - [x] 获取图片下载路径 59 | - [x] 获取录音下载路径 60 | - [ ] 设置群成员头衔(XQAPI未找到对应函数) 61 | - [x] 禁言 62 | - [x] 全局禁言 63 | - [ ] 禁言匿名用户(XQAPI未找到对应函数,但是理论上可以用qqweb.qq.com的接口实现) 64 | - [x] 撤回消息(支持所有撤回,但管理员撤回群员消息需要在收到消息5分钟内撤回,否则消息ID会失效) 65 | 66 | CQ码 67 | - [x] at 68 | - [x] image(file或者URL) 69 | - [x] record(只支持silk文件或者接收到的{GUID}.amr) 70 | - [x] face 71 | - [x] emoji(单向发送,接受还是[emoji=]的格式) 72 | - [x] share(单向发送) 73 | - [ ] contact 74 | - [ ] location 75 | - [ ] show(受先驱限制无法支持) 76 | - [ ] sign(受先驱限制无法支持) 77 | - [ ] rich 78 | - [ ] music 79 | - [ ] rps(受先驱限制无法支持) 80 | - [ ] dice(受先驱限制无法支持) 81 | - [x] anonymous 82 | - [x] shake 83 | - [ ] sface(受先驱限制无法支持) 84 | - [ ] bface(受先驱限制无法支持) 85 | 86 | 87 | GUI及管理 88 | - [x] 启用/禁用插件 89 | - [x] 重载插件 90 | - [x] 插件优先级 91 | - [x] 插件菜单 92 | - [ ] 插件权限管理 93 | - [ ] 悬浮窗 94 | - [ ] cpk解析 95 | -------------------------------------------------------------------------------- /app_id.txt: -------------------------------------------------------------------------------- 1 | CQXQ.XQ -------------------------------------------------------------------------------- /cmake/Modules/FindVcpkgIncludeDir.cmake: -------------------------------------------------------------------------------- 1 | find_path(VCPKG_INCLUDE_DIR iconv.h) 2 | -------------------------------------------------------------------------------- /cmake/Modules/FixDebugLibraryLookup.cmake: -------------------------------------------------------------------------------- 1 | if(CMAKE_BUILD_TYPE MATCHES "^Debug$") 2 | # fix Vcpkg debug library lookup bug, see https://github.com/Microsoft/vcpkg/issues/1626 3 | list(APPEND CMAKE_IGNORE_PATH ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib) 4 | endif() 5 | -------------------------------------------------------------------------------- /cmake/Modules/FixLinkConflict.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:LIBC /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:LIBCD /NODEFAULTLIB:LIBCMTD /NODEFAULTLIB:MSVCRT") 2 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /NODEFAULTLIB:LIBC /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:LIBCD /NODEFAULTLIB:LIBCMTD /NODEFAULTLIB:MSVCRTD") 3 | -------------------------------------------------------------------------------- /cmake/x86-windows-static-custom.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x86) 2 | set(VCPKG_CRT_LINKAGE static) 3 | set(VCPKG_LIBRARY_LINKAGE static) -------------------------------------------------------------------------------- /dummyCQP/native.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "native.h" 7 | 8 | using namespace std; 9 | 10 | #define XQAPI(ReturnType, Name, Size, ...) using Name##_FUNC = std::function; \ 11 | Name##_FUNC _##Name; \ 12 | using Name##_TYPE = ReturnType (__stdcall*)(__VA_ARGS__); \ 13 | __pragma(comment(linker, "/EXPORT:" #Name "=_" #Name "@" #Size)) \ 14 | extern "C" __declspec(dllexport) ReturnType __stdcall Name(__VA_ARGS__) 15 | 16 | XQAPI(int32_t, CQ_canSendImage, 4, int32_t plugin_id) 17 | { 18 | return _CQ_canSendImage(plugin_id); 19 | } 20 | 21 | XQAPI(int32_t, CQ_canSendRecord, 4, int32_t plugin_id) 22 | { 23 | return _CQ_canSendRecord(plugin_id); 24 | } 25 | 26 | XQAPI(int32_t, CQ_sendPrivateMsg, 16, int32_t plugin_id, int64_t account, const char* msg) 27 | { 28 | return _CQ_sendPrivateMsg(plugin_id, account, msg); 29 | } 30 | 31 | XQAPI(int32_t, CQ_sendGroupMsg, 16, int32_t plugin_id, int64_t group, const char* msg) 32 | { 33 | return _CQ_sendGroupMsg(plugin_id, group, msg); 34 | } 35 | 36 | XQAPI(int32_t, CQ_setFatal, 8, int32_t plugin_id, const char* info) 37 | { 38 | return _CQ_setFatal(plugin_id, info); 39 | } 40 | 41 | XQAPI(const char*, CQ_getAppDirectory, 4, int32_t plugin_id) 42 | { 43 | return _CQ_getAppDirectory(plugin_id); 44 | } 45 | 46 | XQAPI(int64_t, CQ_getLoginQQ, 4, int32_t plugin_id) 47 | { 48 | return _CQ_getLoginQQ(plugin_id); 49 | } 50 | 51 | XQAPI(const char*, CQ_getLoginNick, 4, int32_t plugin_id) 52 | { 53 | return _CQ_getLoginNick(plugin_id); 54 | } 55 | 56 | XQAPI(int32_t, CQ_setGroupAnonymous, 16, int32_t plugin_id, int64_t group, BOOL enable) 57 | { 58 | return _CQ_setGroupAnonymous(plugin_id, group, enable); 59 | } 60 | 61 | XQAPI(int32_t, CQ_setGroupBan, 28, int32_t plugin_id, int64_t group, int64_t member, int64_t duration) 62 | { 63 | return _CQ_setGroupBan(plugin_id, group, member, duration); 64 | } 65 | 66 | XQAPI(int32_t, CQ_setGroupCard, 24, int32_t plugin_id, int64_t group, int64_t member, const char* card) 67 | { 68 | return _CQ_setGroupCard(plugin_id, group, member, card); 69 | } 70 | 71 | XQAPI(int32_t, CQ_setGroupKick, 24, int32_t plugin_id, int64_t group, int64_t member, BOOL reject) 72 | { 73 | return _CQ_setGroupKick(plugin_id, group, member, reject); 74 | } 75 | 76 | XQAPI(int32_t, CQ_setGroupLeave, 16, int32_t plugin_id, int64_t group, BOOL dismiss) 77 | { 78 | return _CQ_setGroupLeave(plugin_id, group, dismiss); 79 | } 80 | 81 | XQAPI(int32_t, CQ_setGroupSpecialTitle, 32, int32_t plugin_id, int64_t group, int64_t member, 82 | const char* title, int64_t duration) 83 | { 84 | return _CQ_setGroupSpecialTitle(plugin_id, group, member, title, duration); 85 | } 86 | 87 | XQAPI(int32_t, CQ_setGroupWholeBan, 16, int32_t plugin_id, int64_t group, BOOL enable) 88 | { 89 | return _CQ_setGroupWholeBan(plugin_id, group, enable); 90 | } 91 | 92 | XQAPI(int32_t, CQ_deleteMsg, 12, int32_t plugin_id, int64_t msg_id) 93 | { 94 | return _CQ_deleteMsg(plugin_id, msg_id); 95 | } 96 | 97 | XQAPI(const char*, CQ_getFriendList, 8, int32_t plugin_id, BOOL reserved) 98 | { 99 | return _CQ_getFriendList(plugin_id, reserved); 100 | } 101 | 102 | XQAPI(const char*, CQ_getGroupInfo, 16, int32_t plugin_id, int64_t group, BOOL cache) 103 | { 104 | return _CQ_getGroupInfo(plugin_id, group, cache); 105 | } 106 | 107 | XQAPI(const char*, CQ_getGroupList, 4, int32_t plugin_id) 108 | { 109 | return _CQ_getGroupList(plugin_id); 110 | } 111 | 112 | XQAPI(const char*, CQ_getGroupMemberInfoV2, 24, int32_t plugin_id, int64_t group, int64_t account, BOOL cache) 113 | { 114 | return _CQ_getGroupMemberInfoV2(plugin_id, group, account, cache); 115 | } 116 | 117 | XQAPI(const char*, CQ_getGroupMemberList, 12, int32_t plugin_id, int64_t group) 118 | { 119 | return _CQ_getGroupMemberList(plugin_id, group); 120 | } 121 | 122 | XQAPI(const char*, CQ_getCookiesV2, 8, int32_t plugin_id, const char* domain) 123 | { 124 | return _CQ_getCookiesV2(plugin_id, domain); 125 | } 126 | 127 | XQAPI(const char*, CQ_getCsrfToken, 4, int32_t plugin_id) 128 | { 129 | return _CQ_getCsrfToken(plugin_id); 130 | } 131 | 132 | XQAPI(const char*, CQ_getImage, 8, int32_t plugin_id, const char* image) 133 | { 134 | return _CQ_getImage(plugin_id, image); 135 | } 136 | 137 | XQAPI(const char*, CQ_getRecordV2, 12, int32_t plugin_id, const char* file, const char* format) 138 | { 139 | return _CQ_getRecordV2(plugin_id, file, format); 140 | } 141 | 142 | XQAPI(const char*, CQ_getStrangerInfo, 16, int32_t plugin_id, int64_t account, BOOL cache) 143 | { 144 | return _CQ_getStrangerInfo(plugin_id, account, cache); 145 | } 146 | 147 | XQAPI(int32_t, CQ_sendDiscussMsg, 16, int32_t plugin_id, int64_t group, const char* msg) 148 | { 149 | return _CQ_sendDiscussMsg(plugin_id, group, msg); 150 | } 151 | 152 | XQAPI(int32_t, CQ_sendLikeV2, 16, int32_t plugin_id, int64_t account, int32_t times) 153 | { 154 | return _CQ_sendLikeV2(plugin_id, account, times); 155 | } 156 | 157 | XQAPI(int32_t, CQ_setDiscussLeave, 12, int32_t plugin_id, int64_t group) 158 | { 159 | return _CQ_setDiscussLeave(plugin_id, group); 160 | } 161 | 162 | XQAPI(int32_t, CQ_setFriendAddRequest, 16, int32_t plugin_id, const char* id, int32_t type, const char* remark) 163 | { 164 | return _CQ_setFriendAddRequest(plugin_id, id, type, remark); 165 | } 166 | 167 | XQAPI(int32_t, CQ_setGroupAddRequestV2, 20, int32_t plugin_id, const char* id, int32_t req_type, int32_t fb_type, 168 | const char* reason) 169 | { 170 | return _CQ_setGroupAddRequestV2(plugin_id, id, req_type, fb_type, reason); 171 | } 172 | 173 | XQAPI(int32_t, CQ_setGroupAdmin, 24, int32_t plugin_id, int64_t group, int64_t account, BOOL admin) 174 | { 175 | return _CQ_setGroupAdmin(plugin_id, group, account, admin); 176 | } 177 | 178 | XQAPI(int32_t, CQ_setGroupAnonymousBan, 24, int32_t plugin_id, int64_t group, const char* id, int64_t duration) 179 | { 180 | return _CQ_setGroupAnonymousBan(plugin_id, group, id, duration); 181 | } 182 | 183 | XQAPI(int32_t, CQ_addLog, 16, int32_t plugin_id, int32_t priority, const char* type, const char* content) 184 | { 185 | return _CQ_addLog(plugin_id, priority, type, content); 186 | } 187 | // Legacy 188 | 189 | XQAPI(const char*, CQ_getCookies, 4, int32_t plugin_id) 190 | { 191 | return _CQ_getCookies(plugin_id); 192 | } 193 | 194 | XQAPI(int32_t, CQ_setGroupAddRequest, 16, int32_t plugin_id, const char* id, int32_t req_type, int32_t fb_type) 195 | { 196 | return _CQ_setGroupAddRequest(plugin_id, id, req_type, fb_type); 197 | } 198 | 199 | XQAPI(int32_t, CQ_sendLike, 12, int32_t plugin_id, int64_t account) 200 | { 201 | return _CQ_sendLike(plugin_id, account); 202 | } 203 | 204 | XQAPI(int32_t, CQ_reload, 4, int32_t plugin_id) 205 | { 206 | return _CQ_reload(plugin_id); 207 | } 208 | 209 | XQAPI(int32_t, isCQXQ, 0) 210 | { 211 | return 1; 212 | } 213 | 214 | HMODULE XQHModule = nullptr; 215 | 216 | BOOL APIENTRY DllMain(HMODULE hModule, 217 | DWORD ul_reason_for_call, 218 | LPVOID lpReserved) 219 | { 220 | switch(ul_reason_for_call) 221 | { 222 | case DLL_PROCESS_ATTACH: 223 | XQHModule = GetModuleHandleA("CQXQ.XQ.dll"); 224 | if (!XQHModule) XQHModule = GetModuleHandleA("CQOQ.OQ.dll"); 225 | if (!XQHModule) throw std::runtime_error("Unable to load compatibility layer"); 226 | _CQ_canSendImage = (CQ_canSendImage_TYPE)GetProcAddress(XQHModule, "CQ_canSendImage"); 227 | _CQ_canSendRecord = (CQ_canSendRecord_TYPE)GetProcAddress(XQHModule, "CQ_canSendRecord"); 228 | _CQ_sendPrivateMsg = (CQ_sendPrivateMsg_TYPE)GetProcAddress(XQHModule, "CQ_sendPrivateMsg"); 229 | _CQ_sendGroupMsg = (CQ_sendGroupMsg_TYPE)GetProcAddress(XQHModule, "CQ_sendGroupMsg"); 230 | _CQ_setFatal = (CQ_setFatal_TYPE)GetProcAddress(XQHModule, "CQ_setFatal"); 231 | _CQ_getAppDirectory = (CQ_getAppDirectory_TYPE)GetProcAddress(XQHModule, "CQ_getAppDirectory"); 232 | _CQ_getLoginQQ = (CQ_getLoginQQ_TYPE)GetProcAddress(XQHModule, "CQ_getLoginQQ"); 233 | _CQ_getLoginNick = (CQ_getLoginNick_TYPE)GetProcAddress(XQHModule, "CQ_getLoginNick"); 234 | _CQ_setGroupAnonymous = (CQ_setGroupAnonymous_TYPE)GetProcAddress(XQHModule, "CQ_setGroupAnonymous"); 235 | _CQ_setGroupBan = (CQ_setGroupBan_TYPE)GetProcAddress(XQHModule, "CQ_setGroupBan"); 236 | _CQ_setGroupCard = (CQ_setGroupCard_TYPE)GetProcAddress(XQHModule, "CQ_setGroupCard"); 237 | _CQ_setGroupKick = (CQ_setGroupKick_TYPE)GetProcAddress(XQHModule, "CQ_setGroupKick"); 238 | _CQ_setGroupLeave = (CQ_setGroupLeave_TYPE)GetProcAddress(XQHModule, "CQ_setGroupLeave"); 239 | _CQ_setGroupSpecialTitle = (CQ_setGroupSpecialTitle_TYPE)GetProcAddress(XQHModule, "CQ_setGroupSpecialTitle"); 240 | _CQ_setGroupWholeBan = (CQ_setGroupWholeBan_TYPE)GetProcAddress(XQHModule, "CQ_setGroupWholeBan"); 241 | _CQ_deleteMsg = (CQ_deleteMsg_TYPE)GetProcAddress(XQHModule, "CQ_deleteMsg"); 242 | _CQ_getFriendList = (CQ_getFriendList_TYPE)GetProcAddress(XQHModule, "CQ_getFriendList"); 243 | _CQ_getGroupInfo = (CQ_getGroupInfo_TYPE)GetProcAddress(XQHModule, "CQ_getGroupInfo"); 244 | _CQ_getGroupList = (CQ_getGroupList_TYPE)GetProcAddress(XQHModule, "CQ_getGroupList"); 245 | _CQ_getGroupMemberInfoV2 = (CQ_getGroupMemberInfoV2_TYPE)GetProcAddress(XQHModule, "CQ_getGroupMemberInfoV2"); 246 | _CQ_getGroupMemberList = (CQ_getGroupMemberList_TYPE)GetProcAddress(XQHModule, "CQ_getGroupMemberList"); 247 | _CQ_getCookiesV2 = (CQ_getCookiesV2_TYPE)GetProcAddress(XQHModule, "CQ_getCookiesV2"); 248 | _CQ_getCsrfToken = (CQ_getCsrfToken_TYPE)GetProcAddress(XQHModule, "CQ_getCsrfToken"); 249 | _CQ_getImage = (CQ_getImage_TYPE)GetProcAddress(XQHModule, "CQ_getImage"); 250 | _CQ_getRecordV2 = (CQ_getRecordV2_TYPE)GetProcAddress(XQHModule, "CQ_getRecordV2"); 251 | _CQ_getStrangerInfo = (CQ_getStrangerInfo_TYPE)GetProcAddress(XQHModule, "CQ_getStrangerInfo"); 252 | _CQ_sendDiscussMsg = (CQ_sendDiscussMsg_TYPE)GetProcAddress(XQHModule, "CQ_sendDiscussMsg"); 253 | _CQ_sendLikeV2 = (CQ_sendLikeV2_TYPE)GetProcAddress(XQHModule, "CQ_sendLikeV2"); 254 | _CQ_setDiscussLeave = (CQ_setDiscussLeave_TYPE)GetProcAddress(XQHModule, "CQ_setDiscussLeave"); 255 | _CQ_setFriendAddRequest = (CQ_setFriendAddRequest_TYPE)GetProcAddress(XQHModule, "CQ_setFriendAddRequest"); 256 | _CQ_setGroupAddRequestV2 = (CQ_setGroupAddRequestV2_TYPE)GetProcAddress(XQHModule, "CQ_setGroupAddRequestV2"); 257 | _CQ_setGroupAdmin = (CQ_setGroupAdmin_TYPE)GetProcAddress(XQHModule, "CQ_setGroupAdmin"); 258 | _CQ_setGroupAnonymousBan = (CQ_setGroupAnonymousBan_TYPE)GetProcAddress(XQHModule, "CQ_setGroupAnonymousBan"); 259 | _CQ_addLog = (CQ_addLog_TYPE)GetProcAddress(XQHModule, "CQ_addLog"); 260 | _CQ_getCookies = (CQ_getCookies_TYPE)GetProcAddress(XQHModule, "CQ_getCookies"); 261 | _CQ_setGroupAddRequest = (CQ_setGroupAddRequest_TYPE)GetProcAddress(XQHModule, "CQ_setGroupAddRequest"); 262 | _CQ_sendLike = (CQ_sendLike_TYPE)GetProcAddress(XQHModule, "CQ_sendLike"); 263 | _CQ_reload = (CQ_reload_TYPE)GetProcAddress(XQHModule, "CQ_reload"); 264 | break; 265 | default: 266 | break; 267 | } 268 | return TRUE; 269 | } -------------------------------------------------------------------------------- /dummyCQP/native.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define GBK (936) 4 | #define CQAPI(ReturnType, Name, Size) __pragma(comment(linker, "/EXPORT:" #Name "=_" #Name "@" #Size))\ 5 | extern "C" __declspec(dllexport) ReturnType __stdcall Name 6 | 7 | typedef int32_t (__stdcall* IntMethod)(); 8 | typedef const char* (__stdcall* StringMethod)(); 9 | typedef int32_t (__stdcall* FuncInitialize)(int32_t); 10 | 11 | typedef int32_t (__stdcall* EvPriMsg)(int32_t, int32_t, int64_t, const char*, int32_t); 12 | typedef int32_t (__stdcall* EvGroupMsg)(int32_t, int32_t, int64_t, int64_t, const char*, const char*, int32_t); 13 | typedef int32_t (__stdcall* EvGroupAdmin)(int32_t, int32_t, int64_t, int64_t); 14 | typedef int32_t (__stdcall* EvGroupMember)(int32_t, int32_t, int64_t, int64_t, int64_t); 15 | typedef int32_t (__stdcall* EvGroupBan)(int32_t, int32_t, int64_t, int64_t, int64_t, int64_t); 16 | typedef int32_t (__stdcall* EvRequestAddGroup)(int32_t, int32_t, int64_t, int64_t, const char*, const char*); 17 | typedef int32_t (__stdcall* EvRequestAddFriend)(int32_t, int32_t, int64_t, const char*, const char*); 18 | typedef int32_t (__stdcall* EvFriendAdd)(int32_t, int32_t, int64_t); 19 | 20 | 21 | #define XQ_Load 12000 22 | #define XQ_Enable 12001 23 | #define XQ_Disable 12002 24 | #define XQ_StartupComplete 10000 25 | #define XQ_Reboot 10001 26 | 27 | #define XQ_FriendMsgEvent 1 28 | #define XQ_GroupMsgEvent 2 29 | #define XQ_GroupTmpMsgEvent 4 30 | 31 | #define XQ_FriendAddReqEvent 101 32 | 33 | #define XQ_GroupInviteReqEvent 214 34 | #define XQ_GroupAddReqEvent 213 35 | #define XQ_GroupInviteOtherReqEvent 215 36 | 37 | #define XQ_GroupMemberIncreaseByApply 212 38 | #define XQ_GroupMemberIncreaseByInvite 219 39 | #define XQ_GroupMemberDecreaseByExit 201 40 | #define XQ_GroupMemberDecreaseByKick 202 41 | 42 | #define XQ_GroupBanEvent 203 43 | #define XQ_GroupUnbanEvent 204 44 | #define XQ_GroupWholeBanEvent 205 45 | #define XQ_GroupWholeUnbanEvent 206 46 | 47 | #define XQ_GroupAdminSet 210 48 | #define XQ_GroupAdminUnset 211 49 | -------------------------------------------------------------------------------- /dummyCQP/native.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "native", "native.vcxproj", "{747CA574-363A-4A4A-A84E-613F8DEB35A3}" 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 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Debug|x64.ActiveCfg = Debug|x64 17 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Debug|x64.Build.0 = Debug|x64 18 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Debug|x86.ActiveCfg = Debug|Win32 19 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Debug|x86.Build.0 = Debug|Win32 20 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Release|x64.ActiveCfg = Release|x64 21 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Release|x64.Build.0 = Release|x64 22 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Release|x86.ActiveCfg = Release|Win32 23 | {747CA574-363A-4A4A-A84E-613F8DEB35A3}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8A955186-D2CF-4650-A562-490076201282} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /dummyCQP/native.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 | {747CA574-363A-4A4A-A84E-613F8DEB35A3} 24 | CQP 25 | 10.0 26 | CQP 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v142 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 | C:\Program Files %28x86%29\Java\jdk1.8.0_241\include\win32;C:\Program Files %28x86%29\Java\jdk1.8.0_241\include;$(IncludePath) 76 | C:\Program Files %28x86%29\Java\jdk1.8.0_241\include\win32;C:\Program Files %28x86%29\Java\jdk1.8.0_241\include;$(ReferencePath) 77 | 78 | 79 | true 80 | C:\Program Files\Java\jdk1.8.0_241\include;C:\Program Files\Java\jdk1.8.0_241\include\win32;$(IncludePath) 81 | C:\Program Files\Java\jdk1.8.0_241\include\win32;C:\Program Files\Java\jdk1.8.0_241\include;$(ReferencePath) 82 | 83 | 84 | false 85 | C:\Program Files (x86)\Java\jdk-11.0.7\include\win32;C:\Program Files (x86)\Java\jdk-11.0.7\include;$(IncludePath) 86 | C:\Program Files (x86)\Java\jdk-11.0.7\include\win32;C:\Program Files (x86)\Java\jdk-11.0.7\include;$(ReferencePath) 87 | 88 | 89 | false 90 | C:\Program Files\Java\jdk1.8.0_241\include;C:\Program Files\Java\jdk1.8.0_241\include\win32;$(IncludePath) 91 | C:\Program Files\Java\jdk1.8.0_241\include\win32;C:\Program Files\Java\jdk1.8.0_241\include;$(ReferencePath) 92 | 93 | 94 | 95 | Level3 96 | true 97 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 98 | true 99 | MultiThreadedDebug 100 | 101 | 102 | Console 103 | true 104 | 105 | 106 | 107 | 108 | 109 | 110 | Level3 111 | true 112 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 113 | true 114 | MultiThreadedDebug 115 | 116 | 117 | Console 118 | true 119 | 120 | 121 | 122 | 123 | 124 | 125 | Level3 126 | true 127 | true 128 | true 129 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 130 | true 131 | MaxSpeed 132 | MultiThreaded 133 | 134 | 135 | 136 | 137 | Console 138 | true 139 | true 140 | true 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | Level3 150 | true 151 | true 152 | true 153 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 154 | true 155 | MaxSpeed 156 | MultiThreaded 157 | 158 | 159 | 160 | 161 | Console 162 | true 163 | true 164 | true 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /dummyCQP/native.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 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 | 18 | 19 | 头文件 20 | 21 | 22 | 23 | 24 | 源文件 25 | 26 | 27 | -------------------------------------------------------------------------------- /dummyCQP/native.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/build.ps1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\helpers.ps1" 2 | 3 | Set-Location $projectDir 4 | 5 | # 检查必要命令 6 | 7 | $cmake = Find-CMakeCommand 8 | 9 | if (-Not $cmake) 10 | { 11 | Write-Failure "请先安装 CMake" 12 | SafeExit 1 13 | } 14 | 15 | Write-Host "CMake 路径:$cmake" 16 | 17 | # 检查是否生成 18 | 19 | $configType = if ($args[0]) { $args[0] } else { "Debug" } 20 | 21 | if (-Not (Test-Path ".\build\$configType\ALL_BUILD.vcxproj")) 22 | { 23 | Write-Failure "请先运行 `"generate.ps1 $configType`" 来生成 CMake 构建目录" 24 | SafeExit 1 25 | } 26 | 27 | # CMake 构建 28 | 29 | Write-Host "正在使用 CMake 构建项目……" 30 | 31 | & $cmake --build .\build\$configType --config $configType -- "-p:Platform=x86" 32 | 33 | if ($?) 34 | { 35 | Write-Success "CMake 构建成功" 36 | } 37 | else 38 | { 39 | Write-Failure "CMake 构建失败" 40 | SafeExit 1 41 | } 42 | 43 | # 退出 44 | 45 | SafeExit 0 46 | -------------------------------------------------------------------------------- /scripts/clean.ps1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\helpers.ps1" 2 | 3 | Set-Location $projectDir 4 | 5 | if (Test-Path .\build) 6 | { 7 | Remove-Item -Recurse -Force -Path .\build 8 | } 9 | 10 | SafeExit 0 11 | -------------------------------------------------------------------------------- /scripts/generate.ps1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\helpers.ps1" 2 | 3 | Set-Location $projectDir 4 | 5 | # 检查必要命令 6 | 7 | $cmake = Find-CMakeCommand 8 | 9 | if (-Not $cmake) 10 | { 11 | Write-Failure "请先安装 CMake" 12 | SafeExit 1 13 | } 14 | 15 | Write-Host "CMake 路径:$cmake" 16 | 17 | # CMake 生成 18 | 19 | $configType = if ($args[0]) { $args[0] } else { "Debug" } 20 | 21 | New-Item -Path .\build\$configType -ItemType Directory -ErrorAction SilentlyContinue 22 | Set-Location .\build\$configType 23 | 24 | $vcpkgRoot = if ($env:VCPKG_ROOT) { $env:VCPKG_ROOT } else { "$projectDir\vcpkg" } 25 | $vcpkgTriplet = if ($env:VCPKG_TRIPLET) { $env:VCPKG_TRIPLET } else { "x86-windows-static-custom" } 26 | 27 | if (-Not (Test-Path "$vcpkgRoot")) 28 | { 29 | Write-Failure "Vcpkg 根目录不存在,请检查 vcpkg 是否正确安装和配置" 30 | SafeExit 1 31 | } 32 | 33 | Write-Host "正在使用 CMake 生成构建目录……" 34 | 35 | & $cmake ` 36 | -DCMAKE_TOOLCHAIN_FILE="$vcpkgRoot\scripts\buildsystems\vcpkg.cmake" ` 37 | -DVCPKG_TARGET_TRIPLET="$vcpkgTriplet" ` 38 | -DCMAKE_CONFIGURATION_TYPES="$configType" ` 39 | -DCMAKE_BUILD_TYPE="$configType" ` 40 | "$projectDir" 41 | 42 | if ($?) 43 | { 44 | Write-Success "CMake 生成成功" 45 | } 46 | else 47 | { 48 | Write-Failure "CMake 生成失败" 49 | SafeExit 1 50 | } 51 | 52 | # 退出 53 | 54 | SafeExit 0 55 | -------------------------------------------------------------------------------- /scripts/helpers.ps1: -------------------------------------------------------------------------------- 1 | $originDir = Get-Location 2 | $projectDir = Split-Path $PSScriptRoot -Parent 3 | 4 | $vcpkgRoot = if ($env:VCPKG_ROOT) { $env:VCPKG_ROOT } else { "$projectDir\vcpkg" } 5 | $vcpkgCmd = "$vcpkgRoot\vcpkg.exe" 6 | $vcpkgTriplet = if ($env:VCPKG_TRIPLET) { $env:VCPKG_TRIPLET } else { "x86-windows-static-custom" } 7 | 8 | # 辅助函数 9 | 10 | function Write-Success 11 | { 12 | param ($InputObject) 13 | Write-Host "$InputObject" -ForegroundColor Green 14 | } 15 | 16 | function Write-Failure 17 | { 18 | param ($InputObject) 19 | Write-Host "$InputObject" -ForegroundColor Red 20 | } 21 | 22 | function SafeExit 23 | { 24 | param ($ExitCode) 25 | Set-Location $originDir 26 | exit $ExitCode 27 | } 28 | 29 | function Find-CMakeCommand 30 | { 31 | $cmd = Get-Command -Name cmake -ErrorAction SilentlyContinue 32 | if ($cmd) { return $cmd } 33 | 34 | # try CMake built in with Visual Studio 35 | $vsInstances = Get-ChildItem "C:\Program Files (x86)\Microsoft Visual Studio" -Filter 20?? | Sort-Object -Descending 36 | foreach ($vs in $vsInstances) 37 | { 38 | $vsPath = $vs.Fullname 39 | $cmd = Get-ChildItem "$vsPath\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" -ErrorAction SilentlyContinue 40 | if ($cmd) { return $cmd } 41 | } 42 | 43 | # try CMake downloaded by vcpkg 44 | $cmd = Get-ChildItem "$projectDir\vcpkg\downloads\tools\cmake-*\*\bin\cmake.exe" | Sort-Object -Descending 45 | if ($cmd) { return $cmd[0] } 46 | } 47 | -------------------------------------------------------------------------------- /scripts/post_build.ps1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\helpers.ps1" 2 | 3 | $appId = $args[0] 4 | $libName = $args[1] 5 | $outDir = $args[2] 6 | 7 | $appOutDir = "$outDir\$appId" 8 | New-Item -Path $appOutDir -ItemType Directory -ErrorAction SilentlyContinue 9 | 10 | $dllName = "$libName.dll" 11 | $dllOutPath = "$appOutDir\$libName.dll" 12 | 13 | Copy-Item -Force $outDir\$dllName $dllOutPath 14 | 15 | if (Test-Path "$PSScriptRoot\install.ps1") 16 | { 17 | Write-Host "正在运行安装脚本 `"install.ps1 $args`"……" 18 | powershell.exe -ExecutionPolicy Bypass -NoProfile -File "$PSScriptRoot\install.ps1" $appId $libName $appOutDir 19 | } 20 | 21 | SafeExit 0 22 | -------------------------------------------------------------------------------- /scripts/prepare.ps1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\helpers.ps1" 2 | 3 | # 切换目录 4 | 5 | Set-Location $projectDir 6 | Write-Host "当前工程目录:$projectDir" 7 | 8 | # 检查必要命令 9 | 10 | if (-Not (Get-Command -Name git -ErrorAction SilentlyContinue)) 11 | { 12 | Write-Failure "请先安装 Git,并确保 git 命令已添加到 PATH 环境变量" 13 | SafeExit 1 14 | } 15 | 16 | # 检查 vcpkg 17 | 18 | if (-Not (Test-Path $vcpkgCmd)) 19 | { 20 | Write-Host "Vcpkg 未安装,即将安装……" 21 | Remove-Item -Recurse -Force $vcpkgRoot -ErrorAction SilentlyContinue 22 | 23 | Write-Host "正在克隆 vcpkg 仓库……" 24 | git clone https://github.com/Microsoft/vcpkg.git "$vcpkgRoot" 25 | 26 | Write-Host "正在构建 vcpkg……" 27 | & "$vcpkgRoot\bootstrap-vcpkg.bat" 28 | if ($?) 29 | { 30 | Write-Success "Vcpkg 构建成功" 31 | } 32 | else 33 | { 34 | Write-Failure "Vcpkg 构建失败" 35 | SafeExit 1 36 | } 37 | } 38 | else 39 | { 40 | Write-Success "Vcpkg 已安装" 41 | } 42 | 43 | $vcpkgTripletPath = "$vcpkgRoot\triplets\$vcpkgTriplet.cmake" 44 | 45 | if (-Not (Test-Path $vcpkgTripletPath)) 46 | { 47 | Write-Host "正在创建 vcpkg triplet……" 48 | Copy-Item "$projectDir\cmake\$vcpkgTriplet.cmake" $vcpkgTripletPath 49 | } 50 | Write-Success "Vcpkg triplet 已创建" 51 | 52 | # 检查依赖 53 | 54 | Write-Host "正在检查依赖……" 55 | Set-Location $vcpkgRoot 56 | 57 | $vcpkgCommit = Get-Content "$projectDir\vcpkg-commit.txt" -ErrorAction SilentlyContinue 58 | if ($vcpkgCommit) 59 | { 60 | git fetch --depth=1 origin $vcpkgCommit 61 | git checkout $vcpkgCommit -- ports 62 | if ($?) 63 | { 64 | Write-Success "Vcpkg 包版本已固定到 $vcpkgCommit" 65 | } 66 | else 67 | { 68 | Write-Failure "固定 vcpkg 包版本失败" 69 | SafeExit 1 70 | } 71 | } 72 | 73 | & "$vcpkgCmd" remove --outdated 74 | if ($?) { Write-Success "已移除过时的依赖" } 75 | 76 | foreach ($package in Get-Content "$projectDir\vcpkg-requirements.txt") 77 | { 78 | $package = $package.trim() 79 | if ($package) 80 | { 81 | Write-Host "正在安装依赖 $package……" 82 | & "$vcpkgCmd" --vcpkg-root "$vcpkgRoot" --triplet $vcpkgTriplet install $package 83 | if ($?) 84 | { 85 | Write-Success "依赖 $package 已安装" 86 | } 87 | else 88 | { 89 | Write-Failure "依赖 $package 安装失败" 90 | SafeExit 1 91 | } 92 | } 93 | } 94 | 95 | Write-Success "构建环境准备完成" 96 | 97 | # 退出 98 | 99 | SafeExit 0 100 | -------------------------------------------------------------------------------- /scripts/vcpkg.ps1: -------------------------------------------------------------------------------- 1 | . "$PSScriptRoot\helpers.ps1" 2 | 3 | & "$vcpkgCmd" --vcpkg-root "$vcpkgRoot" --triplet $vcpkgTriplet $args 4 | -------------------------------------------------------------------------------- /vcpkg-requirements.txt: -------------------------------------------------------------------------------- 1 | libiconv 2 | nlohmann-json --------------------------------------------------------------------------------