Skip to content

Commit

Permalink
feat: 支持多适配器 (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
MeetWq committed Nov 22, 2023
1 parent 676ff9e commit 42d9855
Show file tree
Hide file tree
Showing 6 changed files with 1,894 additions and 1,921 deletions.
149 changes: 39 additions & 110 deletions nonebot_plugin_imagetools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,47 @@
import imghdr
import math
import tempfile
import traceback
from datetime import datetime
from io import BytesIO
from itertools import chain
from pathlib import Path
from typing import List, Union
from zipfile import ZIP_BZIP2, ZipFile

from nonebot import on_command
from nonebot.adapters.onebot.v11 import Bot as V11Bot
from nonebot.adapters.onebot.v11 import GroupMessageEvent as V11GMEvent
from nonebot.adapters.onebot.v11 import Message as V11Msg
from nonebot.adapters.onebot.v11 import MessageEvent as V11MEvent
from nonebot.adapters.onebot.v11 import MessageSegment as V11MsgSeg
from nonebot.adapters.onebot.v12 import Bot as V12Bot
from nonebot.adapters.onebot.v12 import MessageSegment as V12MsgSeg
from nonebot import on_command, require
from nonebot.adapters import Bot, Event
from nonebot.log import logger
from nonebot.matcher import Matcher
from nonebot.params import Depends
from nonebot.plugin import PluginMetadata
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
from nonebot.typing import T_Handler
from nonebot.utils import run_sync
from PIL.Image import Image as IMG
from pil_utils import BuildImage, Text2Image

from .config import imagetools_config
require("nonebot_plugin_saa")
require("nonebot_plugin_alconna")

from nonebot_plugin_alconna import File, UniMessage
from nonebot_plugin_saa import AggregatedMessageFactory, Image, MessageFactory

from .config import Config, imagetools_config
from .data_source import commands
from .utils import Command

__plugin_meta__ = PluginMetadata(
name="图片操作",
description="简单图片操作",
usage="发送“图片操作”查看支持的指令",
type="application",
homepage="https://github.com/noneplugin/nonebot-plugin-imagetools",
config=Config,
supported_adapters=inherit_supported_adapters(
"nonebot_plugin_saa", "nonebot_plugin_alconna"
),
extra={
"unique_name": "imagetools",
"example": "旋转 [图片]",
"author": "meetwq <[email protected]>",
"version": "0.2.1",
"version": "0.3.0",
},
)

Expand Down Expand Up @@ -75,75 +79,41 @@ def cmd_text(commands: List[Command], start: int = 1) -> str:


@help_cmd.handle()
async def _(bot: Union[V11Bot, V12Bot], matcher: Matcher):
async def _():
img = await help_image()

if isinstance(bot, V11Bot):
await matcher.finish(V11MsgSeg.image(img))
else:
resp = await bot.upload_file(
type="data", name="imagetools", data=img.getvalue()
)
file_id = resp["file_id"]
await matcher.finish(V12MsgSeg.image(file_id))
await MessageFactory([Image(img)]).send()


def handler_v11(command: Command) -> T_Handler:
def handler(command: Command) -> T_Handler:
async def handle(
bot: V11Bot,
event: V11MEvent,
bot: Bot,
event: Event,
matcher: Matcher,
res: Union[str, BytesIO, List[BytesIO]] = Depends(command.func),
):
if isinstance(res, str):
await matcher.finish(res)
elif isinstance(res, BytesIO):
await matcher.finish(V11MsgSeg.image(res))
await MessageFactory([Image(res)]).send()
else:
if len(res) > imagetools_config.imagetools_zip_threshold:
zip_file = zip_images(res)
filename = f"{command.keywords[0]}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.zip"
try:
await upload_file(bot, event, zip_file, filename)
except Exception as e:
logger.warning(f"上传文件失败:{e.__class__.__name__}: {e}")

msgs: List[V11Msg] = [V11Msg(V11MsgSeg.image(msg)) for msg in res]
max_forward_msg_num = imagetools_config.max_forward_msg_num
# 超出最大转发消息条数时,改为一条消息包含多张图片
if len(msgs) > max_forward_msg_num:
step = math.ceil(len(msgs) / max_forward_msg_num)
msgs = [
V11Msg(chain.from_iterable(msgs[i : i + step]))
for i in range(0, len(msgs) - 1, step)
]
await send_forward_msg(bot, event, "imagetools", bot.self_id, msgs)

return handle


def handler_v12(command: Command) -> T_Handler:
async def handle(
bot: V12Bot,
matcher: Matcher,
res: Union[str, BytesIO, List[BytesIO]] = Depends(command.func),
):
if isinstance(res, str):
await matcher.finish(res)
elif isinstance(res, BytesIO):
resp = await bot.upload_file(
type="data", name="imagetools", data=res.getvalue()
)
file_id = resp["file_id"]
await matcher.finish(V12MsgSeg.image(file_id))
else:
zip_file = zip_images(res)
filename = f"{command.keywords[0]}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.zip"
resp = await bot.upload_file(
type="data", name=filename, data=zip_file.getvalue()
)
file_id = resp["file_id"]
await matcher.finish(V12MsgSeg.file(file_id))
file = File(
raw=zip_file.getvalue(),
name=filename,
mimetype="application/zip",
)
await UniMessage([file]).send(event, bot)
except:
logger.warning(f"上传文件失败:{traceback.format_exc()}")

if len(res) <= imagetools_config.imagetools_max_forward_msg_num:
try:
await AggregatedMessageFactory([Image(img) for img in res]).send()
except:
logger.warning(f"合并消息发送失败: {traceback.format_exc()}")

return handle

Expand All @@ -153,34 +123,12 @@ def create_matchers():
matcher = on_command(
command.keywords[0], aliases=set(command.keywords), block=True, priority=12
)
matcher.append_handler(handler_v11(command))
matcher.append_handler(handler_v12(command))
matcher.append_handler(handler(command))


create_matchers()


async def send_forward_msg(
bot: V11Bot,
event: V11MEvent,
name: str,
uin: str,
msgs: List[V11Msg],
):
def to_json(msg):
return {"type": "node", "data": {"name": name, "uin": uin, "content": msg}}

messages = [to_json(msg) for msg in msgs]
if isinstance(event, V11GMEvent):
await bot.call_api(
"send_group_forward_msg", group_id=event.group_id, messages=messages
)
else:
await bot.call_api(
"send_private_forward_msg", user_id=event.user_id, messages=messages
)


def zip_images(files: List[BytesIO]):
output = BytesIO()
with ZipFile(output, "w", ZIP_BZIP2) as zip_file:
Expand All @@ -189,22 +137,3 @@ def zip_images(files: List[BytesIO]):
ext = imghdr.what(None, h=file_bytes)
zip_file.writestr(f"{i}.{ext}", file_bytes)
return output


async def upload_file(
bot: V11Bot,
event: V11MEvent,
file: BytesIO,
filename: str,
):
with tempfile.TemporaryDirectory() as temp_dir:
with open(Path(temp_dir) / filename, "wb") as f:
f.write(file.getbuffer())
if isinstance(event, V11GMEvent):
await bot.call_api(
"upload_group_file", group_id=event.group_id, file=f.name, name=filename
)
else:
await bot.call_api(
"upload_private_file", user_id=event.user_id, file=f.name, name=filename
)
2 changes: 1 addition & 1 deletion nonebot_plugin_imagetools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Config(BaseModel, extra=Extra.ignore):
"""
输出图片数量大于该数目时,打包为zip以文件形式发送
"""
max_forward_msg_num: int = 99
imagetools_max_forward_msg_num: int = 99
"""
合并转发消息条数上限
"""
Expand Down
71 changes: 34 additions & 37 deletions nonebot_plugin_imagetools/depends.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
import shlex
import traceback
from io import BytesIO
from typing import List, Union

from nonebot.adapters.onebot.v11 import Bot as V11Bot
from nonebot.adapters.onebot.v11 import Message as V11Msg
from nonebot.adapters.onebot.v11 import MessageEvent as V11MEvent
from nonebot.adapters.onebot.v11.utils import unescape
from nonebot.adapters.onebot.v12 import Bot as V12Bot
from nonebot.adapters.onebot.v12 import Message as V12Msg
from nonebot.adapters.onebot.v12 import MessageEvent as V12MEvent
from typing import List

from nonebot.adapters import Bot, Event, Message
from nonebot.log import logger
from nonebot.params import CommandArg, Depends
from nonebot.typing import T_State
from nonebot_plugin_alconna import Image, UniMessage, image_fetch
from nonebot_plugin_alconna.uniseg.segment import reply_handle
from pil_utils import BuildImage
from typing_extensions import Literal

from .utils import download_url


def Imgs():
async def dependency(
bot: Union[V11Bot, V12Bot],
event: Union[V11MEvent, V12MEvent],
msg: Union[V11Msg, V12Msg] = CommandArg(),
bot: Bot, event: Event, state: T_State, msg: Message = CommandArg()
):
urls: List[str] = []
if isinstance(bot, V11Bot):
assert isinstance(event, V11MEvent)
assert isinstance(msg, V11Msg)
img_segs = msg["image"]
if event.reply:
img_segs = event.reply.message["image"].extend(img_segs)
urls = [seg.data["url"] for seg in img_segs]
else:
assert isinstance(event, V12MEvent)
assert isinstance(msg, V12Msg)
img_segs = msg["image"]
for seg in img_segs:
file_id = seg.data["file_id"]
data = await bot.get_file(type="url", file_id=file_id)
urls.append(data["url"])

return [BuildImage.open(BytesIO(await download_url(url))) for url in urls]
imgs: List[bytes] = []

uni_msg = UniMessage()
if msg:
uni_msg = await UniMessage.generate(message=msg)
uni_msg_with_reply = UniMessage()
if reply := await reply_handle(event, bot):
if isinstance(reply.msg, Message) and reply.msg:
uni_msg_with_reply = await UniMessage.generate(message=reply.msg)
uni_msg_with_reply.extend(uni_msg)

for seg in uni_msg_with_reply:
if isinstance(seg, Image):
try:
result = await image_fetch(
bot=bot, event=event, state=state, img=seg
)
if isinstance(result, bytes):
imgs.append(result)
except:
logger.warning(f"Fail to fetch image: {traceback.format_exc()}")

return [BuildImage.open(BytesIO(img)) for img in imgs]

return Depends(dependency)

Expand All @@ -53,11 +53,8 @@ async def dependency(imgs: List[BuildImage] = Imgs()):


def Arg():
async def dependency(msg: Union[V11Msg, V12Msg] = CommandArg()):
if isinstance(msg, V11Msg):
return unescape(msg.extract_plain_text().strip())
else:
return msg.extract_plain_text().strip()
async def dependency(arg: Message = CommandArg()):
return arg.extract_plain_text().strip()

return Depends(dependency)

Expand Down
10 changes: 7 additions & 3 deletions nonebot_plugin_imagetools/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from io import BytesIO
from typing import List, Optional

from PIL import Image, ImageFilter, ImageOps
from PIL import ImageFilter, ImageOps
from PIL.Image import Transpose
from PIL.ImageColor import colormap
from pil_utils import BuildImage, text2image
from pil_utils.gradient import ColorStop, LinearGradient
Expand All @@ -21,11 +22,11 @@


def flip_horizontal(img: BuildImage = Img(), arg=NoArg()):
return make_jpg_or_gif(img, lambda img: img.transpose(Image.FLIP_LEFT_RIGHT))
return make_jpg_or_gif(img, lambda img: img.transpose(Transpose.FLIP_LEFT_RIGHT))


def flip_vertical(img: BuildImage = Img(), arg=NoArg()):
return make_jpg_or_gif(img, lambda img: img.transpose(Image.FLIP_TOP_BOTTOM))
return make_jpg_or_gif(img, lambda img: img.transpose(Transpose.FLIP_TOP_BOTTOM))


def grey(img: BuildImage = Img(), arg=NoArg()):
Expand Down Expand Up @@ -132,6 +133,7 @@ def color_mask(img: BuildImage = Img(), arg: str = Arg()):
color = arg
elif match := re.fullmatch(color_pattern_num, arg):
color = tuple(map(int, match.groups()))
assert len(color) == 3
elif arg in color_table:
color = color_table[arg]
else:
Expand All @@ -144,6 +146,7 @@ def color_image(arg: str = Arg()):
color = arg
elif match := re.fullmatch(color_pattern_num, arg):
color = tuple(map(int, match.groups()))
assert len(color) == 3
elif arg in color_table:
color = color_table[arg]
else:
Expand Down Expand Up @@ -173,6 +176,7 @@ def gradient_image(args: List[str] = Args()):
color = arg
elif match := re.fullmatch(color_pattern_num, arg):
color = tuple(map(int, match.groups()))
assert len(color) == 3
elif arg in color_table:
color = color_table[arg]
else:
Expand Down
Loading

0 comments on commit 42d9855

Please sign in to comment.