commit
deae8e4888
|
@ -0,0 +1,138 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
|
@ -0,0 +1,10 @@
|
|||
from . import config, manage
|
||||
from .aidraw import AIDRAW
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .extension.deepdanbooru import deepdanbooru
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="AI绘图",
|
||||
description="调用novelai进行二次元AI绘图",
|
||||
usage=f"基础用法:\n.aidraw[指令] [空格] loli,[参数]\n示例:.aidraw loli,cute,kawaii,\n项目地址:https://github.com/Mutsukibot/tree/nonebot-plugin-novelai\n说明书:https://sena-nana.github.io/MutsukiDocs/",
|
||||
)
|
||||
__all__ = ["AIDRAW", "__plugin_meta__"]
|
|
@ -0,0 +1,253 @@
|
|||
import time
|
||||
import re
|
||||
|
||||
from collections import deque
|
||||
import aiohttp
|
||||
from aiohttp.client_exceptions import ClientConnectorError, ClientOSError
|
||||
from argparse import Namespace
|
||||
from asyncio import get_running_loop
|
||||
from nonebot import get_bot, on_shell_command
|
||||
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageSegment, Bot
|
||||
from nonebot.rule import ArgumentParser
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.log import logger
|
||||
from nonebot.params import ShellCommandArgs
|
||||
|
||||
from .config import config
|
||||
from .utils.data import lowQuality, basetag, htags
|
||||
from .backend import AIDRAW
|
||||
from .extension.anlas import anlas_check, anlas_set
|
||||
from .extension.daylimit import DayLimit
|
||||
from .utils.save import save_img
|
||||
from .utils.prepocess import prepocess_tags, combine_multi_args
|
||||
from .version import version
|
||||
from .utils import sendtosuperuser
|
||||
cd = {}
|
||||
gennerating = False
|
||||
wait_list = deque([])
|
||||
|
||||
aidraw_parser = ArgumentParser()
|
||||
aidraw_parser.add_argument("tags", nargs="*", help="标签")
|
||||
aidraw_parser.add_argument("-r", "--resolution", "-形状",
|
||||
help="画布形状/分辨率", dest="shape")
|
||||
aidraw_parser.add_argument("-c", "--scale", "-服从",
|
||||
type=float, help="对输入的服从度", dest="scale")
|
||||
aidraw_parser.add_argument(
|
||||
"-s", "--seed", "-种子", type=int, help="种子", dest="seed")
|
||||
aidraw_parser.add_argument("-b", "--batch", "-数量",
|
||||
type=int, default=1, help="生成数量", dest="batch")
|
||||
aidraw_parser.add_argument("-t", "--steps", "-步数",
|
||||
type=int, help="步数", dest="steps")
|
||||
aidraw_parser.add_argument("-u", "--ntags", "-排除",
|
||||
default=" ", nargs="*", help="负面标签", dest="ntags")
|
||||
aidraw_parser.add_argument("-e", "--strength", "-强度",
|
||||
type=float, help="修改强度", dest="strength")
|
||||
aidraw_parser.add_argument("-n", "--noise", "-噪声",
|
||||
type=float, help="修改噪声", dest="noise")
|
||||
aidraw_parser.add_argument("-o", "--override", "-不优化",
|
||||
action='store_true', help="不使用内置优化参数", dest="override")
|
||||
aidraw_parser.add_argument("--sampler", "-采样器",
|
||||
default="Euler a", nargs="+", help="设置采样器", dest="sampler")
|
||||
aidraw_parser.add_argument("--hires", "-高清修复",
|
||||
action='store_true', help="启用高清修复", dest="hires")
|
||||
|
||||
aidraw = on_shell_command(
|
||||
".aidraw",
|
||||
aliases={"绘画", "咏唱", "召唤", "约稿", "aidraw"},
|
||||
parser=aidraw_parser,
|
||||
priority=5
|
||||
)
|
||||
|
||||
|
||||
@aidraw.handle()
|
||||
async def aidraw_get(bot: Bot, event: GroupMessageEvent, args: Namespace = ShellCommandArgs()):
|
||||
user_id = str(event.user_id)
|
||||
group_id = str(event.group_id)
|
||||
# 判断是否禁用,若没禁用,进入处理流程
|
||||
if await config.get_value(group_id, "on"):
|
||||
message = ""
|
||||
# 判断最大生成数量
|
||||
if args.batch > config.novelai_max:
|
||||
message = message+f",批量生成数量过多,自动修改为{config.novelai_max}"
|
||||
args.batch = config.novelai_max
|
||||
# 判断次数限制
|
||||
if config.novelai_daylimit and not await SUPERUSER(bot, event):
|
||||
left = DayLimit.count(user_id, args.batch)
|
||||
if left == -1:
|
||||
await aidraw.finish(f"今天你的次数不够了哦")
|
||||
else:
|
||||
message = message + f",今天你还能够生成{left}张"
|
||||
# 判断cd
|
||||
nowtime = time.time()
|
||||
deltatime = nowtime - cd.get(user_id, 0)
|
||||
cd_ = int(await config.get_value(group_id, "cd"))
|
||||
if deltatime < cd_:
|
||||
await aidraw.finish(f"你冲的太快啦,请休息一下吧,剩余CD为{cd_ - int(deltatime)}s")
|
||||
else:
|
||||
cd[user_id] = nowtime
|
||||
# 初始化参数
|
||||
args.tags = await prepocess_tags(args.tags)
|
||||
args.ntags = await prepocess_tags(args.ntags)
|
||||
args.sampler = await combine_multi_args(args.sampler)
|
||||
fifo = AIDRAW(user_id=user_id, group_id=group_id, **vars(args))
|
||||
# 检测是否有18+词条
|
||||
if not config.novelai_h:
|
||||
pattern = re.compile(f"(\s|,|^)({htags})(\s|,|$)")
|
||||
if (re.search(pattern, fifo.tags) is not None):
|
||||
await aidraw.finish(f"H是不行的!")
|
||||
if not args.override:
|
||||
fifo.tags = basetag + await config.get_value(group_id, "tags") + "," + fifo.tags
|
||||
fifo.ntags = lowQuality + fifo.ntags
|
||||
|
||||
# 以图生图预处理
|
||||
img_url = ""
|
||||
reply = event.reply
|
||||
if reply:
|
||||
for seg in reply.message['image']:
|
||||
img_url = seg.data["url"]
|
||||
for seg in event.message['image']:
|
||||
img_url = seg.data["url"]
|
||||
if img_url:
|
||||
if config.novelai_paid:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
logger.info(f"检测到图片,自动切换到以图生图,正在获取图片")
|
||||
async with session.get(img_url) as resp:
|
||||
fifo.add_image(await resp.read())
|
||||
message = f",已切换至以图生图"+message
|
||||
else:
|
||||
await aidraw.finish(f"以图生图功能已禁用")
|
||||
logger.debug(fifo)
|
||||
# 初始化队列
|
||||
if fifo.cost > 0:
|
||||
anlascost = fifo.cost
|
||||
hasanlas = await anlas_check(fifo.user_id)
|
||||
if hasanlas >= anlascost:
|
||||
await wait_fifo(fifo, anlascost, hasanlas - anlascost, message=message, bot=bot)
|
||||
else:
|
||||
await aidraw.finish(f"你的点数不足,你的剩余点数为{hasanlas}")
|
||||
else:
|
||||
await wait_fifo(fifo, message=message, bot=bot)
|
||||
|
||||
|
||||
async def wait_fifo(fifo, anlascost=None, anlas=None, message="", bot=None):
|
||||
# 创建队列
|
||||
list_len = wait_len()
|
||||
has_wait = f"排队中,你的前面还有{list_len}人"+message
|
||||
no_wait = "请稍等,图片生成中"+message
|
||||
if anlas:
|
||||
has_wait += f"\n本次生成消耗点数{anlascost},你的剩余点数为{anlas}"
|
||||
no_wait += f"\n本次生成消耗点数{anlascost},你的剩余点数为{anlas}"
|
||||
if config.novelai_limit:
|
||||
await aidraw.send(has_wait if list_len > 0 else no_wait)
|
||||
wait_list.append(fifo)
|
||||
await fifo_gennerate(bot=bot)
|
||||
else:
|
||||
await aidraw.send(no_wait)
|
||||
await fifo_gennerate(fifo, bot)
|
||||
|
||||
|
||||
def wait_len():
|
||||
# 获取剩余队列长度
|
||||
list_len = len(wait_list)
|
||||
if gennerating:
|
||||
list_len += 1
|
||||
return list_len
|
||||
|
||||
|
||||
async def fifo_gennerate(fifo: AIDRAW = None, bot: Bot = None):
|
||||
# 队列处理
|
||||
global gennerating
|
||||
if not bot:
|
||||
bot = get_bot()
|
||||
|
||||
async def generate(fifo: AIDRAW):
|
||||
id = fifo.user_id if config.novelai_antireport else bot.self_id
|
||||
resp = await bot.get_group_member_info(group_id=fifo.group_id, user_id=fifo.user_id)
|
||||
nickname = resp["card"] or resp["nickname"]
|
||||
|
||||
# 开始生成
|
||||
logger.info(
|
||||
f"队列剩余{wait_len()}人 | 开始生成:{fifo}")
|
||||
try:
|
||||
im = await _run_gennerate(fifo)
|
||||
except Exception as e:
|
||||
logger.exception("生成失败")
|
||||
message = f"生成失败,"
|
||||
for i in e.args:
|
||||
message += str(i)
|
||||
await bot.send_group_msg(
|
||||
message=message,
|
||||
group_id=fifo.group_id
|
||||
)
|
||||
else:
|
||||
logger.info(f"队列剩余{wait_len()}人 | 生成完毕:{fifo}")
|
||||
if await config.get_value(fifo.group_id, "pure"):
|
||||
message = MessageSegment.at(fifo.user_id)
|
||||
for i in im["image"]:
|
||||
message += i
|
||||
message_data = await bot.send_group_msg(
|
||||
message=message,
|
||||
group_id=fifo.group_id,
|
||||
)
|
||||
else:
|
||||
message = []
|
||||
for i in im:
|
||||
message.append(MessageSegment.node_custom(
|
||||
id, nickname, i))
|
||||
message_data = await bot.send_group_forward_msg(
|
||||
messages=message,
|
||||
group_id=fifo.group_id,
|
||||
)
|
||||
revoke = await config.get_value(fifo.group_id, "revoke")
|
||||
if revoke:
|
||||
message_id = message_data["message_id"]
|
||||
loop = get_running_loop()
|
||||
loop.call_later(
|
||||
revoke,
|
||||
lambda: loop.create_task(
|
||||
bot.delete_msg(message_id=message_id)),
|
||||
)
|
||||
|
||||
if fifo:
|
||||
await generate(fifo)
|
||||
|
||||
if not gennerating:
|
||||
logger.info("队列开始")
|
||||
gennerating = True
|
||||
|
||||
while len(wait_list) > 0:
|
||||
fifo = wait_list.popleft()
|
||||
try:
|
||||
await generate(fifo)
|
||||
except:
|
||||
pass
|
||||
|
||||
gennerating = False
|
||||
logger.info("队列结束")
|
||||
await version.check_update()
|
||||
|
||||
|
||||
async def _run_gennerate(fifo: AIDRAW):
|
||||
# 处理单个请求
|
||||
try:
|
||||
await fifo.post()
|
||||
except ClientConnectorError:
|
||||
await sendtosuperuser(f"远程服务器拒绝连接,请检查配置是否正确,服务器是否已经启动")
|
||||
raise RuntimeError(f"远程服务器拒绝连接,请检查配置是否正确,服务器是否已经启动")
|
||||
except ClientOSError:
|
||||
await sendtosuperuser(f"远程服务器崩掉了欸……")
|
||||
raise RuntimeError(f"服务器崩掉了欸……请等待主人修复吧")
|
||||
# 若启用ai检定,取消注释下行代码,并将构造消息体部分注释
|
||||
# message = await check_safe_method(fifo, img_bytes, message)
|
||||
# 构造消息体并保存图片
|
||||
message = f"{config.novelai_mode}绘画完成~"
|
||||
for i in fifo.result:
|
||||
await save_img(fifo, i, fifo.group_id)
|
||||
message += MessageSegment.image(i)
|
||||
for i in fifo.format():
|
||||
message += MessageSegment.text(i)
|
||||
# 扣除点数
|
||||
if fifo.cost > 0:
|
||||
await anlas_set(fifo.user_id, -fifo.cost)
|
||||
return message
|
|
@ -0,0 +1,20 @@
|
|||
from ..config import config
|
||||
"""def AIDRAW():
|
||||
if config.novelai_mode=="novelai":
|
||||
from .novelai import AIDRAW
|
||||
elif config.novelai_mode=="naifu":
|
||||
from .naifu import AIDRAW
|
||||
elif config.novelai_mode=="sd":
|
||||
from .sd import AIDRAW
|
||||
else:
|
||||
raise RuntimeError(f"错误的mode设置,支持的字符串为'novelai','naifu','sd'")
|
||||
return AIDRAW()"""
|
||||
|
||||
if config.novelai_mode=="novelai":
|
||||
from .novelai import AIDRAW
|
||||
elif config.novelai_mode=="naifu":
|
||||
from .naifu import AIDRAW
|
||||
elif config.novelai_mode=="sd":
|
||||
from .sd import AIDRAW
|
||||
else:
|
||||
raise RuntimeError(f"错误的mode设置,支持的字符串为'novelai','naifu','sd'")
|
|
@ -0,0 +1,266 @@
|
|||
import asyncio
|
||||
import base64
|
||||
import random
|
||||
import time
|
||||
from io import BytesIO
|
||||
|
||||
import aiohttp
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
from PIL import Image
|
||||
|
||||
from ..config import config
|
||||
from ..utils import png2jpg
|
||||
from ..utils.data import shapemap
|
||||
|
||||
|
||||
class AIDRAW_BASE:
|
||||
max_resolution: int = 16
|
||||
sampler: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_id: str,
|
||||
group_id: str,
|
||||
tags: str = "",
|
||||
ntags: str = "",
|
||||
seed: int = None,
|
||||
scale: int = None,
|
||||
steps: int = None,
|
||||
batch: int = None,
|
||||
strength: float = None,
|
||||
noise: float = None,
|
||||
shape: str = "p",
|
||||
model: str = None,
|
||||
sampler: str = "",
|
||||
hires: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
AI绘画的核心部分,将与服务器通信的过程包装起来,并方便扩展服务器类型
|
||||
|
||||
:user_id: 用户id,必须
|
||||
:group_id: 群聊id,如果是私聊则应置为0,必须
|
||||
:tags: 图像的标签
|
||||
:ntags: 图像的反面标签
|
||||
:seed: 生成的种子,不指定的情况下随机生成
|
||||
:scale: 标签的参考度,值越高越贴近于标签,但可能产生过度锐化。范围为0-30,默认11
|
||||
:steps: 训练步数。范围为1-50,默认28.以图生图时强制50
|
||||
:batch: 同时生成数量
|
||||
:strength: 以图生图时使用,变化的强度。范围为0-1,默认0.7
|
||||
:noise: 以图生图时使用,变化的噪音,数值越大细节越多,但可能产生伪影,不建议超过strength。范围0-1,默认0.2
|
||||
:shape: 图像的形状,支持"p""s""l"三种,同时支持以"x"分割宽高的指定分辨率。
|
||||
该值会被设置限制,并不会严格遵守输入
|
||||
类初始化后,该参数会被拆分为:width:和:height:
|
||||
:model: 指定的模型,模型名称在配置文件中手动设置。不指定模型则按照负载均衡自动选择
|
||||
|
||||
AIDRAW还包含了以下几种内置的参数
|
||||
:status: 记录了AIDRAW的状态,默认为0等待中(处理中)
|
||||
非0的值为运行完成后的状态值,200和201为正常运行,其余值为产生错误
|
||||
:result: 当正常运行完成后,该参数为一个包含了生成图片bytes信息的数组
|
||||
:maxresolution: 一般不用管,用于限制不同服务器的最大分辨率
|
||||
如果你的SD经过了魔改支持更大分辨率可以修改该值并重新设置宽高
|
||||
:cost: 记录了本次生成需要花费多少点数,自动计算
|
||||
:signal: asyncio.Event类,可以作为信号使用。仅占位,需要自行实现相关方法
|
||||
"""
|
||||
self.status: int = 0
|
||||
self.result: list = []
|
||||
self.signal: asyncio.Event = None
|
||||
self.model = model
|
||||
self.time = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.user_id: str = user_id
|
||||
self.tags: str = tags
|
||||
self.seed: list[int] = [seed or random.randint(0, 4294967295)]
|
||||
self.group_id: str = group_id
|
||||
self.scale: int = int(scale or 11)
|
||||
self.strength: float = strength or 0.7
|
||||
self.batch: int = batch or 1
|
||||
self.steps: int = steps or 28
|
||||
self.noise: float = noise or 0.2
|
||||
self.ntags: str = ntags
|
||||
self.img2img: bool = False
|
||||
self.image: str = None
|
||||
self.width, self.height = self.extract_shape(shape)
|
||||
self.sampler = sampler
|
||||
self.hires = hires
|
||||
# 数值合法检查
|
||||
if self.steps <= 0 or self.steps > (50 if not config.novelai_paid else 28):
|
||||
self.steps = 28
|
||||
if self.strength < 0 or self.strength > 1:
|
||||
self.strength = 0.7
|
||||
if self.noise < 0 or self.noise > 1:
|
||||
self.noise = 0.2
|
||||
if self.scale <= 0 or self.scale > 30:
|
||||
self.scale = 11
|
||||
# 多图时随机填充剩余seed
|
||||
for i in range(self.batch - 1):
|
||||
self.seed.append(random.randint(0, 4294967295))
|
||||
# 计算cost
|
||||
self.update_cost()
|
||||
|
||||
def extract_shape(self, shape: str):
|
||||
"""
|
||||
将shape拆分为width和height
|
||||
"""
|
||||
if shape:
|
||||
if "x" in shape:
|
||||
width, height, *_ = shape.split("x")
|
||||
if width.isdigit() and height.isdigit():
|
||||
return self.shape_set(int(width), int(height))
|
||||
else:
|
||||
return shapemap.get(shape)
|
||||
else:
|
||||
return shapemap.get(shape)
|
||||
else:
|
||||
return (512, 768)
|
||||
|
||||
def update_cost(self):
|
||||
"""
|
||||
更新cost
|
||||
"""
|
||||
if config.novelai_paid == 1:
|
||||
anlas = 0
|
||||
if (self.width * self.height > 409600) or self.image or self.batch > 1:
|
||||
anlas = round(
|
||||
self.width
|
||||
* self.height
|
||||
* self.strength
|
||||
* self.batch
|
||||
* self.steps
|
||||
/ 2293750
|
||||
)
|
||||
if anlas < 2:
|
||||
anlas = 2
|
||||
if self.user_id in get_driver().config.superusers:
|
||||
self.cost = 0
|
||||
else:
|
||||
self.cost = anlas
|
||||
elif config.novelai_paid == 2:
|
||||
anlas = round(
|
||||
self.width
|
||||
* self.height
|
||||
* self.strength
|
||||
* self.batch
|
||||
* self.steps
|
||||
/ 2293750
|
||||
)
|
||||
if anlas < 2:
|
||||
anlas = 2
|
||||
if self.user_id in get_driver().config.superusers:
|
||||
self.cost = 0
|
||||
else:
|
||||
self.cost = anlas
|
||||
else:
|
||||
self.cost = 0
|
||||
|
||||
def add_image(self, image: bytes):
|
||||
"""
|
||||
向类中添加图片,将其转化为以图生图模式
|
||||
也可用于修改类中已经存在的图片
|
||||
"""
|
||||
# 根据图片重写长宽
|
||||
tmpfile = BytesIO(image)
|
||||
image_ = Image.open(tmpfile)
|
||||
width, height = image_.size
|
||||
self.width, self.height = self.shape_set(width, height)
|
||||
self.image = str(base64.b64encode(image), "utf-8")
|
||||
self.steps = 50
|
||||
self.img2img = True
|
||||
self.update_cost()
|
||||
|
||||
def shape_set(self, width: int, height: int):
|
||||
"""
|
||||
设置宽高
|
||||
"""
|
||||
limit = 1024 if config.paid else 640
|
||||
if width * height > pow(min(config.novelai_size, limit), 2):
|
||||
if width <= height:
|
||||
ratio = height / width
|
||||
width: float = config.novelai_size / pow(ratio, 0.5)
|
||||
height: float = width * ratio
|
||||
else:
|
||||
ratio = width / height
|
||||
height: float = config.novelai_size / pow(ratio, 0.5)
|
||||
width: float = height * ratio
|
||||
base = round(max(width, height) / 64)
|
||||
if base > self.max_resolution:
|
||||
base = self.max_resolution
|
||||
if width <= height:
|
||||
return (round(width / height * base) * 64, 64 * base)
|
||||
else:
|
||||
return (64 * base, round(height / width * base) * 64)
|
||||
|
||||
async def post_(self, header: dict, post_api: str, json: dict):
|
||||
"""
|
||||
向服务器发送请求的核心函数,不要直接调用,请使用post函数
|
||||
:header: 请求头
|
||||
:post_api: 请求地址
|
||||
:json: 请求体
|
||||
"""
|
||||
# 请求交互
|
||||
async with aiohttp.ClientSession(headers=header) as session:
|
||||
# 向服务器发送请求
|
||||
async with session.post(post_api, json=json) as resp:
|
||||
if resp.status not in [200, 201]:
|
||||
logger.error(await resp.text())
|
||||
raise RuntimeError(f"与服务器沟通时发生{resp.status}错误")
|
||||
img = await self.fromresp(resp)
|
||||
logger.debug(f"获取到返回图片,正在处理")
|
||||
|
||||
# 将图片转化为jpg
|
||||
if config.novelai_save == 1:
|
||||
image_new = await png2jpg(img)
|
||||
else:
|
||||
image_new = base64.b64decode(img)
|
||||
self.result.append(image_new)
|
||||
return image_new
|
||||
|
||||
async def fromresp(self, resp):
|
||||
"""
|
||||
处理请求的返回内容,不要直接调用,请使用post函数
|
||||
"""
|
||||
img: str = await resp.text()
|
||||
return img.split("data:")[1]
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
运行核心函数,发送请求并处理
|
||||
"""
|
||||
pass
|
||||
|
||||
def keys(self):
|
||||
return (
|
||||
"seed",
|
||||
"scale",
|
||||
"strength",
|
||||
"noise",
|
||||
"sampler",
|
||||
"model",
|
||||
"steps",
|
||||
"width",
|
||||
"height",
|
||||
"img2img",
|
||||
)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
def format(self):
|
||||
dict_self = dict(self)
|
||||
list = []
|
||||
str = ""
|
||||
for i, v in dict_self.items():
|
||||
str += f"{i}={v}\n"
|
||||
list.append(str)
|
||||
list.append(f"tags={self.tags}\n")
|
||||
list.append(f"ntags={self.ntags}")
|
||||
return list
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"time={self.time}\nuser_id={self.user_id}\ngroup_id={self.group_id}\ncost={self.cost}\nbatch={self.batch}\n"
|
||||
+ "".join(self.format())
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__().replace("\n", ";")
|
|
@ -0,0 +1,34 @@
|
|||
from .base import AIDRAW_BASE
|
||||
from ..config import config
|
||||
class AIDRAW(AIDRAW_BASE):
|
||||
"""队列中的单个请求"""
|
||||
|
||||
async def post(self):
|
||||
header = {
|
||||
"content-type": "application/json",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36",
|
||||
}
|
||||
site=config.novelai_site or "127.0.0.1:6969"
|
||||
post_api="http://"+site + "/generate-stream"
|
||||
for i in range(self.batch):
|
||||
parameters = {
|
||||
"prompt":self.tags,
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"qualityToggle": False,
|
||||
"scale": self.scale,
|
||||
"sampler": self.sampler,
|
||||
"steps": self.steps,
|
||||
"seed": self.seed[i],
|
||||
"n_samples": 1,
|
||||
"ucPreset": 0,
|
||||
"uc": self.ntags,
|
||||
}
|
||||
if self.img2img:
|
||||
parameters.update({
|
||||
"image": self.image,
|
||||
"strength": self.strength,
|
||||
"noise": self.noise
|
||||
})
|
||||
await self.post_(header, post_api,parameters)
|
||||
return self.result
|
|
@ -0,0 +1,44 @@
|
|||
from ..config import config
|
||||
from .base import AIDRAW_BASE
|
||||
|
||||
class AIDRAW(AIDRAW_BASE):
|
||||
"""队列中的单个请求"""
|
||||
model: str = "nai-diffusion" if config.novelai_h else "safe-diffusion"
|
||||
|
||||
async def post(self):
|
||||
# 获取请求体
|
||||
header = {
|
||||
"authorization": "Bearer " + config.novelai_token,
|
||||
":authority": "https://api.novelai.net",
|
||||
":path": "/ai/generate-image",
|
||||
"content-type": "application/json",
|
||||
"referer": "https://novelai.net",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36",
|
||||
}
|
||||
post_api = "https://api.novelai.net/ai/generate-image"
|
||||
for i in range(self.batch):
|
||||
parameters = {
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"qualityToggle": False,
|
||||
"scale": self.scale,
|
||||
"sampler": self.sampler,
|
||||
"steps": self.steps,
|
||||
"seed": self.seed[i],
|
||||
"n_samples": 1,
|
||||
"ucPreset": 0,
|
||||
"uc": self.ntags,
|
||||
}
|
||||
if self.img2img:
|
||||
parameters.update({
|
||||
"image": self.image,
|
||||
"strength": self.strength,
|
||||
"noise": self.noise
|
||||
})
|
||||
json= {
|
||||
"input": self.tags,
|
||||
"model": self.model,
|
||||
"parameters": parameters
|
||||
}
|
||||
await self.post_(header, post_api,json)
|
||||
return self.result
|
|
@ -0,0 +1,43 @@
|
|||
from .base import AIDRAW_BASE
|
||||
from ..config import config
|
||||
|
||||
|
||||
class AIDRAW(AIDRAW_BASE):
|
||||
"""队列中的单个请求"""
|
||||
max_resolution: int = 32
|
||||
|
||||
async def fromresp(self, resp):
|
||||
img: dict = await resp.json()
|
||||
return img["images"][0]
|
||||
|
||||
async def post(self):
|
||||
site=config.novelai_site or "127.0.0.1:7860"
|
||||
header = {
|
||||
"content-type": "application/json",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36",
|
||||
}
|
||||
post_api = f"http://{site}/sdapi/v1/img2img" if self.img2img else f"http://{site}/sdapi/v1/txt2img"
|
||||
for i in range(self.batch):
|
||||
parameters = {
|
||||
"prompt": self.tags,
|
||||
"seed": self.seed[i],
|
||||
"steps": self.steps,
|
||||
"cfg_scale": self.scale,
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"sampler_name": self.sampler,
|
||||
"negative_prompt": self.ntags,
|
||||
"enable_hr": self.hires,
|
||||
"denoising_strength": 0.7,
|
||||
"hr_scale": 2,
|
||||
"hr_upscaler": "Latent"
|
||||
}
|
||||
print("向API发送以下参数:")
|
||||
print(parameters)
|
||||
if self.img2img:
|
||||
parameters.update({
|
||||
"init_images": ["data:image/jpeg;base64,"+self.image],
|
||||
"denoising_strength": self.strength,
|
||||
})
|
||||
await self.post_(header, post_api, parameters)
|
||||
return self.result
|
|
@ -0,0 +1,155 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import aiofiles
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
from pydantic import BaseSettings, validator
|
||||
from pydantic.fields import ModelField
|
||||
|
||||
jsonpath = Path("data/novelai/config.json").resolve()
|
||||
nickname = list(get_driver().config.nickname)[0] if len(
|
||||
get_driver().config.nickname) else "nonebot-plugin-novelai"
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
# 服务器设置
|
||||
novelai_token: str = "" # 官网的token
|
||||
# novelai: dict = {"novelai":""}# 你的服务器地址(包含端口),不包含http头,例:127.0.0.1:6969
|
||||
novelai_mode: str = "novelai"
|
||||
novelai_site: str = ""
|
||||
# 后台设置
|
||||
novelai_save: int = 1 # 是否保存图片至本地,0为不保存,1保存,2同时保存追踪信息
|
||||
novelai_paid: int = 0 # 0为禁用付费模式,1为点数制,2为不限制
|
||||
novelai_pure: bool = False # 是否启用简洁返回模式(只返回图片,不返回tag等数据)
|
||||
novelai_limit: bool = True # 是否开启限速
|
||||
novelai_daylimit: int = 0 # 每日次数限制,0为禁用
|
||||
novelai_h: bool = False # 是否允许H
|
||||
novelai_antireport: bool = True # 玄学选项。开启后,合并消息内发送者将会显示为调用指令的人而不是bot
|
||||
novelai_max: int = 3 # 每次能够生成的最大数量
|
||||
# 允许生成的图片最大分辨率,对应(值)^2.默认为1024(即1024*1024)。如果服务器比较寄,建议改成640(640*640)或者根据能够承受的情况修改。naifu和novelai会分别限制最大长宽为1024
|
||||
novelai_size: int = 1024
|
||||
# 可运行更改的设置
|
||||
novelai_tags: str = "" # 内置的tag
|
||||
novelai_ntags: str = "" # 内置的反tag
|
||||
novelai_cd: int = 60 # 默认的cd
|
||||
novelai_on: bool = True # 是否全局开启
|
||||
novelai_revoke: int = 0 # 是否自动撤回,该值不为0时,则为撤回时间
|
||||
# 翻译API设置
|
||||
bing_key: str = None # bing的翻译key
|
||||
deepl_key: str = None # deepL的翻译key
|
||||
|
||||
# 允许单群设置的设置
|
||||
def keys(cls):
|
||||
return ("novelai_cd", "novelai_tags", "novelai_on", "novelai_ntags", "novelai_revoke")
|
||||
|
||||
def __getitem__(cls, item):
|
||||
return getattr(cls, item)
|
||||
|
||||
@validator("novelai_cd", "novelai_max")
|
||||
def non_negative(cls, v: int, field: ModelField):
|
||||
if v < 1:
|
||||
return field.default
|
||||
return v
|
||||
|
||||
@validator("novelai_paid")
|
||||
def paid(cls, v: int, field: ModelField):
|
||||
if v < 0:
|
||||
return field.default
|
||||
elif v > 3:
|
||||
return field.default
|
||||
return v
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
||||
async def set_enable(cls, group_id, enable):
|
||||
# 设置分群启用
|
||||
await cls.__init_json()
|
||||
now = await cls.get_value(group_id, "on")
|
||||
logger.debug(now)
|
||||
if now:
|
||||
if enable:
|
||||
return f"aidraw已经处于启动状态"
|
||||
else:
|
||||
if await cls.set_value(group_id, "on", "false"):
|
||||
return f"aidraw已关闭"
|
||||
else:
|
||||
if enable:
|
||||
if await cls.set_value(group_id, "on", "true"):
|
||||
return f"aidraw开始运行"
|
||||
else:
|
||||
return f"aidraw已经处于关闭状态"
|
||||
|
||||
async def __init_json(cls):
|
||||
# 初始化设置文件
|
||||
if not jsonpath.exists():
|
||||
jsonpath.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with aiofiles.open(jsonpath, "w+") as f:
|
||||
await f.write("{}")
|
||||
|
||||
async def get_value(cls, group_id, arg: str):
|
||||
# 获取设置值
|
||||
group_id = str(group_id)
|
||||
arg_ = arg if arg.startswith("novelai_") else "novelai_" + arg
|
||||
if arg_ in cls.keys():
|
||||
await cls.__init_json()
|
||||
async with aiofiles.open(jsonpath, "r") as f:
|
||||
jsonraw = await f.read()
|
||||
configdict: dict = json.loads(jsonraw)
|
||||
return configdict.get(group_id, {}).get(arg_, dict(cls)[arg_])
|
||||
else:
|
||||
return None
|
||||
|
||||
async def get_groupconfig(cls, group_id):
|
||||
# 获取当群所有设置值
|
||||
group_id = str(group_id)
|
||||
await cls.__init_json()
|
||||
async with aiofiles.open(jsonpath, "r") as f:
|
||||
jsonraw = await f.read()
|
||||
configdict: dict = json.loads(jsonraw)
|
||||
baseconfig = {}
|
||||
for i in cls.keys():
|
||||
value = configdict.get(group_id, {}).get(
|
||||
i, dict(cls)[i])
|
||||
baseconfig[i] = value
|
||||
logger.debug(baseconfig)
|
||||
return baseconfig
|
||||
|
||||
async def set_value(cls, group_id, arg: str, value: str):
|
||||
"""设置当群设置值"""
|
||||
# 将值转化为bool和int
|
||||
if value.isdigit():
|
||||
value: int = int(value)
|
||||
elif value.lower() == "false":
|
||||
value = False
|
||||
elif value.lower() == "true":
|
||||
value = True
|
||||
group_id = str(group_id)
|
||||
arg_ = arg if arg.startswith("novelai_") else "novelai_" + arg
|
||||
# 判断是否合法
|
||||
if arg_ in cls.keys() and isinstance(value, type(dict(cls)[arg_])):
|
||||
await cls.__init_json()
|
||||
# 读取文件
|
||||
async with aiofiles.open(jsonpath, "r") as f:
|
||||
jsonraw = await f.read()
|
||||
configdict: dict = json.loads(jsonraw)
|
||||
# 设置值
|
||||
groupdict = configdict.get(group_id, {})
|
||||
if value == "default":
|
||||
groupdict[arg_] = False
|
||||
else:
|
||||
groupdict[arg_] = value
|
||||
configdict[group_id] = groupdict
|
||||
# 写入文件
|
||||
async with aiofiles.open(jsonpath, "w") as f:
|
||||
jsonnew = json.dumps(configdict)
|
||||
await f.write(jsonnew)
|
||||
return True
|
||||
else:
|
||||
logger.debug(f"不正确的赋值,{arg_},{value},{type(value)}")
|
||||
return False
|
||||
|
||||
|
||||
config = Config(**get_driver().config.dict())
|
||||
logger.info(f"加载config完成" + str(config))
|
|
@ -0,0 +1,78 @@
|
|||
from pathlib import Path
|
||||
import json
|
||||
import aiofiles
|
||||
from nonebot.adapters.onebot.v11 import Bot,GroupMessageEvent, Message, MessageSegment
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot import on_command, get_driver
|
||||
|
||||
jsonpath = Path("data/novelai/anlas.json").resolve()
|
||||
setanlas = on_command(".anlas")
|
||||
|
||||
@setanlas.handle()
|
||||
async def anlas_handle(bot:Bot,event: GroupMessageEvent, args: Message = CommandArg()):
|
||||
atlist = []
|
||||
user_id = str(event.user_id)
|
||||
for seg in event.original_message["at"]:
|
||||
atlist.append(seg.data["qq"])
|
||||
messageraw = args.extract_plain_text().strip()
|
||||
if not messageraw or messageraw == "help":
|
||||
await setanlas.finish(f"点数计算方法(四舍五入):分辨率*数量*强度/45875\n.anlas+数字+@某人 将自己的点数分给对方\n.anlas check 查看自己的点数")
|
||||
elif messageraw == "check":
|
||||
if await SUPERUSER(bot,event):
|
||||
await setanlas.finish(f"Master不需要点数哦")
|
||||
else:
|
||||
anlas = await anlas_check(user_id)
|
||||
await setanlas.finish(f"你的剩余点数为{anlas}")
|
||||
if atlist:
|
||||
at = atlist[0]
|
||||
if messageraw.isdigit():
|
||||
anlas_change = int(messageraw)
|
||||
if anlas_change > 1000:
|
||||
await setanlas.finish(f"一次能给予的点数不超过1000")
|
||||
if await SUPERUSER(bot,event):
|
||||
_, result = await anlas_set(at, anlas_change)
|
||||
message = f"分配完成:" + \
|
||||
MessageSegment.at(at)+f"的剩余点数为{result}"
|
||||
else:
|
||||
result, user_anlas = await anlas_set(user_id, -anlas_change)
|
||||
if result:
|
||||
_, at_anlas = await anlas_set(at, anlas_change)
|
||||
message = f"分配完成:\n"+MessageSegment.at(
|
||||
user_id)+f"的剩余点数为{user_anlas}\n"+MessageSegment.at(at)+f"的剩余点数为{at_anlas}"
|
||||
await setanlas.finish(message)
|
||||
else:
|
||||
await setanlas.finish(f"分配失败:点数不足,你的剩余点数为{user_anlas}")
|
||||
await setanlas.finish(message)
|
||||
else:
|
||||
await setanlas.finish(f"请以正整数形式输入点数")
|
||||
else:
|
||||
await setanlas.finish(f"请@你希望给予点数的人")
|
||||
|
||||
|
||||
async def anlas_check(user_id):
|
||||
if not jsonpath.exists():
|
||||
jsonpath.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with aiofiles.open(jsonpath, "w+")as f:
|
||||
await f.write("{}")
|
||||
async with aiofiles.open(jsonpath, "r") as f:
|
||||
jsonraw = await f.read()
|
||||
anlasdict: dict = json.loads(jsonraw)
|
||||
anlas = anlasdict.get(user_id, 0)
|
||||
return anlas
|
||||
|
||||
|
||||
async def anlas_set(user_id, change):
|
||||
oldanlas = await anlas_check(user_id)
|
||||
newanlas = oldanlas+change
|
||||
if newanlas < 0:
|
||||
return False, oldanlas
|
||||
anlasdict = {}
|
||||
async with aiofiles.open(jsonpath, "r") as f:
|
||||
jsonraw = await f.read()
|
||||
anlasdict: dict = json.loads(jsonraw)
|
||||
anlasdict[user_id] = newanlas
|
||||
async with aiofiles.open(jsonpath, "w+") as f:
|
||||
jsonnew = json.dumps(anlasdict)
|
||||
await f.write(jsonnew)
|
||||
return True, newanlas
|
|
@ -0,0 +1,20 @@
|
|||
import time
|
||||
from ..config import config
|
||||
|
||||
|
||||
class DayLimit():
|
||||
day: int = time.localtime(time.time()).tm_yday
|
||||
data: dict = {}
|
||||
|
||||
@classmethod
|
||||
def count(cls, user: str, num):
|
||||
day_ = time.localtime(time.time()).tm_yday
|
||||
if day_ != cls.day:
|
||||
cls.day = day_
|
||||
cls.data = {}
|
||||
count: int = cls.data.get(user, 0)+num
|
||||
if count > config.novelai_daylimit:
|
||||
return -1
|
||||
else:
|
||||
cls.data[user] = count
|
||||
return config.novelai_daylimit-count
|
|
@ -0,0 +1,41 @@
|
|||
import aiohttp
|
||||
import base64
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageSegment
|
||||
from nonebot.log import logger
|
||||
from .translation import translate
|
||||
|
||||
deepdanbooru = on_command(".gettag", aliases={"鉴赏", "查书"})
|
||||
|
||||
|
||||
@deepdanbooru.handle()
|
||||
async def deepdanbooru_handle(event: GroupMessageEvent):
|
||||
url = ""
|
||||
for seg in event.message['image']:
|
||||
url = seg.data["url"]
|
||||
if url:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
logger.info(f"正在获取图片")
|
||||
async with session.get(url) as resp:
|
||||
bytes = await resp.read()
|
||||
str_img = str(base64.b64encode(bytes), "utf-8")
|
||||
message = MessageSegment.at(event.user_id)
|
||||
start = "data:image/jpeg;base64,"
|
||||
str0 = start+str_img
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post('https://mayhug-rainchan-anime-image-label.hf.space/api/predict/', json={"data": [str0, 0.6,"ResNet101"]}) as resp:
|
||||
if resp.status != 200:
|
||||
await deepdanbooru.finish(f"识别失败,错误代码为{resp.status}")
|
||||
jsonresult = await resp.json()
|
||||
data = jsonresult['data'][0]
|
||||
logger.info(f"TAG查询完毕")
|
||||
tags = ""
|
||||
for label in data['confidences']:
|
||||
tags = tags+label["label"]+","
|
||||
tags_ch = await translate(tags.replace("_", " "), "zh")
|
||||
if tags_ch == tags.replace("_", " "):
|
||||
message = message+tags
|
||||
message = message+tags+f"\n机翻结果:"+tags_ch
|
||||
await deepdanbooru.finish(message)
|
||||
else:
|
||||
await deepdanbooru.finish(f"未找到图片")
|
|
@ -0,0 +1,113 @@
|
|||
import aiohttp
|
||||
from ..config import config
|
||||
from nonebot.log import logger
|
||||
|
||||
|
||||
async def translate(text: str, to: str):
|
||||
# en,jp,zh
|
||||
result = await translate_deepl(text, to) or await translate_bing(text, to) or await translate_google_proxy(text, to) or await translate_youdao(text, to)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
logger.error(f"未找到可用的翻译引擎!")
|
||||
return text
|
||||
|
||||
|
||||
async def translate_bing(text: str, to: str):
|
||||
"""
|
||||
en,jp,zh_Hans
|
||||
"""
|
||||
if to == "zh":
|
||||
to = "zh-Hans"
|
||||
key = config.bing_key
|
||||
if not key:
|
||||
return None
|
||||
header = {
|
||||
"Ocp-Apim-Subscription-Key": key,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
body = [{'text': text}]
|
||||
params = {
|
||||
"api-version": "3.0",
|
||||
"to": to,
|
||||
"profanityAction": "Deleted",
|
||||
}
|
||||
async with session.post('https://api.cognitive.microsofttranslator.com/translate', json=body, params=params, headers=header) as resp:
|
||||
if resp.status != 200:
|
||||
logger.error(f"Bing翻译接口调用失败,错误代码{resp.status},{await resp.text()}")
|
||||
return None
|
||||
jsonresult = await resp.json()
|
||||
result=jsonresult[0]["translations"][0]["text"]
|
||||
logger.debug(f"Bing翻译启动,获取到{text},翻译后{result}")
|
||||
return result
|
||||
|
||||
|
||||
async def translate_deepl(text: str, to: str):
|
||||
"""
|
||||
EN,JA,ZH
|
||||
"""
|
||||
to = to.upper()
|
||||
key = config.deepl_key
|
||||
if not key:
|
||||
return None
|
||||
async with aiohttp.ClientSession() as session:
|
||||
params = {
|
||||
"auth_key":key,
|
||||
"text": text,
|
||||
"target_lang": to,
|
||||
}
|
||||
async with session.get('https://api-free.deepl.com/v2/translate', params=params) as resp:
|
||||
if resp.status != 200:
|
||||
logger.error(f"DeepL翻译接口调用失败,错误代码{resp.status},{await resp.text()}")
|
||||
return None
|
||||
jsonresult = await resp.json()
|
||||
result=jsonresult["translations"][0]["text"]
|
||||
logger.debug(f"DeepL翻译启动,获取到{text},翻译后{result}")
|
||||
return result
|
||||
|
||||
|
||||
async def translate_youdao(input: str, type: str):
|
||||
"""
|
||||
默认auto
|
||||
ZH_CH2EN 中译英
|
||||
EN2ZH_CN 英译汉
|
||||
"""
|
||||
if type == "zh":
|
||||
type = "EN2ZH_CN"
|
||||
elif type == "en":
|
||||
type = "ZH_CH2EN"
|
||||
async with aiohttp.ClientSession() as session:
|
||||
data = {
|
||||
'doctype': 'json',
|
||||
'type': type,
|
||||
'i': input
|
||||
}
|
||||
async with session.post("http://fanyi.youdao.com/translate", data=data) as resp:
|
||||
if resp.status != 200:
|
||||
logger.error(f"有道翻译接口调用失败,错误代码{resp.status},{await resp.text()}")
|
||||
return None
|
||||
result = await resp.json()
|
||||
result=result["translateResult"][0][0]["tgt"]
|
||||
logger.debug(f"有道翻译启动,获取到{input},翻译后{result}")
|
||||
return result
|
||||
|
||||
|
||||
async def translate_google_proxy(input: str, to: str):
|
||||
"""
|
||||
en,jp,zh 需要来源语言
|
||||
"""
|
||||
if to == "zh":
|
||||
from_ = "en"
|
||||
else:
|
||||
from_="zh"
|
||||
async with aiohttp.ClientSession()as session:
|
||||
data = {"data": [input, from_, to]}
|
||||
async with session.post("https://hf.space/embed/mikeee/gradio-gtr/+/api/predict", json=data)as resp:
|
||||
if resp.status != 200:
|
||||
logger.error(f"谷歌代理翻译接口调用失败,错误代码{resp.status},{await resp.text()}")
|
||||
return None
|
||||
result = await resp.json()
|
||||
result=result["data"][0]
|
||||
logger.debug(f"谷歌代理翻译启动,获取到{input},翻译后{result}")
|
||||
return result
|
|
@ -0,0 +1,19 @@
|
|||
from collections import deque
|
||||
|
||||
|
||||
class FIFO():
|
||||
gennerating: dict={}
|
||||
queue: deque = deque([])
|
||||
|
||||
@classmethod
|
||||
def len(cls):
|
||||
return len(cls.queue)+1 if cls.gennerating else len(cls.queue)
|
||||
|
||||
@classmethod
|
||||
async def add(cls, aidraw):
|
||||
cls.queue.append(aidraw)
|
||||
await cls.gennerate()
|
||||
|
||||
@classmethod
|
||||
async def gennerate(cls):
|
||||
pass
|
|
@ -0,0 +1,42 @@
|
|||
from nonebot.adapters.onebot.v11 import GROUP_ADMIN, GROUP_OWNER
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, Bot
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.params import RegexGroup
|
||||
from nonebot import on_regex
|
||||
from nonebot.log import logger
|
||||
from .config import config
|
||||
on = on_regex(f"(?:^\.aidraw|^绘画|^aidraw)[ ]*(on$|off$|开启$|关闭$)",
|
||||
priority=4, block=True)
|
||||
set = on_regex(
|
||||
"(?:^\.aidraw set|^绘画设置|^aidraw set)[ ]*([a-z]*)[ ]*(.*)", priority=4, block=True)
|
||||
|
||||
|
||||
@set.handle()
|
||||
async def set_(bot: Bot, event: GroupMessageEvent, args= RegexGroup()):
|
||||
if await GROUP_ADMIN(bot, event) or await GROUP_OWNER(bot, event) or await SUPERUSER(bot, event):
|
||||
if args[0] and args[1]:
|
||||
key, value = args
|
||||
await set.finish(f"设置群聊{key}为{value}完成" if await config.set_value(event.group_id, key,
|
||||
value) else f"不正确的赋值")
|
||||
else:
|
||||
group_config = await config.get_groupconfig(event.group_id)
|
||||
message = "当前群的设置为\n"
|
||||
for i, v in group_config.items():
|
||||
message += f"{i}:{v}\n"
|
||||
await set.finish(message)
|
||||
else:
|
||||
await set.send(f"权限不足!")
|
||||
|
||||
|
||||
@on.handle()
|
||||
async def on_(bot: Bot, event: GroupMessageEvent, args=RegexGroup()):
|
||||
if await GROUP_ADMIN(bot, event) or await GROUP_OWNER(bot, event) or await SUPERUSER(bot, event):
|
||||
if args[0] in ["on", "开启"]:
|
||||
set = True
|
||||
else:
|
||||
set = False
|
||||
result = await config.set_enable(event.group_id, set)
|
||||
logger.info(result)
|
||||
await on.finish(result)
|
||||
else:
|
||||
await on.send(f"权限不足!")
|
|
@ -0,0 +1,54 @@
|
|||
from ..config import config, nickname
|
||||
from ..utils.save import save_img
|
||||
from io import BytesIO
|
||||
import base64
|
||||
import aiohttp
|
||||
import asyncio
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.log import logger
|
||||
|
||||
|
||||
async def check_safe_method(fifo, img_bytes, message):
|
||||
if config.novelai_h:
|
||||
for i in img_bytes:
|
||||
await save_img(fifo, i)
|
||||
message += MessageSegment.image(i)
|
||||
else:
|
||||
nsfw_count = 0
|
||||
for i in img_bytes:
|
||||
try:
|
||||
label = await check_safe(i)
|
||||
except RuntimeError as e:
|
||||
logger.error(f"NSFWAPI调用失败,错误代码为{e.args}")
|
||||
label = "unknown"
|
||||
if label != "explicit":
|
||||
message += MessageSegment.image(i)
|
||||
else:
|
||||
nsfw_count += 1
|
||||
await save_img(fifo, i, label)
|
||||
if nsfw_count > 0:
|
||||
message += f"\n有{nsfw_count}张图片太涩了,{nickname}已经帮你吃掉了哦"
|
||||
return message
|
||||
|
||||
|
||||
async def check_safe(img_bytes: BytesIO):
|
||||
# 检查图片是否安全
|
||||
start = "data:image/jpeg;base64,"
|
||||
image = img_bytes.getvalue()
|
||||
image = str(base64.b64encode(image), "utf-8")
|
||||
str0 = start + image
|
||||
# 重试三次
|
||||
for i in range(3):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# 调用API
|
||||
async with session.post('https://hf.space/embed/mayhug/rainchan-image-porn-detection/api/predict/',
|
||||
json={"data": [str0]}) as resp:
|
||||
if resp.status == 200:
|
||||
jsonresult = await resp.json()
|
||||
break
|
||||
else:
|
||||
await asyncio.sleep(2)
|
||||
error = resp.status
|
||||
else:
|
||||
raise RuntimeError(error)
|
||||
return jsonresult["data"][0]["label"]
|
|
@ -0,0 +1,48 @@
|
|||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import re
|
||||
import aiohttp
|
||||
import base64
|
||||
|
||||
|
||||
async def check_last_version(package: str):
|
||||
# 检查包的最新版本
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get("https://pypi.org/simple/"+package) as resp:
|
||||
text = await resp.text()
|
||||
pattern = re.compile("-(\d.*?).tar.gz")
|
||||
pypiversion = re.findall(pattern, text)[-1]
|
||||
return pypiversion
|
||||
|
||||
|
||||
async def compare_version(old: str, new: str):
|
||||
# 比较两个版本哪个最新
|
||||
oldlist = old.split(".")
|
||||
newlist = new.split(".")
|
||||
for i in range(len(oldlist)):
|
||||
if int(newlist[i]) > int(oldlist[i]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def sendtosuperuser(message):
|
||||
# 将消息发送给superuser
|
||||
from nonebot import get_bot, get_driver
|
||||
import asyncio
|
||||
superusers = get_driver().config.superusers
|
||||
bot = get_bot()
|
||||
for superuser in superusers:
|
||||
await bot.call_api('send_msg', **{
|
||||
'message': message,
|
||||
'user_id': superuser,
|
||||
})
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
async def png2jpg(raw: bytes):
|
||||
raw:BytesIO = BytesIO(base64.b64decode(raw))
|
||||
img_PIL = Image.open(raw).convert("RGB")
|
||||
image_new = BytesIO()
|
||||
img_PIL.save(image_new, format="JPEG", quality=95)
|
||||
image_new=image_new.getvalue()
|
||||
return image_new
|
|
@ -0,0 +1,20 @@
|
|||
# 基础优化tag
|
||||
basetag = "masterpiece, best quality,"
|
||||
|
||||
# 基础排除tag
|
||||
lowQuality = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, pubic hair,long neck,blurry"
|
||||
|
||||
# 屏蔽词
|
||||
htags = "nsfw|nude|naked|nipple|blood|censored|vagina|gag|gokkun|hairjob|tentacle|oral|fellatio|areolae|lactation|paizuri|piercing|sex|footjob|masturbation|hips|penis|testicles|ejaculation|cum|tamakeri|pussy|pubic|clitoris|mons|cameltoe|grinding|crotch|cervix|cunnilingus|insertion|penetration|fisting|fingering|peeing|ass|buttjob|spanked|anus|anal|anilingus|enema|x-ray|wakamezake|humiliation|tally|futa|incest|twincest|pegging|femdom|ganguro|bestiality|gangbang|3P|tribadism|molestation|voyeurism|exhibitionism|rape|spitroast|cock|69|doggystyle|missionary|virgin|shibari|bondage|bdsm|rope|pillory|stocks|bound|hogtie|frogtie|suspension|anal|dildo|vibrator|hitachi|nyotaimori|vore|amputee|transformation|bloody"
|
||||
|
||||
shapemap = {
|
||||
"square": [640, 640],
|
||||
"s": [640, 640],
|
||||
"方": [640, 640],
|
||||
"portrait": [512, 768],
|
||||
"p": [512, 768],
|
||||
"高": [512, 768],
|
||||
"landscape": [768, 512],
|
||||
"l": [768, 512],
|
||||
"宽": [768, 512]
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import re
|
||||
from ..extension.translation import translate
|
||||
|
||||
escape_table = {
|
||||
'[': '[',
|
||||
']': ']'
|
||||
}
|
||||
|
||||
async def prepocess_tags(tags: list[str]):
|
||||
tags: str = "".join([i+" " for i in tags if isinstance(i, str)])
|
||||
# 去除CQ码
|
||||
tags = re.sub("\[CQ[^\s]*?]", "", tags)
|
||||
# 检测中文
|
||||
taglist = tags.split(",")
|
||||
tagzh = ""
|
||||
tags_ = ""
|
||||
for i in taglist:
|
||||
if re.search('[\u4e00-\u9fa5]', tags):
|
||||
tagzh += f"{i},"
|
||||
else:
|
||||
tags_ += f"{i},"
|
||||
if tagzh:
|
||||
tags_en = await translate(tagzh, "en")
|
||||
if tags_en == tagzh:
|
||||
return ""
|
||||
else:
|
||||
tags_ += tags_en
|
||||
return await fix_char_escape(tags_)
|
||||
|
||||
|
||||
async def combine_multi_args(args: list[str]):
|
||||
return ' '.join(args)
|
||||
|
||||
|
||||
async def fix_char_escape(tags: str):
|
||||
for escape, raw in escape_table.items():
|
||||
tags = tags.replace(escape, raw)
|
||||
return tags
|
|
@ -0,0 +1,17 @@
|
|||
from ..config import config
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import aiofiles
|
||||
path = Path("data/novelai/output").resolve()
|
||||
async def save_img(fifo, img_bytes: bytes, extra: str = "unknown"):
|
||||
# 存储图片
|
||||
if config.novelai_save:
|
||||
path_ = path / extra
|
||||
path_.mkdir(parents=True, exist_ok=True)
|
||||
hash = hashlib.md5(img_bytes).hexdigest()
|
||||
file = (path_ / hash).resolve()
|
||||
async with aiofiles.open(str(file) + ".jpg", "wb") as f:
|
||||
await f.write(img_bytes)
|
||||
if config.novelai_save==2:
|
||||
async with aiofiles.open(str(file) + ".txt", "w") as f:
|
||||
await f.write(repr(fifo))
|
|
@ -0,0 +1,50 @@
|
|||
import time
|
||||
from importlib.metadata import version
|
||||
|
||||
from nonebot.log import logger
|
||||
|
||||
from .utils import check_last_version, sendtosuperuser, compare_version
|
||||
class Version():
|
||||
version: str # 当前版本
|
||||
lastcheck: float = 0 # 上次检查时间
|
||||
ispushed: bool = True # 是否已经推送
|
||||
latest: str = "0.0.0" # 最新版本
|
||||
package = "nonebot-plugin-novelai"
|
||||
url = "https://sena-nana.github.io/MutsukiDocs/update/novelai/"
|
||||
|
||||
def __init__(self):
|
||||
# 初始化当前版本
|
||||
try:
|
||||
self.version = version(self.package)
|
||||
except:
|
||||
self.version = "0.5.7"
|
||||
|
||||
async def check_update(self):
|
||||
"""检查更新,并推送"""
|
||||
# 每日检查
|
||||
if time.time() - self.lastcheck > 80000:
|
||||
update = await check_last_version(self.package)
|
||||
# 判断是否重复检查
|
||||
if await compare_version(self.latest, update):
|
||||
self.latest = update
|
||||
# 判断是否是新版本
|
||||
if await compare_version(self.version, self.latest):
|
||||
logger.info(self.push_txt())
|
||||
self.ispushed = False
|
||||
else:
|
||||
logger.info(f"novelai插件检查版本完成,当前版本{self.version},最新版本{self.latest}")
|
||||
else:
|
||||
logger.info(f"novelai插件检查版本完成,当前版本{self.version},最新版本{self.latest}")
|
||||
self.lastcheck = time.time()
|
||||
# 如果没有推送,则启动推送流程
|
||||
if not self.ispushed:
|
||||
await sendtosuperuser(self.push_txt())
|
||||
self.ispushed = True
|
||||
|
||||
def push_txt(self):
|
||||
# 获取推送文本
|
||||
logger.debug(self.__dict__)
|
||||
return f"novelai插件检测到新版本{self.latest},当前版本{self.version},请使用pip install --upgrade {self.package}命令升级,更新日志:{self.url}"
|
||||
|
||||
|
||||
version = Version()
|
Loading…
Reference in New Issue