04 translation - musnows/Kook-Valorant-Bot GitHub Wiki
翻译功能实现
打开 code/endpoints/translate.py,这便是本仓库中翻译代码的实现。
其中第一部分有道翻译的代码来自Many-Translaters项目,该项目上一次维护是在4年前,且里面的部分代码已经无法使用。该代码属于一个白嫖产物,稳定性未知。
为了不让我的bot在有道翻译接口失效后直接没有了翻译功能,这里我使用了彩云小译来作“备胎”
你可以在彩云小译的官网上找到api文档,内部包含了一个Python代码示例,开箱即用!免费用户申请的
api-key
,每月有100w字符的免费额度,对于我们的bot算是够用了。
1.关于aiohttp和requests的优劣
在khl.py
一众大佬的建议下,我简单学习了aiohttp
的代码,并将彩云小译的requests
修改为了aiohttp
#原有requests代码
response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
return json.loads(response.text)["target"]
#替换为aiohttp代码
async with aiohttp.ClientSession() as session:
async with session.post(url, data=json.dumps(payload), headers=headers) as response:
return json.loads(await response.text())["target"]
这里简单说一下aiohttp
和requests
的区别
requests
途中,程序会挂起,bot将不会响应其他命令aiohttp
作为异步框架,bot在使用它的同时,可以同步处理其他命令
举个栗子:假设有用户每1分钟调用一次bot的翻译接口,彩云小译的服务器用了3秒钟(实际肯定没那么久)响应了我们的requests
。这3秒钟内,如果有其他用户调用了bot的另外一个指令,我们的bot就跟假死了一样,不会响应该用户的指令
这样看来,优势就很明显了:虽然在网络稳定的时候,requests
和aiohttp
不会形成鲜明的效率差距,但在KOOK或者彩云小译服务器拥堵期间,我们的bot也能做到不会因为requests
时间太长而影响用户的使用体验。
2.利用抛异常机制更改翻译引擎
看到main.py中的translate
部分,这里我import
了translate.py
中的相关函数,随后使用bot.command
来调用这两个函数。那么如何让bot在有道翻译接口寄了的时候,自动去找备胎彩云小译呢?
- 简单了解抛异常
在python中,基本的抛异常机制如下:
try:
num('a', 'b')
except:
print('程序奔溃啦!')
上面这个代码就是一个简单的抛异常机制。编译器会先尝试运行try
后面的代码,如果该部分报错,则会转而执行except
后的代码。
转换倒我们这里的例子,我们只需要在try后面写入有道翻译的代码,在except后写入彩云小译的代码,编译器就能在有道的接口出错的时候,自动找备胎
try:
#有道代码实现
except:
#彩云小译代码实现
3.关于带空格英文句子传参问题
当我们翻译一个句子的时候,中文内容往往没有空格,但英文句子极其依赖于空格
进行单词的分割。
如果我们简单地使用str来接受传参,就会导致用户需要翻译的英文内容,只有第一个单词传了过来
async def translate(msg: Message,txt:str):
比如当用户打出:/TL I LOVE YOU
时,bot实际接收到的只有首单词I
,它会翻译该单词,可后面的LOVE YOU
直接被无情抛弃了!
这里我们就需要使用python中牛逼的不定传参*arg
了!
async def translate(msg: Message,*arg):
*arg
是python中支持的不定参数传参,即函数先前不知道用户会传入多少个参数。我们可以在传参完毕后,再对这些参数进行操作。
利用*arg
的特性,我们可以一次性把所有单词都接收过来,再在函数中将它们拼成一个完整的字符串,传入到translate
函数中。
async def translate(msg: Message,*arg):
word = " ".join(arg)
这里的" ".jion(arg)
代表用空格来分隔每一个参数,这样才能拼出一个完整的英文句子!
好了,基本的单句翻译已经写好了,但我们还可以整点花活,让bot可以实时翻译某一个文字频道内的所有消息!
4.实时翻译(全局变量)
见main.py
中的ListTL
,这里我为实时翻译创建了一个全局数组,用来存放需要实时翻译的文字频道id。
注意:ListTL
作为全局变量,在函数中调用的时候,需要先用global ListTL
进行全局变量声明。否侧程序会在该函数中创建临时变量!
- 当用户需要实时翻译时,利用
/TLON
功能在他所在的文字频道开启该功能
bot可以在msg.ctx.channel.id
中获取到用户所在文字频道的id,并将其写入List中
- 使用
@bot.command(regex=r'(.+)')
正则表达式获取文字频道的所有内容,再通过判断该文字频道id是否存于List之中,来确认是否要进行实时翻译并返回结果
这种正则方法也是让bot监看文字频道的一个非常好的办法,比如发现关键词之后,自动发送对应消息提示。
- 当用户使用
/TLOFF
关闭实时翻译后,将对应位置的List置零,空出栏位
具体的代码实现可以查看main.py
中translate
相关部分的函数
5.处理met和rol消息
为了避免冲突,机器人不应该翻译 @xx用户
和@xx角色
的消息,在kook的后台,机器人会接收到 (met)user_id(met)/(rol)role_id(rol)
格式的文字;
用下面的代码处理,可以将这两个串替换成空串,不进行翻译
# 单独处理met和rol消息,不翻译这部分内容
def deleteByStartAndEnd(s, start, end):
# 找出两个字符串在原始字符串中的位置
# 开始位置是:开始始字符串的最左边第一个位置;
# 结束位置是:结束字符串的最右边的第一个位置
while s.find(start) != -1:
x1 = s.find(start)
x2 = s.find(end, x1 + 5) + len(end) # s.index()函数算出来的是字符串的最左边的第一个位置,所以需要加上长度找到末尾
# 找出两个字符串之间的内容
x3 = s[x1:x2]
# 将内容替换为空字符串s
s = s.replace(x3, "")
print(f'Handel{start}: {s}')
return s
结语
使用实时翻译可以实现一些好玩的事情:比如把一句话来回翻译N遍进行“提纯”
(非常无聊了属于是)