我有一个游戏电报机器人,它使用名字-姓氏对来根据用户的分数拼出聊天中的用户排行榜。截图示例如下:
因此,每个用户都有一个指向他们的链接。生成链接的实际代码:
EscapeType = typing.Literal['html']
def escape_string(s: str, escape: EscapeType | None = None) -> str:
if escape == 'html':
s = html_escape(s)
elif escape is None:
pass
else:
raise NotImplementedError(escape)
return s
def getter(d):
if isinstance(d, User):
return lambda attr: getattr(d, attr, None)
elif hasattr(d, '__getitem__') and hasattr(d, 'get'):
return lambda attr: d.get(attr, None)
else:
return lambda attr: getattr(d, attr, None)
def personal_appeal(user: User | dict, escape: EscapeType | None = 'html') -> str:
get = getter(user)
if full_name := get("full_name"):
appeal = full_name
elif name := get("name"):
appeal = name
elif first_name := get("first_name"):
if last_name := get("last_name"):
appeal = f"{first_name} {last_name}"
else:
appeal = first_name
elif username := get('username'):
appeal = username
else:
raise ValueError(user)
return escape_string(appeal, escape)
def user_mention(id: int | User, name: str | None = None, escape: EscapeType | None = 'html') -> str:
if isinstance(id, User):
user = id
id = user.id
name = personal_appeal(user)
name = escape_string(name, escape=escape)
if name is None:
name = "N/A"
if id is not None:
return f'<a href="tg://user?id={id}">{name}</a>'
else:
return name
基本上,此代码从用户名 - 用户 ID 对生成链接。正如你所看到的,名称默认是 HTML 转义的。
但是,有一个用户通过他们不寻常的名字以某种方式破坏了此代码,这是他们使用的实际字符序列:
'$̴̢̛̙͈͚̎̓͆͑.̸̱̖͑͒ ̧̡͉̺̬͎̯.̸̧̢̠̺̮̬͙͛̓̀̐́.̵̦͑̉͌͌̎͘ ̞ ̷̡͈̤̓̀͋͗͊̈́̑̽͝'
针对该名字运行相同代码的结果的屏幕截图:
如您所见,电报似乎在标记中丢失了。该链接转义到其他不相关的字符,并且
<b>
标签也被破坏。
这是发送到电报服务器的实际字符串(除了我删除的 id):
🔝🏆 <u>Рейтинг игроков чата</u>:
🥇 1. <a href="tg://user?id=1">andy alexanderson</a> (<b>40</b>)
🥈 2. <a href="tg://user?id=2">$̴̢̛̙͈͚̎̓͆͑.̸̱̖͑͒ ̧̡͉̺̬͎̯.̸̧̢̠̺̮̬͙͛̓̀̐́.̵̦͑̉͌͌̎͘ ̞ ̷̡͈̤̓̀͋͗͊̈́̑̽͝</a> (<b>40</b>)
🤡 3. <a href="tg://user?id=3">: )</a> (<b>0</b>)
⏱️ <i>Рейтинг составлен 1 минуту назад</i>.
⏭️ <i>Следующее обновление через 28 минут</i>.
不过,这个标记中唯一奇怪的是昵称。
这是 Telegram 错误吗?
可以采取一些措施来缓解这种情况,以便我的用户无法逃避 HTML 标记吗? 我愿意牺牲他们名称表示的正确性(因为这些用户愿意混淆他们的名字),但是我需要以某种方式能够区分一些会破坏标记的东西。
或者也许有一些我错过的 UTF-16 <-> UTF-8 编码内容?
使用的框架:
python-telegram-bot
。
Python版本:3.10.12
.
正如 @roganjosh 所指出的,这实际上是一个所谓的“zalgo”字符序列。为了删除 zalgo 字符,我首先从一个名为 lunicode.js 的旧 JS 库中找到了 这个解码函数 。我通过逆向找到它这个zalgo-文本编码器-解码器网站。
原来是一个非常简单的函数,所以这里用python写的:
def remove_zalgo(txt: str) -> str:
return ''.join([
char
for char in txt
if ord(char) < 768 or ord(char) > 865
])
现在我的标记没有中断,并且我的用户名中没有 zalgo 字符。我想,这是一场胜利:)