nonebot2聊天机器人插件3:计算器calculator


该插件涉及知识点:将字符串作为代码执行,正则过滤
插件合集:nonebot2聊天机器人插件

该系列为用于QQ群聊天机器人的nonebot2相关插件,不保证完全符合标准规范写法,如有差错和改进余地,欢迎大佬指点修正。
前端:nonebot2
后端:go-cqhttp
插件所用语言:python3
前置环境安装过程建议参考零基础2分钟教你搭建QQ机器人——基于nonebot2,但是请注意该教程中的后端版本过旧导致私聊发图异常,需要手动更新go-cqhttp版本。

1. 插件用途

用户能够通过"计算"命令让bot完成对相应算式的计算,从而实现简单的对话式计算器。
缺点:python无法在异步执行时强行终止超时的计算工作,导致在用户使用例如6**6**6**6**6这样的连续幂计算,或者过于夸张的阶乘计算时,程序不会报错中断,而是会花很长的时间进行计算导致bot的响应卡死。
暂时没有很好的超时中止解决方法,使用asyncio库的超时报错在实装部署到nonebo2插件中时也并没有生效(连超时异常都没有捕获到),原因不明,目前只能禁止用户使用数量过大的阶乘和幂运算。

2. 目录结构

在plugins文件夹中新建一个文件夹calculator,文件夹内目录结构如下:

|-calculator
    |-img
        |-think.gif
    |-__init__.py
    |-calculator.py
    |-config.py

其中img为用于存储发送的图片文件的文件夹,calculator.py为程序主要代码的位置,config.py用于存储配置项,__init__.py为程序启动位置。

3. 实现难点与解决方案

3.1 将字符串作为代码执行

使用python自带的eval()方法运行字符串,必须事先import可能用到的函数,直接用变量获取运行后的返回值。

# 将字符串作为代码运行计算,并保留5位小数防止浮点数的尾数溢出
result = round(eval(msg), 5)

3.2 正则过滤

将信息字符串作为代码运行需要小心可能存在的注入攻击,因此使用正则匹配的形式只允许输入规范中的算式内容,对不在规范中的符号与函数拒绝执行。

# 将中文的括号与逗号都替换为英文的
msg = str(event.get_message()).strip().replace('\r\n','').replace('\n','').replace('(','(').replace(')',')').replace(',',',')
require = "(\d| |\+|\-|\*|/|\(|\)|abs\(|sqrt\(|factorial\(|%|E|e|\.|log\(|\,|j)*"
# 未通过正则匹配
if msg != re.match(require, msg, flags=0).group() or "**" in msg:
    last_response = time.time()
    await calculator.finish("错误:输入算式中含有非法格式,请使用[计算帮助]查询规范。"+MessageSegment.image(think_img))

4. 代码实现

Config.py

class Config:
    # 记录在哪些群组中使用
    used_in_group = ["131551175"]
    # 插件执行优先级
    priority = 10
    # 计算最小间隔
    cd = 10
    # 管理员QQ号,管理员无视冷却cd
    super_uid = ["673321342"]
    # 允许的最大阶乘
    factorial_limit = 100

__init__.py

from .calculator import *

calculator.py

from nonebot import on_command
from nonebot.typing import T_State
from nonebot.adapters import Bot, Event
from math import sqrt, factorial, log
import re
import os
from nonebot.adapters.cqhttp import MessageSegment
import time
from .config import Config

__plugin_name__ = 'calculator'
__plugin_usage__ = '用法:计算器。'

dir_path = 'file:///' + os.path.split(os.path.realpath(__file__))[0] + '/img/'

# 这是一张图片,会在击杀复读者时发送
think_img = dir_path + 'think.gif'

# 记录上一次响应时间, 初始化为开机时间-cd时间
last_response = time.time() - Config.cd

calculator_help = on_command("计算帮助", priority=Config.priority)
@calculator_help.handle()
async def handle_first_receive(bot: Bot, event: Event, state: T_State):
    await calculator_help.finish(f'''关于计算模块的使用说明:
使用命令为"计算 [算式]"
命令响应冷却时间为:{Config.cd}秒
警告:由于遭到大数据滥用,现已不支持幂运算!
警告:阶乘计算输入值不得超过{Config.factorial_limit}!

目前支持的输入内容包括(由张3作为示例):
空格与换行符:不会影响计算
3:整数
3.3:小数
3+3j:虚数
3e3或3e-3或3E3:科学计数法表示的数字
():小括号
+-*/:四则运算或负号
abs(-3):绝对值
sqrt(3):平方根
//:整除
%:求余数
factorial(3):阶乘
log(3,3):对数''')

calculator = on_command("计算", priority=Config.priority)
@calculator.handle()
async def handle_first_receive(bot: Bot, event: Event, state: T_State):
    global last_response
    use_calculator = False
    ids = event.get_session_id()
    # 如果这是一条群聊信息
    if ids.startswith("group"):
        _, group_id, user_id = event.get_session_id().split("_")
        if group_id in Config.used_in_group:
            use_calculator = True
    else:
        user_id = ids
        use_calculator = True
    if use_calculator:
        # 检测响应cd, 如果允许则继续,管理员无视冷却cd
        if time.time() - last_response >= Config.cd or user_id in Config.super_uid:
            # 将中文的括号与逗号都替换为英文的
            msg = str(event.get_message()).strip().replace('\r\n','').replace('\n','').replace('(','(').replace(')',')').replace(',',',')
            require = "(\d| |\+|\-|\*|/|\(|\)|abs\(|sqrt\(|factorial\(|%|E|e|\.|log\(|\,|j)*"
            # 判断是否有过大阶乘输入
            facts = re.findall("factorial\(\d*\)", msg)
            facts = list(map(lambda x: int(x[10:-1]), facts))
            large_fact = False
            for fact in facts:
                if fact > Config.factorial_limit:
                    large_fact = True
                    break
            # 未通过正则匹配
            if msg != re.match(require, msg, flags=0).group() or "**" in msg:
                last_response = time.time()
                await calculator.finish("错误:输入算式中含有非法格式,请使用[计算帮助]查询规范。"+MessageSegment.image(think_img))
            # 如果阶乘输入过大
            elif large_fact:
                await calculator.finish("错误:输入算式中阶乘输入值过大,请使用[计算帮助]查询规范。" + MessageSegment.image(think_img))
            # 通过匹配
            else:
                try:
                    # 将字符串作为代码运行计算,并保留5位小数防止浮点数的尾数溢出
                    result = round(eval(msg), 5)
                except ZeroDivisionError:
                    last_response = time.time()
                    await calculator.finish("错误:除数为零。" + MessageSegment.image(think_img))
                except:
                    last_response = time.time()
                    await calculator.finish("错误:输入算式并不是正确的算式。"+MessageSegment.image(think_img))
                else:
                    last_response = time.time()
                    await calculator.finish("计算结果:" + str(result) + MessageSegment.image(think_img))

5. 插件配图

think.gif
请添加图片描述

6. 实际效果

请添加图片描述

7. 下一个插件

nonebot2聊天机器人插件4:群聊与戳一戳响应chat

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值