mirror of
https://github.com/Refound-445/nonebot-plugin-nailongremove.git
synced 2025-11-04 21:22:43 +08:00
refactor
This commit is contained in:
parent
008d3021ad
commit
80e25e5b69
76
PKG-INFO
76
PKG-INFO
@ -1,76 +0,0 @@
|
||||
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)。
|
||||
|
||||
|
||||
173
README.md
173
README.md
@ -1,52 +1,159 @@
|
||||
# nonebot_plugin_nailongremove
|
||||
<!-- markdownlint-disable MD031 MD033 MD036 MD041 -->
|
||||
|
||||
<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
|
||||
## 简介
|
||||
|
||||
NailongRemove是一款由简单的分类模型建立的奶龙识别插件,可以识别群中的奶龙表情包并撤回该表情。
|
||||
- 模型精度:目前模型只是轮廓所以精度相对较低,还在进一步优化中,如果您也是人工智能方面的兴趣爱好者,欢迎来群里交流学习和开发(人工智能学习交流群 949992679),届时我们会署上贡献者名单~
|
||||
- 关于插件安装:目前插件需要通过 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` | 是否使用黑名单模式 |
|
||||
|
||||
## 🎉 使用
|
||||
|
||||
## 使用
|
||||
只要有人发奶龙表情包被识别出来,就会被撤回并提醒。
|
||||
|
||||
## 即刻开始
|
||||
## 📞 联系
|
||||
|
||||
- 使用 nb-cli
|
||||
|
||||
```
|
||||
nb plugin install nonebot_plugin_nailongremove
|
||||
```
|
||||
|
||||
- 使用 pip
|
||||
|
||||
```
|
||||
pip install nonebot_plugin_nailongremove
|
||||
```
|
||||
## 其他
|
||||
- 人工智能学习交流群:949992679<span style="color: yellow;">(开源代码,数据集,从本群获取)</span>
|
||||
|
||||
|
||||
- 机器人插件学习交流群:200980266<span style="color: yellow;">(机器人及插件安装包,从本群获取)</span>
|
||||
|
||||
|
||||
欢迎大家进群一起学习交流~
|
||||
## 许可证
|
||||
|
||||
`noneBot_plugin_picsbank` 采用 `MIT` 协议开源,协议文件参考 [LICENSE](../plugins/nonebot_plugin_picsbank-master/LICENSE)。
|
||||
<!-- ## 📝 更新日志
|
||||
|
||||
芝士刚刚发布的插件,还没有更新日志的说 qwq~ -->
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
# ruff: noqa: E402
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import get_plugin_config, require
|
||||
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 nonebot import get_plugin_config
|
||||
from .handler import *
|
||||
|
||||
__version__ = "2.0.0"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="自动撤回奶龙",
|
||||
description="一个基于图像分类模型的简单插件~",
|
||||
@ -12,8 +17,9 @@ __plugin_meta__ = PluginMetadata(
|
||||
type="application",
|
||||
homepage="https://github.com/Refound-445/onoebot-plugin-nailongremove",
|
||||
config=Config,
|
||||
supported_adapters={"~onebot.v11"},
|
||||
supported_adapters=inherit_supported_adapters(
|
||||
"nonebot_plugin_alconna",
|
||||
"nonebot_plugin_uninfo",
|
||||
),
|
||||
)
|
||||
config = get_plugin_config(Config)
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
from pydantic import BaseModel
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from nonebot import get_plugin_config
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
"""Plugin Config Here"""
|
||||
nailong_model_dir: Path = Field(
|
||||
default_factory=lambda: Path.cwd() / "data" / "nailongremove",
|
||||
)
|
||||
nailong_list_scenes: List[str] = Field(default_factory=list)
|
||||
nailong_blacklist: bool = True
|
||||
|
||||
|
||||
config = get_plugin_config(Config)
|
||||
|
||||
@ -1,70 +1,120 @@
|
||||
import logging
|
||||
import io
|
||||
from typing import Iterable, TypeVar, cast
|
||||
|
||||
import httpx
|
||||
import cv2
|
||||
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 import on_message
|
||||
from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent
|
||||
from PIL import Image
|
||||
import io
|
||||
from .model import *
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.utils import run_sync
|
||||
from nonebot_plugin_alconna.builtins.uniseg.market_face import MarketFace
|
||||
from nonebot_plugin_alconna.uniseg import Image, UniMsg, image_fetch
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
from PIL import Image as PilImage
|
||||
|
||||
from .config import config
|
||||
from .model import check_image
|
||||
from .recall import recall
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
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
|
||||
def transform_image(image_data: bytes) -> np.ndarray:
|
||||
image = PilImage.open(io.BytesIO(image_data))
|
||||
|
||||
# 将字节数据加载为Pillow图像对象
|
||||
image = Image.open(io.BytesIO(image_data))
|
||||
if (
|
||||
# image.format == "GIF" and
|
||||
getattr(image, "is_animated", False)
|
||||
):
|
||||
# 处理动图:仅获取第一帧
|
||||
image = image.convert("RGB")
|
||||
image = image.copy() # 获取第一帧
|
||||
else:
|
||||
image = image.convert("RGB")
|
||||
|
||||
# 检查图像格式并处理
|
||||
if image.format == 'GIF' and hasattr(image, 'is_animated') and image.is_animated:
|
||||
# 处理GIF动图:仅获取第一帧
|
||||
image = image.convert("RGB")
|
||||
image = image.copy() # 获取第一帧
|
||||
else:
|
||||
# 对于非GIF图像(如JPEG)
|
||||
image = image.convert("RGB")
|
||||
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)
|
||||
|
||||
# 将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
|
||||
return image_array
|
||||
|
||||
|
||||
|
||||
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
|
||||
return False
|
||||
def judge_list(lst: Iterable[T], val: T, blacklist: bool) -> bool:
|
||||
return (val not in lst) if blacklist else (val in lst)
|
||||
|
||||
|
||||
async def nailong_rule(
|
||||
bot: BaseBot,
|
||||
event: BaseEvent,
|
||||
ss_info: Uninfo,
|
||||
msg: UniMsg,
|
||||
) -> bool:
|
||||
return (
|
||||
bool(ss_info.member) # 检查是否是群聊消息,此值仅在群聊与频道中存在
|
||||
and (
|
||||
# bypass superuser
|
||||
(not await SUPERUSER(bot, event))
|
||||
# bypass group admin
|
||||
or ((not ss_info.member.role) or ss_info.member.role.level <= 1)
|
||||
)
|
||||
and ((Image in msg) or (MarketFace in msg)) # msg has image
|
||||
and judge_list(
|
||||
config.nailong_list_scenes,
|
||||
ss_info.scene_path,
|
||||
config.nailong_blacklist,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
nailong = on_message(rule=Rule(nailong_rule))
|
||||
|
||||
|
||||
nailong =on_message(rule=Rule(group_message_contains_image))
|
||||
@nailong.handle()
|
||||
async def handle_function(bot: Bot, event: GroupMessageEvent):
|
||||
group_id = event.group_id
|
||||
member_info = await bot.get_group_member_info(group_id=group_id, user_id=event.self_id) # 管理员检测
|
||||
if member_info['role'] != 'admin' and member_info['role'] != 'owner':
|
||||
return
|
||||
for seg in event.message:
|
||||
if seg.type == 'image' or seg.type == 'mface':
|
||||
async def handle_function(
|
||||
m: Matcher,
|
||||
bot: BaseBot,
|
||||
ev: BaseEvent,
|
||||
msg: UniMsg,
|
||||
state: T_State,
|
||||
):
|
||||
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:
|
||||
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.")
|
||||
resp = await bot.adapter.request(req)
|
||||
except Exception as e:
|
||||
logger.error(f"处理图片时出错:{e}")
|
||||
logger.warning(f"Failed to fetch {seg!r}: {type(e).__name__}: {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 m.send("本群禁止发送奶龙!")
|
||||
try:
|
||||
await recall(bot, ev)
|
||||
except Exception as e:
|
||||
logger.warning(f"{type(e).__name__}: {e}")
|
||||
await m.finish()
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import nn
|
||||
from torchvision import transforms
|
||||
from torch import Tensor, nn
|
||||
from torch.hub import load_state_dict_from_url
|
||||
transform = transforms.Compose([transforms.ToTensor(),
|
||||
transforms.Normalize(mean=0.5, std=0.5)])
|
||||
from torchvision import transforms
|
||||
|
||||
from .config import config
|
||||
|
||||
transform = transforms.Compose(
|
||||
[transforms.ToTensor(), transforms.Normalize(mean=0.5, std=0.5)],
|
||||
)
|
||||
|
||||
|
||||
class Net(nn.Module):
|
||||
def __init__(self):
|
||||
super(Net, self).__init__()
|
||||
@ -29,7 +34,7 @@ class Net(nn.Module):
|
||||
self.drop = nn.Dropout(0.25)
|
||||
self.lin2 = nn.Linear(64, 11)
|
||||
|
||||
def forward(self, x):
|
||||
def forward(self, x: Tensor):
|
||||
x = self.conv1(x)
|
||||
x = self.max1(x)
|
||||
x = self.bn1(x)
|
||||
@ -45,15 +50,31 @@ class Net(nn.Module):
|
||||
x = self.fla(x)
|
||||
x = self.lin1(x)
|
||||
x = self.drop(x)
|
||||
x = self.lin2(x)
|
||||
return x
|
||||
model=Net()
|
||||
if os.path.exists(os.path.join(Path(__file__).parent, 'nailong.pth')):
|
||||
model.load_state_dict(torch.load(os.path.join(Path(__file__).parent, 'nailong.pth'), weights_only=True, map_location='cpu'))
|
||||
return self.lin2(x)
|
||||
|
||||
|
||||
model_filename = "nailong.pth"
|
||||
model = Net()
|
||||
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:
|
||||
url='https://github.com/Refound-445/nonebot-plugin-nailongremove/releases/download/weights/nailong.pth'
|
||||
state_dict=load_state_dict_from_url(url=url,model_dir=Path(__file__).parent,map_location='cpu',check_hash=True,progress=True)
|
||||
url = f"https://github.com/Refound-445/nonebot-plugin-nailongremove/releases/download/weights/{model_filename}"
|
||||
state_dict = load_state_dict_from_url(
|
||||
url=url,
|
||||
model_dir=str(config.nailong_model_dir),
|
||||
map_location="cpu",
|
||||
check_hash=True,
|
||||
progress=True,
|
||||
)
|
||||
model.load_state_dict(state_dict)
|
||||
|
||||
|
||||
def check_image(image: np.ndarray) -> bool:
|
||||
"""
|
||||
:param image: OpenCV图像数组。
|
||||
@ -61,9 +82,6 @@ def check_image(image: np.ndarray) -> bool:
|
||||
"""
|
||||
image = cv2.resize(image, (32, 32))
|
||||
image = transform(image)
|
||||
image = image.unsqueeze(0)
|
||||
image = image.unsqueeze(0) # type: ignore
|
||||
output = model(image)
|
||||
if output.argmax(1)==10:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return output.argmax(1) == 10
|
||||
|
||||
181
nonebot_plugin_nailongremove/recall.py
Normal file
181
nonebot_plugin_nailongremove/recall.py
Normal file
@ -0,0 +1,181 @@
|
||||
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,35 +1,51 @@
|
||||
[tool.poetry]
|
||||
[project]
|
||||
name = "nonebot-plugin-nailongremove"
|
||||
version = "1.1.7"
|
||||
dynamic = ["version"]
|
||||
description = "一个基于图像分类模型的撤回图片插件"
|
||||
authors = ["445 <2877834692@qq.com>"]
|
||||
authors = [{ name = "445", email = "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"
|
||||
keywords = ["nonebot", "plugin", "image"]
|
||||
|
||||
[project.urls]
|
||||
homepage = "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]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["pdm-backend"]
|
||||
build-backend = "pdm.backend"
|
||||
|
||||
[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