nonebot2聊天机器人插件2:调色盘palette

  • 1. 插件用途
  • 2. 目录结构
  • 3. 实现难点与解决方案
  • 3.1 获取发送者信息
  • 3.2 使用PIL与numpy创建图片
  • 3.3 bot发送图片
  • 3.4 获取程序所在路径
  • 3.5 设置应答冷却时间
  • 4. 代码实现
  • 5. 插件配图
  • 6. 实际效果
  • 7. 下一个插件



该插件涉及知识点:获取发送者信息,使用PIL与numpy创建图片,bot发送图片,获取程序所在路径,设置应答冷却时间


插件合集:nonebot2聊天机器人插件

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

1. 插件用途

用户能够使用“调色盘”命令并且提供RGB颜色参数,让bot自动生成对应色彩的图片作为回应。
命令支持两种形式,一种为3个以空格为分隔的十进制整数,另一种为以#开头的6位十六进制颜色码。
程序需要具备在输入参数错误时的报错能力,只在指定配置的群组中工作,并且拥有一个最短响应cd,在每一次应答后开始计算cd,cd期内不对命令进行回应,以防止群组内同时发送命令的人员过多造成混乱。
配置文件中能够配置该插件的超级用户组【注:不同于bot的管理员用户】,超级用户的命令可以无视冷却cd直接生效。

2. 目录结构

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

|-palette
    |-temp
    |-__init__.py
    |-palette.py
    |-config.py

其中temp为用于存储临时文件的空文件夹,palette.py为程序主要代码的位置,config.py用于存储配置项,__init__.py为程序启动位置。

3. 实现难点与解决方案

3.1 获取发送者信息

通过event.get_session_id()能够获得一个id信息,如果该信息为私聊信息,那么返回值为该用户QQ号。如果该信息为群聊信息,那么返回值为group_群号_该用户QQ号。
为了方便起见,将私聊信息的QQ号前手动添加private_作为group_id,用于检验对应的响应cd。
分割并获得id的代码如下:

# 是否允许使用这个插件
    allow_use = True
    # 如果这是一条群聊信息
    if ids.startswith("group"):
        _, group_id, user_id = event.get_session_id().split("_")
        # 对于不在配置文件允许列表里面的群组,拒绝访问
        if group_id not in Config.used_in_group:
            allow_use = False
    # 如果这是一条私聊信息
    else:
        user_id = ids
        group_id = 'private_' + ids

3.2 使用PIL与numpy创建图片

当获取到RGB三色的参数后,使用numpy库创建一个三维矩阵作为图像数据,然后使用PIL库将创建的图片保存到本地。

# 根据输入的三原色数据转换为500x500x3的numpy矩阵
color = np.array([R, G, B], dtype=np.uint8)
img = np.tile(color, (500, 500, 1))
img = Image.fromarray(img)
# 保存图片到临时文件夹
img.save(img_path)

3.3 bot发送图片

使用nonebot2与go-cqhttp从本地磁盘上发送图片时,必须在图片的路径前面加上file:///,具体写法如下:

from nonebot.adapters.cqhttp import MessageSegment

await palette.send(MessageSegment.image('file:///'+img_path))

3.4 获取程序所在路径

不论是保存图片还是发送图片,为了方便都应该使用相对路径比较友好,而在任何情况下都能获取当前python代码文件所在路径,并且将图片路径设定到temp文件夹内的写法如下:

img_path = os.path.split(os.path.realpath(__file__))[0] + '/temp/temp_img.png'

3.5 设置应答冷却时间

设置一个字典用于储存所有群的上一次应答时间戳,字典的key为以group或者private开头的字符串,在每次响应时计算已经过去的时间是否大于最短cd。

# 记录上一次响应时间
last_response = {}

# 判断是否过了响应cd的函数,默认使用配置文件中的cd
# 如果已经超过了最短响应间隔,返回True
def cool_down(group_id, cd = Config.cd):
    global last_response
    if group_id not in last_response:
        return True
    else:
        return time() - last_response[group_id] > cd

