Cuberite
A lightweight, fast and extensible game server for Minecraft
CompositeChat.cpp
Go to the documentation of this file.
1 
2 // CompositeChat.cpp
3 
4 // Implements the cCompositeChat class used to wrap a chat message with multiple parts (text, url, cmd)
5 
6 #include "Globals.h"
7 #include "CompositeChat.h"
8 #include "ClientHandle.h"
9 #include "JsonUtils.h"
10 
11 
12 
13 
14 
16 // cCompositeChat:
17 
19  m_MessageType(mtCustom)
20 {
21 }
22 
23 
24 
25 
26 
27 cCompositeChat::cCompositeChat(const AString & a_ParseText, eMessageType a_MessageType) :
28  m_MessageType(a_MessageType)
29 {
30  ParseText(a_ParseText);
31 }
32 
33 
34 
35 
36 
38 {
39  m_Parts.clear();
40 }
41 
42 
43 
44 
45 
46 void cCompositeChat::AddTextPart(const AString & a_Message, const AString & a_Style)
47 {
48  m_Parts.push_back(TextPart{{ a_Message, a_Style, {} } });
49 }
50 
51 
52 
53 
54 
55 void cCompositeChat::AddClientTranslatedPart(const AString & a_TranslationID, const AStringVector & a_Parameters, const AString & a_Style)
56 {
57  m_Parts.push_back(ClientTranslatedPart{{ a_TranslationID, a_Style, {} }, a_Parameters });
58 }
59 
60 
61 
62 
63 
64 void cCompositeChat::AddUrlPart(const AString & a_Text, const AString & a_Url, const AString & a_Style)
65 {
66  m_Parts.push_back(UrlPart{{ a_Text, a_Style, {} }, a_Url });
67 }
68 
69 
70 
71 
72 
73 void cCompositeChat::AddRunCommandPart(const AString & a_Text, const AString & a_Command, const AString & a_Style)
74 {
75  m_Parts.push_back(RunCommandPart{{{ a_Text, a_Style, {} }, a_Command } });
76 }
77 
78 
79 
80 
81 
82 void cCompositeChat::AddSuggestCommandPart(const AString & a_Text, const AString & a_SuggestedCommand, const AString & a_Style)
83 {
84  m_Parts.push_back(SuggestCommandPart{{{ a_Text, a_Style, {} }, a_SuggestedCommand } });
85 }
86 
87 
88 
89 
90 
91 void cCompositeChat::AddShowAchievementPart(const AString & a_PlayerName, const AString & a_Achievement, const AString & a_Style)
92 {
93  m_Parts.push_back(ShowAchievementPart{{ a_Achievement, a_Style, {} }, a_PlayerName });
94 }
95 
96 
97 
98 
107 void cCompositeChat::ParseText(const AString & a_ParseText)
108 {
109  size_t len = a_ParseText.length();
110  size_t cursor = 0;
111  AString CurrentStyle;
112  AString CurrentText;
113 
114  for (size_t i = 0; i < len; i++)
115  {
116  switch (a_ParseText[i])
117  {
118  case '&': //< Color code
119  {
120  if ((i != 0) && (a_ParseText[i-1] == '\\'))
121  {
122  CurrentText.append(a_ParseText, cursor, i - cursor - 1).append("&");
123  AddTextPart(CurrentText, CurrentStyle);
124  CurrentText.clear();
125  cursor = ++i;
126  continue;
127  }
128 
129  if (cursor < i)
130  {
131  CurrentText.append(a_ParseText, cursor, i - cursor);
132  AddTextPart(CurrentText, CurrentStyle);
133  CurrentText.clear();
134  }
135  i++;
136  cursor = i + 1;
137 
138  if (a_ParseText[i] == 'r')
139  {
140  CurrentStyle = "";
141  }
142  else
143  {
144  CurrentStyle.push_back(a_ParseText[i]);
145  }
146  break;
147  }
148 
149  case ':':
150  {
151  static const constexpr std::array<std::string_view, 2> LinkPrefixes =
152  {
153  {
154  "http",
155  "https"
156  }
157  };
158  for (const auto & Prefix : LinkPrefixes)
159  {
160  size_t PrefixLen = Prefix.size();
161  if (
162  (i >= cursor + PrefixLen) && // There is enough space in front of the colon for the prefix
163  (std::string_view(a_ParseText).substr(i - PrefixLen, PrefixLen) == Prefix) // the prefix matches
164  )
165  {
166  // Add everything before this as a text part:
167  if (i > cursor+ PrefixLen)
168  {
169  CurrentText.append(a_ParseText.c_str() + cursor, i - cursor - PrefixLen);
170  cursor= i - PrefixLen;
171  }
172  if (!CurrentText.empty())
173  {
174  AddTextPart(CurrentText, CurrentStyle);
175  CurrentText.clear();
176  }
177 
178  // Go till the last non-whitespace char in the text:
179  for (; i < len; i++)
180  {
181  if (isspace(a_ParseText[i]))
182  {
183  break;
184  }
185  }
186  AddUrlPart(a_ParseText.substr(cursor, i - cursor), a_ParseText.substr(cursor, i - cursor), CurrentStyle);
187  cursor = i;
188  break;
189  }
190  } // for Prefix - LinkPrefix[]
191  break;
192  } // case ':'
193  } // switch (a_ParseText[i])
194  } // for i - a_ParseText[]
195  if (cursor < len)
196  {
197  CurrentText.clear();
198  CurrentText.append(a_ParseText, cursor, len - cursor);
199  AddTextPart(CurrentText, CurrentStyle);
200  }
201 }
202 
203 
204 
205 
206 
207 void cCompositeChat::SetMessageType(eMessageType a_MessageType, const AString & a_AdditionalMessageTypeData)
208 {
209  m_MessageType = a_MessageType;
210  m_AdditionalMessageTypeData = a_AdditionalMessageTypeData;
211 }
212 
213 
214 
215 
216 
218 {
219  for (auto & Part : m_Parts)
220  {
221  std::visit(OverloadedVariantAccess
222  {
223  [](TextPart & a_Part) { },
224  [](ClientTranslatedPart & a_Part) { },
225  [](UrlPart & a_Part) { a_Part.Style += 'n'; },
226  [](RunCommandPart & a_Part) { },
227  [](SuggestCommandPart & a_Part) { },
228  [](ShowAchievementPart & a_Part) { },
229  }, Part);
230  }
231 }
232 
233 
234 
235 
236 
238 {
239  AString Msg;
240  for (const auto & Part : m_Parts)
241  {
242  std::visit(OverloadedVariantAccess
243  {
244  [&Msg](const TextPart & a_Part) { Msg.append(a_Part.Text); },
245  [&Msg](const ClientTranslatedPart & a_Part) { Msg.append(a_Part.Text); },
246  [&Msg](const UrlPart & a_Part) { Msg.append(a_Part.Url); },
247  [&Msg](const RunCommandPart & a_Part) { Msg.append(a_Part.Text); },
248  [&Msg](const SuggestCommandPart & a_Part) { Msg.append(a_Part.Text); },
249  [ ](const ShowAchievementPart & a_Part) { },
250  }, Part);
251  }
252  return Msg;
253 }
254 
255 
256 
257 
258 
260 {
261  switch (a_MessageType)
262  {
263  case mtCustom: return eLogLevel::Regular;
264  case mtFailure: return eLogLevel::Warning;
265  case mtInformation: return eLogLevel::Info;
266  case mtSuccess: return eLogLevel::Regular;
267  case mtWarning: return eLogLevel::Warning;
268  case mtFatal: return eLogLevel::Error;
269  case mtDeath: return eLogLevel::Regular;
271  case mtJoin: return eLogLevel::Regular;
272  case mtLeave: return eLogLevel::Regular;
273  case mtMaxPlusOne: break;
274  }
275  ASSERT(!"Unhandled MessageType");
276  return eLogLevel::Error;
277 }
278 
279 
280 
281 
282 
283 AString cCompositeChat::CreateJsonString(bool a_ShouldUseChatPrefixes) const
284 {
285  Json::Value Message;
286  Message["text"] = cClientHandle::FormatMessageType(a_ShouldUseChatPrefixes, GetMessageType(), GetAdditionalMessageTypeData()); // The client crashes without this field being present
287  for (const auto & Part : m_Parts)
288  {
289  Json::Value JsonPart;
290  std::visit(OverloadedVariantAccess
291  {
292  [this, &JsonPart](const TextPart & a_Part)
293  {
294  JsonPart["text"] = a_Part.Text;
295  AddChatPartStyle(JsonPart, a_Part.Style);
296  },
297  [this, &JsonPart](const ClientTranslatedPart & a_Part)
298  {
299  JsonPart["translate"] = a_Part.Text;
300  Json::Value With;
301  for (const auto & Parameter : a_Part.Parameters)
302  {
303  With.append(Parameter);
304  }
305  if (!a_Part.Parameters.empty())
306  {
307  JsonPart["with"] = With;
308  }
309  AddChatPartStyle(JsonPart, a_Part.Style);
310  },
311  [this, &JsonPart](const UrlPart & a_Part)
312  {
313  JsonPart["text"] = a_Part.Text;
314  Json::Value Url;
315  Url["action"] = "open_url";
316  Url["value"] = a_Part.Url;
317  JsonPart["clickEvent"] = Url;
318  AddChatPartStyle(JsonPart, a_Part.Style);
319  },
320  [this, &JsonPart](const RunCommandPart & a_Part)
321  {
322  JsonPart["text"] = a_Part.Text;
323  Json::Value Cmd;
324  Cmd["action"] = "run_command";
325  Cmd["value"] = a_Part.Command;
326  JsonPart["clickEvent"] = Cmd;
327  AddChatPartStyle(JsonPart, a_Part.Style);
328  },
329  [this, &JsonPart](const SuggestCommandPart & a_Part)
330  {
331  JsonPart["text"] = a_Part.Text;
332  Json::Value Cmd;
333  Cmd["action"] = "suggest_command";
334  Cmd["value"] = a_Part.Command;
335  JsonPart["clickEvent"] = Cmd;
336  AddChatPartStyle(JsonPart, a_Part.Style);
337  },
338  [this, &JsonPart](const ShowAchievementPart & a_Part)
339  {
340  JsonPart["translate"] = "chat.type.achievement";
341 
342  Json::Value Ach;
343  Ach["action"] = "show_achievement";
344  Ach["value"] = a_Part.Text;
345 
346  Json::Value AchColourAndName;
347  AchColourAndName["color"] = "green";
348  AchColourAndName["translate"] = a_Part.Text;
349  AchColourAndName["hoverEvent"] = Ach;
350 
351  Json::Value Extra;
352  Extra.append(AchColourAndName);
353 
354  Json::Value Name;
355  Name["text"] = a_Part.PlayerName;
356 
357  Json::Value With;
358  With.append(Name);
359  With.append(Extra);
360 
361  JsonPart["with"] = With;
362  AddChatPartStyle(JsonPart, a_Part.Style);
363  },
364  }, Part);
365  Message["extra"].append(JsonPart);
366  } // for itr - Parts[]
367 
368  #if 1
369  // Serialize as machine-readable string (no whitespace):
370  return JsonUtils::WriteFastString(Message);
371  #else
372  // Serialize as human-readable string (pretty-printed):
373  return JsonUtils::WriteStyledString(msg);
374  #endif
375 }
376 
377 
378 
379 
380 
381 void cCompositeChat::AddChatPartStyle(Json::Value & a_Value, const AString & a_PartStyle) const
382 {
383  size_t len = a_PartStyle.length();
384  for (size_t i = 0; i < len; i++)
385  {
386  switch (a_PartStyle[i])
387  {
388  case 'k': a_Value["obfuscated"] = Json::Value(true); break;
389  case 'l': a_Value["bold"] = Json::Value(true); break;
390  case 's': // Deprecated
391  LOGERROR("Value s in AddChatPartStyle() is deprecated");
392  case 'm': a_Value["strikethrough"] = Json::Value(true); break;
393  case 'u': // Deprecated
394  LOGERROR("Value u in AddChatPartStyle() is deprecated");
395  case 'n': a_Value["underlined"] = Json::Value(true); break;
396  case 'i': // Deprecated
397  LOGERROR("Value i in AddChatPartStyle() is deprecated");
398  case 'o': a_Value["italic"] = Json::Value(true); break;
399  case '0': a_Value["color"] = Json::Value("black"); break;
400  case '1': a_Value["color"] = Json::Value("dark_blue"); break;
401  case '2': a_Value["color"] = Json::Value("dark_green"); break;
402  case '3': a_Value["color"] = Json::Value("dark_aqua"); break;
403  case '4': a_Value["color"] = Json::Value("dark_red"); break;
404  case '5': a_Value["color"] = Json::Value("dark_purple"); break;
405  case '6': a_Value["color"] = Json::Value("gold"); break;
406  case '7': a_Value["color"] = Json::Value("gray"); break;
407  case '8': a_Value["color"] = Json::Value("dark_gray"); break;
408  case '9': a_Value["color"] = Json::Value("blue"); break;
409  case 'a': a_Value["color"] = Json::Value("green"); break;
410  case 'b': a_Value["color"] = Json::Value("aqua"); break;
411  case 'c': a_Value["color"] = Json::Value("red"); break;
412  case 'd': a_Value["color"] = Json::Value("light_purple"); break;
413  case 'e': a_Value["color"] = Json::Value("yellow"); break;
414  case 'f': a_Value["color"] = Json::Value("white"); break;
415 
416  } // switch (Style[i])
417  } // for i - a_PartStyle[]
418 }
eMessageType
Definition: Defines.h:352
@ mtWarning
Definition: Defines.h:360
@ mtPrivateMessage
Definition: Defines.h:363
@ mtJoin
Definition: Defines.h:364
@ mtMaxPlusOne
Definition: Defines.h:366
@ mtSuccess
Definition: Defines.h:359
@ mtCustom
Definition: Defines.h:356
@ mtInformation
Definition: Defines.h:358
@ mtDeath
Definition: Defines.h:362
@ mtLeave
Definition: Defines.h:365
@ mtFatal
Definition: Defines.h:361
@ mtFailure
Definition: Defines.h:357
#define ASSERT(x)
Definition: Globals.h:276
void LOGERROR(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:73
eLogLevel
Definition: LoggerSimple.h:6
std::vector< AString > AStringVector
Definition: StringUtils.h:12
std::string AString
Definition: StringUtils.h:11
AString WriteStyledString(const Json::Value &a_Root)
Definition: JsonUtils.cpp:24
AString WriteFastString(const Json::Value &a_Root)
Definition: JsonUtils.cpp:12
static AString FormatMessageType(bool ShouldAppendChatPrefixes, eMessageType a_ChatPrefix, const AString &a_AdditionalData)
Formats the type of message with the proper color and prefix for sending to the client.
void Clear(void)
Removes all parts from the object.
AString CreateJsonString(bool a_ShouldUseChatPrefixes=true) const
void SetMessageType(eMessageType a_MessageType, const AString &a_AdditionalMessageTypeData="")
Sets the message type, which is indicated by prefixes added to the message when serializing Takes opt...
void UnderlineUrls(void)
Adds the "underline" style to each part that is an URL.
void AddUrlPart(const AString &a_Text, const AString &a_Url, const AString &a_Style="nc")
Adds a part that opens an URL when clicked.
AString ExtractText(void) const
Returns the text from the parts that comprises the human-readable data.
AString m_AdditionalMessageTypeData
Additional data pertaining to message type, for example, the name of a mtPrivateMsg sender.
void AddSuggestCommandPart(const AString &a_Text, const AString &a_SuggestedCommand, const AString &a_Style="nb")
Adds a part that suggests a command (enters it into the chat message area, but doesn't send) when cli...
void ParseText(const AString &a_ParseText)
Parses text into various parts, adds those.
eMessageType GetMessageType(void) const
Returns the message type set previously by SetMessageType().
void AddTextPart(const AString &a_Message, const AString &a_Style="")
Adds a plain text part, with optional style.
void AddClientTranslatedPart(const AString &a_TranslationID, const AStringVector &a_Parameters, const AString &a_Style="")
Adds a part that is translated client-side, with the formatting parameters and optional style.
cCompositeChat(void)
Creates a new empty chat message.
void AddRunCommandPart(const AString &a_Text, const AString &a_Command, const AString &a_Style="na")
Adds a part that runs a command when clicked.
eMessageType m_MessageType
The message type, as indicated by prefixes.
void AddChatPartStyle(Json::Value &a_Value, const AString &a_PartStyle) const
Adds the chat part's style (represented by the part's stylestring) into the Json object.
std::vector< std::variant< TextPart, ClientTranslatedPart, UrlPart, RunCommandPart, SuggestCommandPart, ShowAchievementPart > > m_Parts
All the parts that.
void AddShowAchievementPart(const AString &a_PlayerName, const AString &a_Achievement, const AString &a_Style="")
Adds a part that fully formats a specified achievement using client translatable strings Takes achiev...
AString GetAdditionalMessageTypeData(void) const
Returns additional data pertaining to message type, for example, the name of a mtPrivateMsg sender.
static eLogLevel MessageTypeToLogLevel(eMessageType a_MessageType)
Converts the MessageType to a LogLevel value.
You can use this struct to use in std::visit example: std::visit( OverloadedVariantAccess { [&] (cFir...
Definition: Globals.h:327