翻译功能实现

打开 code/endpoints/translate.py,这便是本仓库中翻译代码的实现。

其中第一部分有道翻译的代码来自Many-Translaters项目,该项目上一次维护是在4年前,且里面的部分代码已经无法使用。该代码属于一个白嫖产物,稳定性未知。

为了不让我的bot在有道翻译接口失效后直接没有了翻译功能,这里我使用了彩云小译来作“备胎”

你可以在彩云小译的官网上找到api文档,内部包含了一个Python代码示例,开箱即用!免费用户申请的api-key,每月有100w字符的免费额度,对于我们的bot算是够用了。

1.关于aiohttp和requests的优劣

khl.py一众大佬的建议下,我简单学习了aiohttp的代码,并将彩云小译的requests修改为了aiohttp

1
2
3
4
5
6
7
#原有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"]

这里简单说一下aiohttprequests的区别

  • requests途中,程序会挂起,bot将不会响应其他命令
  • aiohttp作为异步框架,bot在使用它的同时,可以同步处理其他命令

举个栗子:假设有用户每1分钟调用一次bot的翻译接口,彩云小译的服务器用了3秒钟(实际肯定没那么久)响应了我们的requests。这3秒钟内,如果有其他用户调用了bot的另外一个指令,我们的bot就跟假死了一样,不会响应该用户的指令

这样看来,优势就很明显了:虽然在网络稳定的时候,requestsaiohttp不会形成鲜明的效率差距,但在KOOK或者彩云小译服务器拥堵期间,我们的bot也能做到不会因为requests时间太长而影响用户的使用体验。

2.利用抛异常机制更改翻译引擎

看到main.py中的translate部分,这里我importtranslate.py中的相关函数,随后使用bot.command来调用这两个函数。那么如何让bot在有道翻译接口寄了的时候,自动去找备胎彩云小译呢?

  • 简单了解抛异常

在python中,基本的抛异常机制如下:

1
2
3
4
try:
num('a', 'b')
except:
print('程序奔溃啦!')

上面这个代码就是一个简单的抛异常机制。编译器会先尝试运行try后面的代码,如果该部分报错,则会转而执行except后的代码。

转换倒我们这里的例子,我们只需要在try后面写入有道翻译的代码,在except后写入彩云小译的代码,编译器就能在有道的接口出错的时候,自动找备胎

1
2
3
4
try:
#有道代码实现
except:
#彩云小译代码实现

3.关于带空格英文句子传参问题

当我们翻译一个句子的时候,中文内容往往没有空格,但英文句子极其依赖于空格进行单词的分割。

如果我们简单地使用str来接受传参,就会导致用户需要翻译的英文内容,只有第一个单词传了过来

1
async def translate(msg: Message,txt:str):

比如当用户打出:/TL I LOVE YOU时,bot实际接收到的只有首单词I,它会翻译该单词,可后面的LOVE YOU直接被无情抛弃了!

这里我们就需要使用python中牛逼的不定传参*arg了!

1
async def translate(msg: Message,*arg):

*arg是python中支持的不定参数传参,即函数先前不知道用户会传入多少个参数。我们可以在传参完毕后,再对这些参数进行操作。

利用*arg的特性,我们可以一次性把所有单词都接收过来,再在函数中将它们拼成一个完整的字符串,传入到translate函数中。

1
2
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.pytranslate相关部分的函数

5.处理met和rol消息

为了避免冲突,机器人不应该翻译 @xx用户@xx角色 的消息,在kook的后台,机器人会接收到 (met)user_id(met)/(rol)role_id(rol) 格式的文字;

用下面的代码处理,可以将这两个串替换成空串,不进行翻译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 单独处理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遍进行“提纯”

(非常无聊了属于是)