4. 代码实现

Config.py

class Config:
    # 记录在哪些群组中使用
    used_in_group = ["131551175"]
    # 插件执行优先级
    priority = 10
    # 接话冷却时间(秒),在这段时间内不会连续两次接话
    cd = 15
    # 管理员QQ号,管理员无视冷却cd
    super_uid = ["673321342"]

__init__.py

from .palette import *

palette.py

from nonebot import on_command
from nonebot.typing import T_State
from nonebot.adapters import Bot, Event
from nonebot.adapters.cqhttp import MessageSegment
from PIL import Image
from .config import Config
import os
import numpy as np
from time import time


__plugin_name__ = 'palette'
__plugin_usage__ = '用法: 调色板,根据输入的BGR值返回彩色图片。'

# 记录上一次响应时间
last_response = {}

# 判断是否过了响应cd的函数,默认使用配置文件中的cd
# 如果已经超过了最短响应间隔,返回True
def cool_down(group_id, cd = Config.cd):
    global last_response
    if group_id not in last_response:
        return True
    else:
        return time() - last_response[group_id] > cd

img_path = os.path.split(os.path.realpath(__file__))[0] + '/temp/temp_img.png'

palette = on_command("调色盘", priority=Config.priority)

@palette.handle()
async def handle_first_receive(bot: Bot, event: Event, state: T_State):
    ids = event.get_session_id()
    # 是否允许使用这个插件
    allow_use = True
    # 如果这是一条群聊信息
    if ids.startswith("group"):
        _, group_id, user_id = event.get_session_id().split("_")
        # 对于不在配置文件允许列表里面的群组,拒绝访问
        if group_id not in Config.used_in_group:
            allow_use = False
    # 如果这是一条私聊信息
    else:
        user_id = ids
        group_id = 'private_' + ids
    # 如果允许使用
    if allow_use:
        # 如果已经过了冷却时间,或者用户是管理员
        if cool_down(group_id) or user_id in Config.super_uid:
            # 更新cd时间
            last_response[group_id] = time()
            msg = str(event.get_message()).strip()
            # 如果输入为16进制且位数不合规
            if msg.startswith("#") and len(msg) != 7:
                await palette.finish("输入的RGB数值不符合规范,请使用[调色盘帮助]查询")
            try:
                # 如果输入为16进制
                if msg.startswith("#"):
                    # 拆解16进制输入
                    R, G, B = int(msg[1:3], 16), int(msg[3:5], 16), int(msg[5:], 16)
                # 如果输入为10进制
                else:
                    R, G, B = msg.split(' ')
                    R, G, B = int(R), int(G), int(B)
            except:
                await palette.finish("输入的RGB数值不符合规范,请使用[调色盘帮助]查询")

            if min(R, G, B) >= 0 and max(R, G, B) <= 255:
                # 根据输入的三原色数据转换为500x500x3的numpy矩阵
                color = np.array([R, G, B], dtype=np.uint8)
                img = np.tile(color, (500, 500, 1))
                img = Image.fromarray(img)
                # 保存图片到临时文件夹
                img.save(img_path)
                # 发送图片
                await palette.send(MessageSegment.image('file:///'+img_path))
                # 删除临时文件夹中的图片
                os.remove(img_path)
            else:
                await palette.finish("输入的RGB数值不符合规范,请使用[调色盘帮助]查询")


palette_help = on_command("调色盘帮助", priority=Config.priority)
@palette_help.handle()
async def handle_first_receive(bot: Bot, event: Event, state: T_State):
    await palette_help.finish(f"""调色盘指令说明:
指令使用cd为{Config.cd}秒,有两种不同的使用方式。
1. 调色盘 R G B——RGB为0-255的整数对应红绿蓝三原色,以空格分隔
2. 调色盘 十六进制颜色码——十六进制颜色以#开头,如'#ff0000'""")

5. 插件配图

该插件无配图

6. 实际效果

element ui 聊天机器人 聊天机器人插件_群组

7. 下一个插件

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