Chat : un simple apostrophe dans un chuchotement a un bot faisait tomber le royaume
Quand un joueur chuchote a un playerbot, le core cherche une reponse toute faite
dans ai_talk_whisper et injectait le message BRUT dans la requete :
WHERE '%s' REGEXP cname
Une apostrophe - « c est », « j ai », une phrase de francais sur deux - cassait
la requete. MySQLConnection::_HandleMySQLErrno repond a une erreur SQL par un
abandon du processus : n importe quel joueur faisait donc tomber le serveur en
adressant la parole a un bot, et pouvait y injecter du SQL au passage.
Trois crashs SIGSEGV en une soiree (23h06, 23h44, 23h58), tous avec la meme pile
HandleChatMessage -> DatabaseWorkerPool::Query -> _HandleMySQLErrno -> Abort. Le
defaut preexistait, mais le module de dialogue l a reveille : jusqu ici personne
n adressait la parole aux playerbots.
Le message est desormais echappe et borne a 255 caracteres.
Second changement, sans rapport : Mistral devient un fournisseur a part entiere.
Il parlait deja le dialecte d OpenAI, mais « mistral » n etait qu un alias : sans
adresse explicite, la cle du joueur partait chez OpenAI, qui la rejetait par un
401 incomprehensible. Il a maintenant son adresse par defaut, et l aide en jeu
signale que Gemini ne repond pas depuis un serveur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -335,12 +335,20 @@ void WorldSession::HandleChatMessage(ChatMsg type, uint32 lang, std::string msg,
|
||||
LocaleConstant locale = sender->GetSession()->GetSessionDbcLocale();
|
||||
QueryResult result;
|
||||
|
||||
// SylvaniaCore : le message etait injecte brut dans la requete. Une
|
||||
// apostrophe - "c'est", "j'ai" - la cassait, et le core repond a
|
||||
// une erreur SQL par un abandon du processus : n importe quel joueur
|
||||
// faisait tomber le royaume en chuchotant a un playerbot, et pouvait
|
||||
// y injecter du SQL. On echappe, et on borne la longueur au passage.
|
||||
std::string safeMsg = msg.size() > 255 ? msg.substr(0, 255) : msg;
|
||||
WorldDatabase.EscapeString(safeMsg);
|
||||
|
||||
// Search for locale-specific response first
|
||||
result = WorldDatabase.PQuery(
|
||||
"SELECT `reply` FROM `ai_talk_whisper_locale` "
|
||||
"WHERE locale = %u AND '%s' REGEXP cname "
|
||||
"ORDER BY RAND() LIMIT 1",
|
||||
locale, msg.c_str()
|
||||
locale, safeMsg.c_str()
|
||||
);
|
||||
|
||||
// If nothing is found, English fallback
|
||||
@@ -350,7 +358,7 @@ void WorldSession::HandleChatMessage(ChatMsg type, uint32 lang, std::string msg,
|
||||
"SELECT `reply` FROM `ai_talk_whisper` "
|
||||
"WHERE '%s' REGEXP cname "
|
||||
"ORDER BY RAND() LIMIT 1",
|
||||
msg.c_str()
|
||||
safeMsg.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace
|
||||
char const* OPENAI_DEFAULT_URL = "https://api.openai.com/v1/chat/completions";
|
||||
char const* ANTHROPIC_DEFAULT_URL = "https://api.anthropic.com/v1/messages";
|
||||
char const* GEMINI_DEFAULT_HOST = "https://generativelanguage.googleapis.com/v1beta/models/";
|
||||
char const* MISTRAL_DEFAULT_URL = "https://api.mistral.ai/v1/chat/completions";
|
||||
|
||||
std::string JsonEscape(std::string const& text)
|
||||
{
|
||||
@@ -272,6 +273,7 @@ char const* MercenaryChatMgr::GetProviderName(uint8 provider)
|
||||
case MERC_LLM_OPENAI: return "OpenAI";
|
||||
case MERC_LLM_ANTHROPIC: return "Anthropic";
|
||||
case MERC_LLM_GEMINI: return "Gemini";
|
||||
case MERC_LLM_MISTRAL: return "Mistral";
|
||||
default: return "inconnu";
|
||||
}
|
||||
}
|
||||
@@ -281,8 +283,10 @@ uint8 MercenaryChatMgr::ParseProvider(std::string const& name)
|
||||
std::string lowered = name;
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(), ::tolower);
|
||||
|
||||
if (lowered == "mistral" || lowered == "lechat")
|
||||
return MERC_LLM_MISTRAL;
|
||||
if (lowered == "openai" || lowered == "compatible" || lowered == "openrouter"
|
||||
|| lowered == "mistral" || lowered == "groq" || lowered == "ollama")
|
||||
|| lowered == "groq" || lowered == "ollama" || lowered == "cerebras")
|
||||
return MERC_LLM_OPENAI;
|
||||
if (lowered == "anthropic" || lowered == "claude")
|
||||
return MERC_LLM_ANTHROPIC;
|
||||
@@ -336,9 +340,10 @@ bool MercenaryChatMgr::HandleApiCommand(Player* owner, std::string const& argume
|
||||
{
|
||||
handler.PSendSysMessage("|cff00ff00[Mercenaire]|r Pour delier la langue de votre mercenaire, fournissez votre propre cle :");
|
||||
handler.PSendSysMessage(" |cffffd100!api <fournisseur> <modele> <cle>|r");
|
||||
handler.PSendSysMessage(" fournisseurs : |cffffd100openai|r, |cffffd100anthropic|r, |cffffd100gemini|r");
|
||||
handler.PSendSysMessage(" exemple : |cffffd100!api anthropic claude-sonnet-5 sk-ant-...|r");
|
||||
handler.PSendSysMessage(" service compatible OpenAI : ajoutez l'adresse en fin de ligne.");
|
||||
handler.PSendSysMessage(" fournisseurs : |cffffd100mistral|r, |cffffd100anthropic|r, |cffffd100openai|r, |cffffd100gemini|r");
|
||||
handler.PSendSysMessage(" exemple : |cffffd100!api mistral mistral-small-latest VOTRE_CLE|r");
|
||||
handler.PSendSysMessage(" |cffff8800Gemini ne repond pas depuis ce royaume|r (Google refuse les serveurs).");
|
||||
handler.PSendSysMessage(" service compatible OpenAI (Groq, OpenRouter...) : ajoutez l'adresse en fin de ligne.");
|
||||
handler.PSendSysMessage(" |cffffd100!api off|r retire votre cle immediatement.");
|
||||
handler.PSendSysMessage("Votre cle n'est jamais enregistree : elle vit en memoire et disparait a la rupture du contrat ou a votre deconnexion.");
|
||||
return true;
|
||||
@@ -591,6 +596,8 @@ std::string MercenaryChatMgr::BuildUrl(MercenaryChatRequest const& request)
|
||||
{
|
||||
case MERC_LLM_ANTHROPIC:
|
||||
return ANTHROPIC_DEFAULT_URL;
|
||||
case MERC_LLM_MISTRAL:
|
||||
return MISTRAL_DEFAULT_URL;
|
||||
case MERC_LLM_GEMINI:
|
||||
{
|
||||
// Gemini porte la cle dans l adresse et le modele dans le chemin.
|
||||
@@ -711,21 +718,41 @@ bool MercenaryChatMgr::Perform(MercenaryChatRequest const& request, std::string&
|
||||
return false;
|
||||
}
|
||||
|
||||
if (httpCode == 401 || httpCode == 403)
|
||||
{
|
||||
error = "Votre cle a ete refusee par le fournisseur.";
|
||||
return false;
|
||||
}
|
||||
if (httpCode == 429)
|
||||
{
|
||||
error = "Votre fournisseur refuse le rythme : trop de demandes.";
|
||||
return false;
|
||||
}
|
||||
if (httpCode >= 400)
|
||||
{
|
||||
// Le fournisseur explique presque toujours son refus dans le corps de la
|
||||
// reponse - modele inconnu, quota depasse, cle sans droits. Renvoyer un
|
||||
// code nu au joueur lui cachait justement la seule information utile.
|
||||
std::string detail;
|
||||
size_t const errorSection = body.find("\"error\"");
|
||||
if (errorSection != std::string::npos)
|
||||
FindJsonString(body, "message", errorSection, detail);
|
||||
|
||||
std::ostringstream text;
|
||||
text << "Le fournisseur a repondu par une erreur (" << httpCode << ").";
|
||||
if (httpCode == 401 || httpCode == 403)
|
||||
text << "Votre cle a ete refusee";
|
||||
else if (httpCode == 404)
|
||||
text << "Modele introuvable pour votre cle";
|
||||
else if (httpCode == 429)
|
||||
text << "Votre fournisseur refuse le rythme";
|
||||
else
|
||||
text << "Le fournisseur a repondu par une erreur";
|
||||
|
||||
text << " (" << httpCode << ")";
|
||||
if (!detail.empty())
|
||||
text << " : " << Flatten(detail, 180);
|
||||
else
|
||||
text << ".";
|
||||
|
||||
error = text.str();
|
||||
|
||||
// Trace de diagnostic : le corps envoye, jamais l adresse - chez Gemini
|
||||
// la cle voyage dans l URL. Le refus d un fournisseur porte presque
|
||||
// toujours sur ce qu on lui a transmis.
|
||||
TC_LOG_ERROR("server.worldserver", "Mercenaires (dialogue): refus %ld du fournisseur %s. Corps envoye : %s",
|
||||
httpCode, GetProviderName(request.provider), payload.c_str());
|
||||
TC_LOG_ERROR("server.worldserver", "Mercenaires (dialogue): reponse recue : %s", body.c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,12 @@ class Player;
|
||||
|
||||
enum MercenaryLlmProvider
|
||||
{
|
||||
MERC_LLM_OPENAI = 0, // et tout service compatible : Mistral, Groq, OpenRouter, Ollama...
|
||||
MERC_LLM_OPENAI = 0, // et tout service compatible : Groq, OpenRouter, Ollama...
|
||||
MERC_LLM_ANTHROPIC = 1,
|
||||
MERC_LLM_GEMINI = 2,
|
||||
// Mistral parle le dialecte d OpenAI : seule son adresse par defaut change.
|
||||
// Il a droit a son propre nom pour que le joueur n ait pas a la coller.
|
||||
MERC_LLM_MISTRAL = 3,
|
||||
MERC_LLM_UNKNOWN = 255
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user