Add chat_open, chat_send, chat_wait and chat_close to the MCP tool set, behind a new McpChat switch that defaults to off and is independent of the read-only switch. The tools reuse the existing client chat link: the main connection sends COMMAND_CHAT, the client opens a sub-connection with TOKEN_CONN_AUTH and TOKEN_CHAT_START, the server answers with COMMAND_NEXT_CHAT, and both directions then carry raw CP936 text. Sessions are per host and held in shared pointers under their own mutex, so a waiting chat_wait survives a close or a dropped sub-connection, and chat_send is serialised per session. Text is converted strictly between UTF-8 and CP936: invalid UTF-8, characters GBK cannot represent (emoji, accented letters) and messages over the client's 4000-byte buffer are rejected with -32602 instead of being mangled. A one-second settle delay after the sub-connection binds keeps the first message from reaching the client before its chat window exists. The routing hooks run before the host-management hooks in MessageHandle and clear the route in OfflineProc before the context leaves the list. A five-second tombstone absorbs a late TOKEN_CHAT_START after a timeout, so a failed chat_open does not pop the MFC chat dialog. Idle sessions are swept on every tool call, and audit entries record the session id and byte counts only, never message bodies. The settings dialog gains the chat checkbox and the translated labels for all three languages, and the strict UTF-8 to CP936 helpers move from McpMachine.cpp to McpInternal so both tool families can share them. Co-Authored-By: deepseek-flash
211 lines
6.8 KiB
C++
211 lines
6.8 KiB
C++
#include "stdafx.h"
|
||
#include "McpInternal.h"
|
||
#include "context.h" // context 接口(GetClientID/IsLogin)
|
||
#include "2015RemoteDlg.h" // CMy2015RemoteDlg 成员(m_HostList/m_cs)+ WM_SHOWERRORMSG
|
||
#include "LangManager.h" // _TR
|
||
|
||
#ifndef _WIN64
|
||
#ifdef _DEBUG
|
||
#pragma comment(lib, "jsoncpp/jsoncppd.lib")
|
||
#else
|
||
#pragma comment(lib, "jsoncpp/jsoncpp.lib")
|
||
#endif
|
||
#else
|
||
#ifdef _DEBUG
|
||
#pragma comment(lib, "jsoncpp/jsoncpp_x64d.lib")
|
||
#else
|
||
#pragma comment(lib, "jsoncpp/jsoncpp_x64.lib")
|
||
#endif
|
||
#endif
|
||
|
||
namespace McpInternal {
|
||
|
||
std::string JsonToString(const Json::Value& v) {
|
||
Json::StreamWriterBuilder b;
|
||
b["indentation"] = "";
|
||
return Json::writeString(b, v);
|
||
}
|
||
|
||
std::string BuildResult(const Json::Value& id, const Json::Value& result) {
|
||
Json::Value resp(Json::objectValue);
|
||
resp["jsonrpc"] = "2.0";
|
||
resp["id"] = id;
|
||
resp["result"] = result;
|
||
return JsonToString(resp);
|
||
}
|
||
|
||
std::string BuildError(const Json::Value& id, int code, const std::string& msg) {
|
||
Json::Value resp(Json::objectValue);
|
||
resp["jsonrpc"] = "2.0";
|
||
resp["id"] = id;
|
||
Json::Value err(Json::objectValue);
|
||
err["code"] = code;
|
||
err["message"] = msg;
|
||
resp["error"] = err;
|
||
return JsonToString(resp);
|
||
}
|
||
|
||
std::string ToLowerAscii(const std::string& s) {
|
||
std::string r = s;
|
||
for (char& c : r) if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
|
||
return r;
|
||
}
|
||
|
||
bool ContainsCI(const std::string& haystack, const std::string& needle) {
|
||
if (needle.empty()) return true;
|
||
return ToLowerAscii(haystack).find(ToLowerAscii(needle)) != std::string::npos;
|
||
}
|
||
|
||
std::string JsonStrField(const Json::Value& v, const char* key) {
|
||
if (v.isObject() && v.isMember(key) && v[key].isString())
|
||
return v[key].asString();
|
||
return "";
|
||
}
|
||
|
||
bool IsDigits(const std::string& s) {
|
||
if (s.empty()) return false;
|
||
for (char c : s) if (c < '0' || c > '9') return false;
|
||
return true;
|
||
}
|
||
|
||
std::string GetStringArg(const Json::Value& args, const char* key) {
|
||
return JsonStrField(args, key);
|
||
}
|
||
|
||
bool GetIntArg(const Json::Value& args, const char* key, int& out) {
|
||
if (!args.isMember(key)) return false;
|
||
const Json::Value& v = args[key];
|
||
if (v.isNumeric()) { out = v.asInt(); return true; }
|
||
if (v.isString()) {
|
||
const std::string s = v.asString();
|
||
if (IsDigits(s) || (!s.empty() && s[0] == '-' && IsDigits(s.substr(1)))) {
|
||
out = atoi(s.c_str());
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool ReadUInt64(const Json::Value& v, uint64_t& out) {
|
||
if (v.isNumeric()) { out = v.asUInt64(); return true; }
|
||
if (v.isString()) {
|
||
std::string s = v.asString();
|
||
if (IsDigits(s)) { out = strtoull(s.c_str(), nullptr, 10); return true; }
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool ReadUInt64Arg(const Json::Value& args, const char* key, uint64_t& out) {
|
||
if (!args.isMember(key)) return false;
|
||
return ReadUInt64(args[key], out);
|
||
}
|
||
|
||
bool ParseHostIdArg(const Json::Value& args, uint64_t& out, std::string& err) {
|
||
std::string sid = GetStringArg(args, "id");
|
||
if (sid.empty()) {
|
||
err = "Missing required parameter: id";
|
||
return false;
|
||
}
|
||
if (!IsDigits(sid)) {
|
||
err = "Invalid id: expected a decimal host id string";
|
||
return false;
|
||
}
|
||
out = strtoull(sid.c_str(), nullptr, 10);
|
||
return true;
|
||
}
|
||
|
||
context* FindMainContext(CMy2015RemoteDlg* parent, uint64_t id) {
|
||
if (!parent) return nullptr;
|
||
context* found = nullptr;
|
||
EnterCriticalSection(&parent->m_cs);
|
||
for (context* ctx : parent->m_HostList) {
|
||
if (ctx && ctx->GetClientID() == id && ctx->IsLogin()) {
|
||
found = ctx;
|
||
break;
|
||
}
|
||
}
|
||
LeaveCriticalSection(&parent->m_cs);
|
||
return found;
|
||
}
|
||
|
||
size_t BoundedStrlen(const char* p, size_t avail) {
|
||
size_t n = 0;
|
||
while (n < avail && p[n]) ++n;
|
||
return n;
|
||
}
|
||
|
||
std::string ToUtf8(const char* s, UINT cp) {
|
||
if (!s || !*s) return "";
|
||
int wlen = MultiByteToWideChar(cp, 0, s, -1, NULL, 0);
|
||
if (wlen <= 0) return "";
|
||
std::wstring w(wlen - 1, L'\0');
|
||
MultiByteToWideChar(cp, 0, s, -1, &w[0], wlen);
|
||
int u8len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, NULL, 0, NULL, NULL);
|
||
if (u8len <= 0) return "";
|
||
std::string out(u8len - 1, '\0');
|
||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &out[0], u8len, NULL, NULL);
|
||
return out;
|
||
}
|
||
|
||
std::string ToAnsi(const std::string& utf8, UINT cp) {
|
||
if (utf8.empty()) return "";
|
||
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), NULL, 0);
|
||
if (wlen <= 0) return utf8;
|
||
std::wstring w(wlen, L'\0');
|
||
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), &w[0], wlen);
|
||
int alen = WideCharToMultiByte(cp, 0, w.c_str(), wlen, NULL, 0, NULL, NULL);
|
||
if (alen <= 0) return utf8;
|
||
std::string out(alen, '\0');
|
||
WideCharToMultiByte(cp, 0, w.c_str(), wlen, &out[0], alen, NULL, NULL);
|
||
return out;
|
||
}
|
||
|
||
bool Utf8ToCpExact(const std::string& utf8, UINT cp, std::string& out) {
|
||
out.clear();
|
||
if (utf8.empty()) return true;
|
||
int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), (int)utf8.size(), NULL, 0);
|
||
if (wlen <= 0) return false;
|
||
std::wstring w(wlen, L'\0');
|
||
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.c_str(), (int)utf8.size(),
|
||
&w[0], wlen) != wlen)
|
||
return false;
|
||
|
||
BOOL usedDefault = FALSE;
|
||
int alen = WideCharToMultiByte(cp, WC_NO_BEST_FIT_CHARS, w.c_str(), wlen, NULL, 0, NULL, &usedDefault);
|
||
if (alen <= 0 || usedDefault) return false;
|
||
out.assign(alen, '\0');
|
||
usedDefault = FALSE;
|
||
if (WideCharToMultiByte(cp, WC_NO_BEST_FIT_CHARS, w.c_str(), wlen, &out[0], alen,
|
||
NULL, &usedDefault) != alen || usedDefault) {
|
||
out.clear();
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool IsValidUtf8(const char* s, size_t n) {
|
||
if (!s || n == 0) return true;
|
||
int wlen = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, (int)n, NULL, 0);
|
||
return wlen > 0;
|
||
}
|
||
|
||
void AuditManageAction(CMy2015RemoteDlg* parent, uint64_t devId,
|
||
const std::string& tool, const std::string& detail) {
|
||
if (!parent) return;
|
||
std::string text = "host " + std::to_string(devId) + " " + tool + ": " + detail;
|
||
parent->PostMessageA(WM_SHOWERRORMSG,
|
||
(WPARAM)new CString(ToAnsi(text, 936).c_str()),
|
||
(LPARAM)new CString(_TR("MCP管理操作")));
|
||
}
|
||
|
||
void AuditChatAction(CMy2015RemoteDlg* parent, uint64_t devId,
|
||
const std::string& tool, const std::string& detail) {
|
||
if (!parent) return;
|
||
std::string text = "host " + std::to_string(devId) + " " + tool + ": " + detail;
|
||
parent->PostMessageA(WM_SHOWERRORMSG,
|
||
(WPARAM)new CString(ToAnsi(text, 936).c_str()),
|
||
(LPARAM)new CString(_TR("MCP远程交谈")));
|
||
}
|
||
|
||
} // namespace McpInternal
|