diff --git a/src/chat.cpp b/src/chat.cpp
index 809d4e4225c6d803ab3ed7120024bd4e489cf362..495e3450b2d132d6203a383176ae551b5d5e31fa 100644
--- a/src/chat.cpp
+++ b/src/chat.cpp
@@ -679,6 +679,9 @@ ChatBackend::~ChatBackend()
 
 void ChatBackend::addMessage(std::wstring name, std::wstring text)
 {
+	name = removeChatEscapes(name);
+	text = removeChatEscapes(text);
+
 	// Note: A message may consist of multiple lines, for example the MOTD.
 	WStrfnd fnd(text);
 	while (!fnd.atend())
diff --git a/src/util/string.cpp b/src/util/string.cpp
index 2c4143c764c1d41ba2aebcdefa3dac9acce78725..c8f528a77a06b296295a649c5f90066d29259433 100644
--- a/src/util/string.cpp
+++ b/src/util/string.cpp
@@ -729,6 +729,33 @@ static bool parseNamedColorString(const std::string &value, video::SColor &color
 	return true;
 }
 
+std::wstring removeChatEscapes(const std::wstring &s) {
+	std::wstring output;
+	size_t i = 0;
+	while (i < s.length()) {
+		if (s[i] == L'\v') {
+			++i;
+			if (i == s.length()) continue;
+			if (s[i] == L'(') {
+				++i;
+				while (i < s.length() && s[i] != L')') {
+					if (s[i] == L'\\') {
+						++i;
+					}
+					++i;
+				}
+				++i;
+			} else {
+				++i;
+			}
+			continue;
+		}
+		output += s[i];
+		++i;
+	}
+	return output;
+}
+
 void str_replace(std::string &str, char from, char to)
 {
 	std::replace(str.begin(), str.end(), from, to);
diff --git a/src/util/string.h b/src/util/string.h
index cf94b7f5f635f0ab32e6b1674bd221db68fd3e19..9e59ab20ace57e39c75ed35fd9c154eac89f2644 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -386,6 +386,13 @@ inline void str_replace(std::string &str, const std::string &pattern,
 	}
 }
 
+/**
+ * Remove all chat escape sequences in \p s.
+ *
+ * @param s The string in which to remove escape sequences.
+ * @return \p s, with escape sequences removed.
+ */
+std::wstring removeChatEscapes(const std::wstring &s);
 
 /**
  * Replace all occurrences of the character \p from in \p str with \p to.