mirror of
https://github.com/Refound-445/nonebot-plugin-nailongremove.git
synced 2025-11-04 21:22:43 +08:00
Revert "refactor"
This commit is contained in:
parent
ef53a3de69
commit
db9096048c
76
PKG-INFO
Normal file
76
PKG-INFO
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: nonebot-plugin-nailongremove
|
||||||
|
Version: 1.0.1
|
||||||
|
Summary: 一个基于图像分类模型的撤回图片插件
|
||||||
|
Home-page: https://github.com/Refound-445/onoebot-plugin-nailongremove
|
||||||
|
Keywords: nonebot,plugin,image
|
||||||
|
Author: 445
|
||||||
|
Author-email: 2877834692@qq.com
|
||||||
|
Requires-Python: >=3.7,<4.0
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
|
Requires-Dist: aiohttp (>=3.7.4,<4.0.0)
|
||||||
|
Requires-Dist: httpx (>=0.27.0)
|
||||||
|
Requires-Dist: keras (>=2.4.3,<3.0.0)
|
||||||
|
Requires-Dist: nonebot-adapter-onebot (>=2.0.0,<3.0.0)
|
||||||
|
Requires-Dist: nonebot-plugin-apscheduler (>=0.4.0,<0.5.0)
|
||||||
|
Requires-Dist: nonebot2 (>=2.0.0,<3.0.0)
|
||||||
|
Requires-Dist: numpy (>=1.19.5,<2.0.0)
|
||||||
|
Requires-Dist: opencv-python (>=4.5.1.48,<5.0.0.0)
|
||||||
|
Requires-Dist: pillow (==10.4.0)
|
||||||
|
Requires-Dist: tensorflow (>=2.4.1,<3.0.0)
|
||||||
|
Requires-Dist: torch (==2.4.1)
|
||||||
|
Requires-Dist: torchvision (==0.19.1)
|
||||||
|
Project-URL: Repository, https://github.com/Refound-445/onoebot-plugin-nailongremove
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
|
||||||
|
# nonebot_plugin_nailongremove
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable-next-line MD036 -->
|
||||||
|
_✨ 一个基于分类模型的简单插件~ ✨_
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
## 声明
|
||||||
|
本插件仅供娱乐和学习交流。
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
NailongRemove是一款由简单的分类模型建立的奶龙识别插件,可以识别群中的奶龙表情包并撤回该表情。
|
||||||
|
|
||||||
|
## 技术
|
||||||
|
本插件模型由三层卷积+两个全连接层构成,基于cifar10数据集的10分类,加入200多张奶龙表情包,并进行预处理为大小(32,32),形成11分类模型,在此基础上训练得到。
|
||||||
|
|
||||||
|
模型会有误差,目前仍在优化改进中...
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
只要有人发奶龙表情包被识别出来,就会被撤回并提醒。
|
||||||
|
|
||||||
|
## 即刻开始
|
||||||
|
|
||||||
|
- 使用 nb-cli
|
||||||
|
|
||||||
|
```
|
||||||
|
nb plugin install nonebot_plugin_nailongremove
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用 pip
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install nonebot_plugin_nailongremove
|
||||||
|
```
|
||||||
|
## 其他
|
||||||
|
- 人工智能学习交流群:949992679
|
||||||
|
|
||||||
|
- 机器人插件学习交流群:200980266
|
||||||
|
|
||||||
|
欢迎大家进群一起学习交流~
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
`noneBot_plugin_picsbank` 采用 `MIT` 协议开源,协议文件参考 [LICENSE](../plugins/nonebot_plugin_picsbank-master/LICENSE)。
|
||||||
|
|
||||||
|
|
||||||
175
README.md
175
README.md
@ -1,161 +1,52 @@
|
|||||||
<!-- markdownlint-disable MD031 MD033 MD036 MD041 -->
|
# nonebot_plugin_nailongremove
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
<a href="https://v2.nonebot.dev/store">
|
|
||||||
<img src="https://raw.githubusercontent.com/A-kirami/nonebot-plugin-template/resources/nbp_logo.png" width="180" height="180" alt="NoneBotPluginLogo">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<img src="https://raw.githubusercontent.com/lgc-NB2Dev/readme/main/template/plugin.svg" alt="NoneBotPluginText">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
# Nonebot-Plugin-NaiLongRemove
|
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable-next-line MD036 -->
|
||||||
_✨ 一个基于分类模型的简单插件~ ✨_
|
_✨ 一个基于分类模型的简单插件~ ✨_
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
## 声明
|
||||||
<a href="https://pdm.fming.dev">
|
|
||||||
<img src="https://img.shields.io/badge/pdm-managed-blueviolet" alt="pdm-managed">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<a href="https://pydantic.dev">
|
|
||||||
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/lgc-NB2Dev/readme/main/template/pyd-v1-or-v2.json" alt="Pydantic Version 1 Or 2" >
|
|
||||||
</a>
|
|
||||||
<a href="./LICENSE">
|
|
||||||
<img src="https://img.shields.io/github/license/Refound-445/nonebot-plugin-nailongremove.svg" alt="license">
|
|
||||||
</a>
|
|
||||||
<a href="https://pypi.python.org/pypi/nonebot-plugin-nailongremove">
|
|
||||||
<img src="https://img.shields.io/pypi/v/nonebot-plugin-nailongremove.svg" alt="pypi">
|
|
||||||
</a>
|
|
||||||
<a href="https://pypi.python.org/pypi/nonebot-plugin-nailongremove">
|
|
||||||
<img src="https://img.shields.io/pypi/dm/nonebot-plugin-nailongremove" alt="pypi download">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<a href="https://registry.nonebot.dev/plugin/nonebot-plugin-nailongremove:nonebot_plugin_nailongremove">
|
|
||||||
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fnbbdg.lgc2333.top%2Fplugin%2Fnonebot-plugin-nailongremove" alt="NoneBot Registry">
|
|
||||||
</a>
|
|
||||||
<a href="https://registry.nonebot.dev/plugin/nonebot-plugin-nailongremove:nonebot_plugin_nailongremove">
|
|
||||||
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fnbbdg.lgc2333.top%2Fplugin-adapters%2Fnonebot-plugin-nailongremove" alt="Supported Adapters">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 📖 介绍
|
|
||||||
|
|
||||||
### 声明
|
|
||||||
|
|
||||||
本插件仅供娱乐和学习交流。
|
本插件仅供娱乐和学习交流。
|
||||||
|
|
||||||
关于一些问题:
|
关于一些问题:
|
||||||
|
- 模型精度:目前模型只是轮廓所以精度相对较低,还在进一步优化中,如果您也是人工智能方面的兴趣爱好者,欢迎来群里交流学习和开发(人工智能学习交流群949992679),届时我们会署上贡献者名单~
|
||||||
|
- 关于插件安装:目前插件需要通过nonebot2进行加载,关于nonebot2及其插件安装请移到官方群768887710交流咨询~
|
||||||
|
- 如果只是安装插件,请先到官方群咨询安装768887710
|
||||||
|
- 如果只是安装插件,请先到官方群咨询安装768887710
|
||||||
|
- 如果只是安装插件,请先到官方群咨询安装768887710
|
||||||
|
## 简介
|
||||||
|
|
||||||
- 模型精度:目前模型只是轮廓所以精度相对较低,还在进一步优化中,如果您也是人工智能方面的兴趣爱好者,欢迎来群里交流学习和开发(人工智能学习交流群 949992679),届时我们会署上贡献者名单~
|
NailongRemove是一款由简单的分类模型建立的奶龙识别插件,可以识别群中的奶龙表情包并撤回该表情。
|
||||||
- 关于插件安装:目前插件需要通过 nonebot2 进行加载,关于 nonebot2 及其插件安装请移到官方群 768887710 交流咨询~
|
|
||||||
- 如果只是安装插件,请先到官方群咨询安装 768887710
|
|
||||||
- 如果只是安装插件,请先到官方群咨询安装 768887710
|
|
||||||
- 如果只是安装插件,请先到官方群咨询安装 768887710
|
|
||||||
|
|
||||||
### 简介
|
## 技术
|
||||||
|
本插件模型由三层卷积+两个全连接层构成,基于cifar10数据集的10分类,加入200多张奶龙表情包,并进行预处理为大小(32,32),形成11分类模型,在此基础上训练得到。
|
||||||
|
|
||||||
NailongRemove 是一款由简单的分类模型建立的奶龙识别插件,可以识别群中的奶龙表情包并撤回该表情。
|
模型会有误差,目前仍在优化改进中...
|
||||||
|
|
||||||
### 技术
|
|
||||||
|
|
||||||
本插件模型由三层卷积+两个全连接层构成,基于 cifar10 数据集的 10 分类,加入 200 多张奶龙表情包,并进行预处理为大小(32,32),形成 11 分类模型,在此基础上训练得到。
|
|
||||||
|
|
||||||
模型会有误差,目前仍在优化改进中……
|
|
||||||
|
|
||||||
## 💿 安装
|
|
||||||
|
|
||||||
以下提到的方法 任选**其一** 即可
|
|
||||||
|
|
||||||
<details open>
|
|
||||||
<summary>[推荐] 使用 nb-cli 安装</summary>
|
|
||||||
在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nb plugin install nonebot-plugin-nailongremove
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>使用包管理器安装</summary>
|
|
||||||
在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>pip</summary>
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install nonebot-plugin-nailongremove
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>pdm</summary>
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pdm add nonebot-plugin-nailongremove
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>poetry</summary>
|
|
||||||
|
|
||||||
```bash
|
|
||||||
poetry add nonebot-plugin-nailongremove
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>conda</summary>
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda install nonebot-plugin-nailongremove
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分的 `plugins` 项里追加写入
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[tool.nonebot]
|
|
||||||
plugins = [
|
|
||||||
# ...
|
|
||||||
"nonebot_plugin_nailongremove"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## ⚙️ 配置
|
|
||||||
|
|
||||||
在 nonebot2 项目的 `.env` 文件中添加下表中的必填配置
|
|
||||||
|
|
||||||
| 配置项 | 必填 | 默认值 | 说明 |
|
|
||||||
| :-------------------: | :--: | :--------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
|
||||||
| `NAILONG_MODEL_DIR` | 否 | `./data/nailongremove` | 模型的下载位置 |
|
|
||||||
| `NAILONG_LIST_SCENES` | 否 | `[]` | 聊天场景 ID 黑白名单列表<br />在单级聊天下为该聊天 ID,如 QQ 群号;<br />在多级聊天下为以 `_` 分割的各级聊天 ID,如频道下的子频道或频道下私聊 |
|
|
||||||
| `NAILONG_BLACKLIST` | 否 | `True` | 是否使用黑名单模式 |
|
|
||||||
| `NAILONG_RECALL` | 否 | `True` | 是否撤回消息 |
|
|
||||||
| `NAILONG_TIP` | 否 | `本群禁止发奶龙!` | 发送的提示,使用 [Alconna 的消息模板](https://nonebot.dev/docs/best-practice/alconna/uniseg#%E4%BD%BF%E7%94%A8%E6%B6%88%E6%81%AF%E6%A8%A1%E6%9D%BF)<br />可用变量:`ev`: [`Event`](https://nonebot.dev/docs/api/adapters/#Event); `msg`: [`UniMessage`](https://nonebot.dev/docs/best-practice/alconna/uniseg#%E9%80%9A%E7%94%A8%E6%B6%88%E6%81%AF%E5%BA%8F%E5%88%97); `ss`: [`Session`](https://github.com/RF-Tar-Railt/nonebot-plugin-uninfo?tab=readme-ov-file#session) |
|
|
||||||
|
|
||||||
## 🎉 使用
|
|
||||||
|
|
||||||
|
## 使用
|
||||||
只要有人发奶龙表情包被识别出来,就会被撤回并提醒。
|
只要有人发奶龙表情包被识别出来,就会被撤回并提醒。
|
||||||
|
|
||||||
## 📞 联系
|
## 即刻开始
|
||||||
|
|
||||||
|
- 使用 nb-cli
|
||||||
|
|
||||||
|
```
|
||||||
|
nb plugin install nonebot_plugin_nailongremove
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用 pip
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install nonebot_plugin_nailongremove
|
||||||
|
```
|
||||||
|
## 其他
|
||||||
- 人工智能学习交流群:949992679<span style="color: yellow;">(开源代码,数据集,从本群获取)</span>
|
- 人工智能学习交流群:949992679<span style="color: yellow;">(开源代码,数据集,从本群获取)</span>
|
||||||
|
|
||||||
|
|
||||||
- 机器人插件学习交流群:200980266<span style="color: yellow;">(机器人及插件安装包,从本群获取)</span>
|
- 机器人插件学习交流群:200980266<span style="color: yellow;">(机器人及插件安装包,从本群获取)</span>
|
||||||
|
|
||||||
|
|
||||||
欢迎大家进群一起学习交流~
|
欢迎大家进群一起学习交流~
|
||||||
|
## 许可证
|
||||||
|
|
||||||
<!-- ## 📝 更新日志
|
`noneBot_plugin_picsbank` 采用 `MIT` 协议开源,协议文件参考 [LICENSE](../plugins/nonebot_plugin_picsbank-master/LICENSE)。
|
||||||
|
|
||||||
芝士刚刚发布的插件,还没有更新日志的说 qwq~ -->
|
|
||||||
|
|||||||
@ -1,15 +1,10 @@
|
|||||||
# ruff: noqa: E402
|
|
||||||
|
|
||||||
from nonebot import get_plugin_config, require
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
|
||||||
require("nonebot_plugin_uninfo")
|
|
||||||
|
|
||||||
from . import handler as handler
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
from nonebot import get_plugin_config
|
||||||
|
from .handler import *
|
||||||
|
|
||||||
__version__ = "2.0.0"
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="自动撤回奶龙",
|
name="自动撤回奶龙",
|
||||||
description="一个基于图像分类模型的简单插件~",
|
description="一个基于图像分类模型的简单插件~",
|
||||||
@ -17,9 +12,8 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/Refound-445/onoebot-plugin-nailongremove",
|
homepage="https://github.com/Refound-445/onoebot-plugin-nailongremove",
|
||||||
config=Config,
|
config=Config,
|
||||||
supported_adapters=inherit_supported_adapters(
|
supported_adapters={"~onebot.v11"},
|
||||||
"nonebot_plugin_alconna",
|
|
||||||
"nonebot_plugin_uninfo",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
config = get_plugin_config(Config)
|
config = get_plugin_config(Config)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
from pathlib import Path
|
from pydantic import BaseModel
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from nonebot import get_plugin_config
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
nailong_model_dir: Path = Field(
|
"""Plugin Config Here"""
|
||||||
default_factory=lambda: Path.cwd() / "data" / "nailongremove",
|
|
||||||
)
|
|
||||||
nailong_list_scenes: List[str] = Field(default_factory=list)
|
|
||||||
nailong_blacklist: bool = True
|
|
||||||
nailong_recall: bool = True
|
|
||||||
nailong_tip: str = "本群禁止发送奶龙!"
|
|
||||||
|
|
||||||
|
|
||||||
config = get_plugin_config(Config)
|
|
||||||
|
|||||||
@ -1,126 +1,70 @@
|
|||||||
import io
|
import logging
|
||||||
from typing import Iterable, TypeVar, cast
|
|
||||||
|
|
||||||
|
import httpx
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from nonebot import logger, on_message
|
|
||||||
from nonebot.adapters import Bot as BaseBot, Event as BaseEvent
|
|
||||||
from nonebot.drivers import Request
|
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.permission import SUPERUSER
|
|
||||||
from nonebot.rule import Rule
|
from nonebot.rule import Rule
|
||||||
from nonebot.typing import T_State
|
from nonebot import on_message
|
||||||
from nonebot.utils import run_sync
|
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||||
from nonebot_plugin_alconna.builtins.uniseg.market_face import MarketFace
|
from PIL import Image
|
||||||
from nonebot_plugin_alconna.uniseg import Image, UniMessage, UniMsg, image_fetch
|
import io
|
||||||
from nonebot_plugin_uninfo import Uninfo
|
from .model import *
|
||||||
from PIL import Image as PilImage
|
|
||||||
|
|
||||||
from .config import config
|
|
||||||
from .model import check_image
|
|
||||||
from .recall import recall
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
def transform_image(image_data: bytes) -> np.ndarray:
|
logger = logging.getLogger(__name__)
|
||||||
image = PilImage.open(io.BytesIO(image_data))
|
async def download_image(url: str) -> np.ndarray:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
image_data = response.content
|
||||||
|
|
||||||
if (
|
# 将字节数据加载为Pillow图像对象
|
||||||
# image.format == "GIF" and
|
image = Image.open(io.BytesIO(image_data))
|
||||||
getattr(image, "is_animated", False)
|
|
||||||
):
|
|
||||||
# 处理动图:仅获取第一帧
|
|
||||||
image = image.convert("RGB")
|
|
||||||
image = image.copy() # 获取第一帧
|
|
||||||
else:
|
|
||||||
image = image.convert("RGB")
|
|
||||||
|
|
||||||
image_array = np.array(image)
|
# 检查图像格式并处理
|
||||||
# OpenCV通常使用BGR格式,因此需要转换
|
if image.format == 'GIF' and hasattr(image, 'is_animated') and image.is_animated:
|
||||||
if len(image_array.shape) == 3 and image_array.shape[2] == 3:
|
# 处理GIF动图:仅获取第一帧
|
||||||
image_array = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
|
image = image.convert("RGB")
|
||||||
|
image = image.copy() # 获取第一帧
|
||||||
|
else:
|
||||||
|
# 对于非GIF图像(如JPEG)
|
||||||
|
image = image.convert("RGB")
|
||||||
|
|
||||||
return image_array
|
# 将Pillow图像转换为NumPy数组
|
||||||
|
image_array = np.array(image)
|
||||||
|
|
||||||
|
# OpenCV通常使用BGR格式,因此需要转换
|
||||||
|
if len(image_array.shape) == 3 and image_array.shape[2] == 3:
|
||||||
|
image_array = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
|
||||||
|
|
||||||
|
return image_array
|
||||||
|
|
||||||
|
|
||||||
def judge_list(lst: Iterable[T], val: T, blacklist: bool) -> bool:
|
|
||||||
return (val not in lst) if blacklist else (val in lst)
|
async def group_message_contains_image(event: MessageEvent) -> bool:
|
||||||
|
if isinstance(event, GroupMessageEvent) and any(seg.type == 'image' or seg.type=='mface' for seg in event.message):
|
||||||
|
return True
|
||||||
async def nailong_rule(
|
return False
|
||||||
bot: BaseBot,
|
|
||||||
event: BaseEvent,
|
|
||||||
session: Uninfo,
|
|
||||||
msg: UniMsg,
|
|
||||||
) -> bool:
|
|
||||||
return (
|
|
||||||
bool(session.member) # 检查是否是群聊消息,此值仅在群聊与频道中存在
|
|
||||||
and (
|
|
||||||
# bypass superuser
|
|
||||||
(not await SUPERUSER(bot, event))
|
|
||||||
# bypass group admin
|
|
||||||
or ((not session.member.role) or session.member.role.level <= 1)
|
|
||||||
)
|
|
||||||
and ((Image in msg) or (MarketFace in msg)) # msg has image
|
|
||||||
and judge_list( # 黑白名单
|
|
||||||
config.nailong_list_scenes,
|
|
||||||
session.scene_path,
|
|
||||||
config.nailong_blacklist,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
nailong = on_message(rule=Rule(nailong_rule))
|
|
||||||
|
|
||||||
|
|
||||||
|
nailong =on_message(rule=Rule(group_message_contains_image))
|
||||||
@nailong.handle()
|
@nailong.handle()
|
||||||
async def handle_function(
|
async def handle_function(bot: Bot, event: GroupMessageEvent):
|
||||||
m: Matcher,
|
group_id = event.group_id
|
||||||
bot: BaseBot,
|
member_info = await bot.get_group_member_info(group_id=group_id, user_id=event.self_id) # 管理员检测
|
||||||
ev: BaseEvent,
|
if member_info['role'] != 'admin' and member_info['role'] != 'owner':
|
||||||
msg: UniMsg,
|
return
|
||||||
session: Uninfo,
|
for seg in event.message:
|
||||||
state: T_State,
|
if seg.type == 'image' or seg.type == 'mface':
|
||||||
):
|
|
||||||
for seg in msg:
|
|
||||||
if isinstance(seg, Image):
|
|
||||||
image = await image_fetch(ev, bot, state, seg)
|
|
||||||
if not image:
|
|
||||||
logger.warning(f"Failed to fetch image: {seg!r}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
elif isinstance(seg, MarketFace):
|
|
||||||
url = f"https://gxh.vip.qq.com/club/item/parcel/item/{seg.id[:2]}/{seg.id}/raw300.gif"
|
|
||||||
req = Request("GET", url)
|
|
||||||
try:
|
try:
|
||||||
resp = await bot.adapter.request(req)
|
image_url = seg.data['url']
|
||||||
|
image = await download_image(image_url)
|
||||||
|
if check_image(image):
|
||||||
|
await bot.call_api('delete_msg', message_id=event.message_id)
|
||||||
|
await bot.send(event, "本群禁止发送奶龙!")
|
||||||
|
logger.info(f"撤回了包含奶龙的图片并发送警告:{image_url}")
|
||||||
|
else:
|
||||||
|
logger.debug("Image does not contain monkey.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to fetch {seg!r}: {type(e).__name__}: {e}")
|
logger.error(f"处理图片时出错:{e}")
|
||||||
continue
|
|
||||||
image = cast(bytes, resp.content)
|
|
||||||
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
ok = await run_sync(check_image)(
|
|
||||||
await run_sync(transform_image)(image),
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
logger.exception(f"Failed to process image: {seg!r}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if ok:
|
|
||||||
logger.info(f"尝试撤回包含奶龙的图片并发送警告:{seg!r}")
|
|
||||||
await (
|
|
||||||
UniMessage.template(config.nailong_tip)
|
|
||||||
.format(ev=ev, msg=msg, session=session)
|
|
||||||
.send()
|
|
||||||
)
|
|
||||||
if config.nailong_recall:
|
|
||||||
try:
|
|
||||||
await recall(bot, ev)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"{type(e).__name__}: {e}")
|
|
||||||
await m.finish()
|
|
||||||
@ -1,17 +1,14 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
from torch import Tensor, nn
|
from torch import nn
|
||||||
from torch.hub import load_state_dict_from_url
|
|
||||||
from torchvision import transforms
|
from torchvision import transforms
|
||||||
|
from torch.hub import load_state_dict_from_url
|
||||||
from .config import config
|
transform = transforms.Compose([transforms.ToTensor(),
|
||||||
|
transforms.Normalize(mean=0.5, std=0.5)])
|
||||||
transform = transforms.Compose(
|
|
||||||
[transforms.ToTensor(), transforms.Normalize(mean=0.5, std=0.5)],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Net(nn.Module):
|
class Net(nn.Module):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Net, self).__init__()
|
super(Net, self).__init__()
|
||||||
@ -32,7 +29,7 @@ class Net(nn.Module):
|
|||||||
self.drop = nn.Dropout(0.25)
|
self.drop = nn.Dropout(0.25)
|
||||||
self.lin2 = nn.Linear(64, 11)
|
self.lin2 = nn.Linear(64, 11)
|
||||||
|
|
||||||
def forward(self, x: Tensor):
|
def forward(self, x):
|
||||||
x = self.conv1(x)
|
x = self.conv1(x)
|
||||||
x = self.max1(x)
|
x = self.max1(x)
|
||||||
x = self.bn1(x)
|
x = self.bn1(x)
|
||||||
@ -48,31 +45,15 @@ class Net(nn.Module):
|
|||||||
x = self.fla(x)
|
x = self.fla(x)
|
||||||
x = self.lin1(x)
|
x = self.lin1(x)
|
||||||
x = self.drop(x)
|
x = self.drop(x)
|
||||||
return self.lin2(x)
|
x = self.lin2(x)
|
||||||
|
return x
|
||||||
|
model=Net()
|
||||||
model_filename = "nailong.pth"
|
if os.path.exists(os.path.join(Path(__file__).parent, 'nailong.pth')):
|
||||||
model = Net()
|
model.load_state_dict(torch.load(os.path.join(Path(__file__).parent, 'nailong.pth'), weights_only=True, map_location='cpu'))
|
||||||
if config.nailong_model_dir.exists():
|
|
||||||
model.load_state_dict(
|
|
||||||
torch.load(
|
|
||||||
config.nailong_model_dir / model_filename,
|
|
||||||
weights_only=True,
|
|
||||||
map_location="cpu",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
url = f"https://github.com/Refound-445/nonebot-plugin-nailongremove/releases/download/weights/{model_filename}"
|
url='https://github.com/Refound-445/nonebot-plugin-nailongremove/releases/download/weights/nailong.pth'
|
||||||
state_dict = load_state_dict_from_url(
|
state_dict=load_state_dict_from_url(url=url,model_dir=Path(__file__).parent,map_location='cpu',check_hash=True,progress=True)
|
||||||
url=url,
|
|
||||||
model_dir=str(config.nailong_model_dir),
|
|
||||||
map_location="cpu",
|
|
||||||
check_hash=True,
|
|
||||||
progress=True,
|
|
||||||
)
|
|
||||||
model.load_state_dict(state_dict)
|
model.load_state_dict(state_dict)
|
||||||
|
|
||||||
|
|
||||||
def check_image(image: np.ndarray) -> bool:
|
def check_image(image: np.ndarray) -> bool:
|
||||||
"""
|
"""
|
||||||
:param image: OpenCV图像数组。
|
:param image: OpenCV图像数组。
|
||||||
@ -80,6 +61,9 @@ def check_image(image: np.ndarray) -> bool:
|
|||||||
"""
|
"""
|
||||||
image = cv2.resize(image, (32, 32))
|
image = cv2.resize(image, (32, 32))
|
||||||
image = transform(image)
|
image = transform(image)
|
||||||
image = image.unsqueeze(0) # type: ignore
|
image = image.unsqueeze(0)
|
||||||
output = model(image)
|
output = model(image)
|
||||||
return output.argmax(1) == 10
|
if output.argmax(1)==10:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|||||||
@ -1,181 +0,0 @@
|
|||||||
from typing import Awaitable, Callable, Dict, TypeVar
|
|
||||||
from typing_extensions import TypeAlias
|
|
||||||
|
|
||||||
from nonebot.adapters import Bot as BaseBot, Event as BaseEvent
|
|
||||||
|
|
||||||
Recaller: TypeAlias = Callable[[BaseBot, BaseEvent], Awaitable[None]]
|
|
||||||
RT = TypeVar("RT", bound=Recaller)
|
|
||||||
|
|
||||||
recall_map: Dict[str, Recaller] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def recaller(adapter_name: str):
|
|
||||||
def deco(func: RT) -> RT:
|
|
||||||
recall_map[adapter_name] = func
|
|
||||||
return func
|
|
||||||
|
|
||||||
return deco
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Discord")
|
|
||||||
async def discord(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.discord import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.delete_message(channel_id=ev.channel_id, message_id=ev.message_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("DoDo")
|
|
||||||
async def dodo(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.dodo import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.set_channel_message_withdraw(message_id=ev.message_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Feishu")
|
|
||||||
async def feishu(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.feishu import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
return await bot.call_api(f"im/v1/messages/{ev.message_id}", method="DELETE")
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Kaiheila")
|
|
||||||
async def kook(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.kaiheila import Bot
|
|
||||||
from nonebot.adapters.kaiheila.event import MessageEvent, PrivateMessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
if isinstance(ev, PrivateMessageEvent):
|
|
||||||
await bot.directMessage_delete(msg_id=ev.msg_id)
|
|
||||||
else:
|
|
||||||
await bot.message_delete(msg_id=ev.msg_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Kritor")
|
|
||||||
async def kritor(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.kritor import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.recall_message(message_id=ev.message_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Mirai")
|
|
||||||
async def mirai(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.mirai import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.recall_message(message=ev.message_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("OneBot V11")
|
|
||||||
async def onebot_v11(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.delete_msg(message_id=ev.message_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("OneBot V12")
|
|
||||||
async def onebot_v12(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.onebot.v12 import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.delete_message(message_id=ev.message_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("QQ")
|
|
||||||
async def qq(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.qq import (
|
|
||||||
AtMessageCreateEvent,
|
|
||||||
Bot,
|
|
||||||
C2CMessageCreateEvent,
|
|
||||||
DirectMessageCreateEvent,
|
|
||||||
GroupAtMessageCreateEvent,
|
|
||||||
MessageCreateEvent,
|
|
||||||
MessageEvent,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(bot, Bot) and isinstance(ev, MessageEvent):
|
|
||||||
if isinstance(ev, C2CMessageCreateEvent): # 私聊
|
|
||||||
await bot.delete_c2c_message(openid=ev.author.id, message_id=ev.id)
|
|
||||||
elif isinstance(ev, GroupAtMessageCreateEvent): # 群聊
|
|
||||||
await bot.delete_group_message(
|
|
||||||
group_openid=ev.group_openid,
|
|
||||||
message_id=ev.id,
|
|
||||||
)
|
|
||||||
elif isinstance(ev, DirectMessageCreateEvent): # 频道私聊
|
|
||||||
await bot.delete_dms_message(guild_id=ev.guild_id, message_id=ev.id)
|
|
||||||
elif isinstance(ev, (AtMessageCreateEvent, MessageCreateEvent)): # 频道
|
|
||||||
await bot.delete_message(channel_id=ev.channel_id, message_id=ev.id)
|
|
||||||
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("RedProtocol")
|
|
||||||
async def red(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.red import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.recall_message(ev.chatType, ev.peerUin, ev.msgId)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Satori")
|
|
||||||
async def satori(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.satori import Bot, MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.message_delete(channel_id=ev.channel.id, message_id=ev.message.id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Telegram")
|
|
||||||
async def telegram(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot.adapters.telegram import Bot
|
|
||||||
from nonebot.adapters.telegram.event import MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
await bot.delete_message(chat_id=ev.chat.id, message_id=ev.message_id)
|
|
||||||
|
|
||||||
|
|
||||||
@recaller("Tailchat")
|
|
||||||
async def tailchat(bot: BaseBot, ev: BaseEvent):
|
|
||||||
from nonebot_adapter_tailchat import Bot
|
|
||||||
from nonebot_adapter_tailchat.event import MessageEvent
|
|
||||||
|
|
||||||
if not (isinstance(bot, Bot) and isinstance(ev, MessageEvent)):
|
|
||||||
raise TypeError("Unsupported bot or event type")
|
|
||||||
|
|
||||||
try:
|
|
||||||
await bot.recallMessage(messageId=ev.get_message_id())
|
|
||||||
except Exception:
|
|
||||||
await bot.deleteMessage(messageId=ev.get_message_id())
|
|
||||||
|
|
||||||
|
|
||||||
async def recall(bot: BaseBot, ev: BaseEvent):
|
|
||||||
f = recall_map.get(bot.adapter.get_name())
|
|
||||||
if f:
|
|
||||||
return await f(bot, ev)
|
|
||||||
raise NotImplementedError
|
|
||||||
@ -1,51 +1,35 @@
|
|||||||
[project]
|
[tool.poetry]
|
||||||
name = "nonebot-plugin-nailongremove"
|
name = "nonebot-plugin-nailongremove"
|
||||||
dynamic = ["version"]
|
version = "1.1.7"
|
||||||
description = "一个基于图像分类模型的撤回图片插件"
|
description = "一个基于图像分类模型的撤回图片插件"
|
||||||
authors = [{ name = "445", email = "2877834692@qq.com" }]
|
authors = ["445 <2877834692@qq.com>"]
|
||||||
requires-python = "<4.0,>=3.9"
|
|
||||||
dependencies = [
|
|
||||||
"nonebot2>=2.2.0",
|
|
||||||
"opencv-python>=4.5",
|
|
||||||
"numpy>=1.19",
|
|
||||||
"keras>=2.4",
|
|
||||||
"pillow>=10",
|
|
||||||
"torch>=2.4",
|
|
||||||
"torchvision>=0.19",
|
|
||||||
"nonebot-plugin-alconna>=0.53.1",
|
|
||||||
"nonebot-plugin-uninfo>=0.5.0",
|
|
||||||
]
|
|
||||||
license = { text = "MIT" }
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["nonebot", "plugin", "image"]
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
homepage = "https://github.com/Refound-445/onoebot-plugin-nailongremove"
|
homepage = "https://github.com/Refound-445/onoebot-plugin-nailongremove"
|
||||||
repository = "https://github.com/Refound-445/onoebot-plugin-nailongremove"
|
repository = "https://github.com/Refound-445/onoebot-plugin-nailongremove"
|
||||||
|
keywords = ["nonebot", "plugin", "image"]
|
||||||
|
include=['nonebot_plugin_nailongremove/Nailong(0.7123).pth']
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.9"
|
||||||
|
nonebot2 = "^2.2.0"
|
||||||
|
nonebot-adapter-onebot = "^2.0.0"
|
||||||
|
opencv-python = "^4.5"
|
||||||
|
numpy = "^1.19"
|
||||||
|
keras = "^2.4"
|
||||||
|
nonebot-plugin-apscheduler = "^0.4.0"
|
||||||
|
pillow="^10"
|
||||||
|
torch="^2.4"
|
||||||
|
torchvision="^0.19"
|
||||||
|
httpx = ">=0.27.0"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
pytest = "^5.2"
|
||||||
|
|
||||||
|
[[tool.poetry.source]]
|
||||||
|
name = "mirrors"
|
||||||
|
url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
|
||||||
|
priority = "primary"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["pdm-backend"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
build-backend = "pdm.backend"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.pdm.dev-dependencies]
|
|
||||||
dev = [
|
|
||||||
"nonebot-adapter-discord>=0.1.8",
|
|
||||||
"nonebot-adapter-dodo>=0.2.1",
|
|
||||||
"nonebot-adapter-feishu>=2.6.0",
|
|
||||||
"nonebot-adapter-kaiheila>=0.3.4",
|
|
||||||
"nonebot-adapter-kritor>=0.3.2",
|
|
||||||
"nonebot-adapter-mirai>=2.3.3",
|
|
||||||
"nonebot-adapter-onebot>=2.4.5",
|
|
||||||
"nonebot-adapter-qq>=1.5.2",
|
|
||||||
"nonebot-adapter-red>=0.9.0",
|
|
||||||
"nonebot-adapter-satori>=0.12.6",
|
|
||||||
"nonebot-adapter-telegram>=0.1.0b18",
|
|
||||||
"nonebot-adapter-tailchat>=0.1.0b12",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.pdm.build]
|
|
||||||
includes = []
|
|
||||||
|
|
||||||
[tool.pdm.version]
|
|
||||||
source = "file"
|
|
||||||
path = "nonebot_plugin_nailongremove/__init__.py"
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user