diff --git a/README.md b/README.md index 5a277e0..b2d2dd8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 说明书:https://sena-nana.github.io/MutsukiDocs/ 环境需求: -- Python>=3.7 +- Python>=3.8 - nonebot2>=b4 ## 依赖 aiohttp,aiofiles @@ -17,11 +17,11 @@ aiohttp,aiofiles 1. NOVELAI_TAG="str" 所有生成都会事先加上这些tag,用来塞私货或者精简指令 2. NOVELAI_CD=int 单个用户的cd,默认为60s 3. NOVELAI_LIMIT=bool 是否启用并行限制,启用的话,bot会将请求加入队列,在服务器返回之前的结果后再申请。可以防止请求过快,在不知道官方会不会封号的情况下有心理安慰作用。默认开启 -4. NOVELAI_API_DOMAIN="str" 白嫖服务器时修改,不设置默认官方服务器(未完成) -5. NOVELAI_SITE_DOMAIN="str" 白嫖服务器时修改,不设置默认官方服务器(未完成) -6. NOVELAI_SAVE_PIC=bool 是否自动保存到本地,默认开启 +5. NOVELAI_SITE="str" 自定义后端时设置 +6. NOVELAI_SAVE=int 是否自动保存到本地,默认开启(1) 7. NOVELAI_MODE="str" 设置插件运行模式,默认"novelai",详细查看说明书(还没写) -8. NOVELAI_PAID=int 是否启用已付费模式,默认为0(禁用),1为点数模式,详细查看说明书,2为无限制模式 +8. NOVELAI_PAID=int 是否启用已付费模式,默认为0(禁用),1为点数模式,2为严格点数模式,3为无限制模式 +9. NOVELAI_DAYLIMIT=int 是否启用每日次数限制,默认为0(禁用),值为次数 9. NOVELAI_BAN=list[int] 设置在哪些群禁用,默认为空,运行时可通过指令修改 10. NOVELAI_H=bool 是否启用r18模式,默认关闭(开启后被风控或者封号不要发issue) 10. NOVELAI_ONCEMAX=int 单次允许生成的最大数量 @@ -59,8 +59,8 @@ novelai模式需要token才能运行,所以你需要首先购买novelai的25 ## FEATURE - NAIFU - - [ ] 支持文本生图 - - [ ] 支持以图生图 + - [x] 支持文本生图 + - [x] 支持以图生图 - WEBUI - [ ] 支持文本生图 - [ ] 支持以图生图 @@ -73,7 +73,8 @@ novelai模式需要token才能运行,所以你需要首先购买novelai的25 - 速度限制 - [x] 支持内置CD和并行限制 - [x] 付费点数制 - - [ ] 每日上限制 + - [x] 严格点数制 + - [x] 每日上限制 - 娱乐功能 - [x] 支持查询图片词条 - [ ] 随机少女 @@ -89,7 +90,6 @@ novelai模式需要token才能运行,所以你需要首先购买novelai的25 - [x] 支持机翻词条为英文 - [x] 生成图片自动保存至data/novelai文件夹 - [x] 支持开关禁止nsfw - - [ ] 支持私聊 - [x] 更新提醒 - [ ] 支持多台后端负载均衡 - [ ] 说明书 \ No newline at end of file diff --git a/nonebot-plugin-novelai/aidraw.py b/nonebot-plugin-novelai/aidraw.py index 4d3d494..ffda2bf 100644 --- a/nonebot-plugin-novelai/aidraw.py +++ b/nonebot-plugin-novelai/aidraw.py @@ -1,37 +1,40 @@ import time -import random from collections import deque import aiohttp +from aiohttp.client_exceptions import ClientConnectorError 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, nickname from .utils.data import lowQuality, basetag -from .novelai import post, FIFO +from .mode import post, FIFO 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 from .version import version from .outofdate.explicit_api import check_safe_method - +from .utils import sendtosuperuser cd = {} gennerating = False limit_list = deque([]) aidraw_parser = ArgumentParser() -aidraw_parser.add_argument("tags", default=" ", nargs="*", help="标签") -aidraw_parser.add_argument("-p", "--shape", "-形状", - default="p", help="画布形状", dest="shape") +aidraw_parser.add_argument("tags", nargs="*", help="标签") +aidraw_parser.add_argument("-p", "--shape", "-形状", help="画布形状", dest="shape") +aidraw_parser.add_argument("-w", "--width", "-宽", help="画布宽", dest="width") +aidraw_parser.add_argument("-h", "--height", "-高", help="画布高", dest="height") aidraw_parser.add_argument("-c", "--scale", "-规模", type=float, help="规模", dest="scale") aidraw_parser.add_argument( - "-s", "--seed", "-种子", default=random.randint(0,4294967295), type=int, help="种子", dest="seed") + "-s", "--seed", "-种子", type=int, help="种子", dest="seed") aidraw_parser.add_argument("-u", "--count", "-数量", type=int, default=1, help="生成数量", dest="count") aidraw_parser.add_argument("-t", "--steps", "-步数", @@ -47,7 +50,7 @@ aidraw = on_shell_command( ".aidraw", - aliases={"绘画", "咏唱", "召唤"}, + aliases={"绘画", "咏唱", "召唤", "aidraw"}, parser=aidraw_parser, priority=5 ) @@ -62,10 +65,18 @@ async def aidraw_get(bot: Bot, group_id = str(event.group_id) # 判断是否禁用,若没禁用,进入处理流程 if await config.get_value(group_id, "on"): + message = "" # 判断最大生成数量 - if args.count>config.novelai_max: - await aidraw.send(f"批量生成数量过多,自动修改为{config.novelai_max}") - args.count=config.novelai_max + if args.count > config.novelai_max: + message = message+f",批量生成数量过多,自动修改为{config.novelai_max}" + args.count = config.novelai_max + # 判断次数限制 + if config.novelai_daylimit and not await SUPERUSER(bot,event): + left = DayLimit.count(user_id, args.count) + if left == -1: + aidraw.finish(f"今天你的次数不够了哦") + else: + message = message + f",今天你还能够生成{left}张" # 判断cd nowtime = time.time() deltatime = nowtime - cd.get(user_id, 0) @@ -76,7 +87,7 @@ async def aidraw_get(bot: Bot, cd[user_id] = nowtime # 初始化参数 - fifo = FIFO(user_id=user_id, group_id=group_id, args=args) + fifo = FIFO(user_id=user_id, group_id=group_id, **vars(args)) error = await prepocess_tags(fifo.tags) or await prepocess_tags(fifo.ntags) if error: await aidraw.finish(error) @@ -98,6 +109,7 @@ async def aidraw_get(bot: Bot, 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) @@ -106,18 +118,18 @@ async def aidraw_get(bot: Bot, anlascost = fifo.cost hasanlas = await anlas_check(fifo.user_id) if hasanlas >= anlascost: - await wait_fifo(fifo, anlascost, hasanlas - anlascost) + await wait_fifo(fifo, anlascost, hasanlas - anlascost, message=message) else: await aidraw.finish(f"你的点数不足,你的剩余点数为{hasanlas}") else: - await wait_fifo(fifo) + await wait_fifo(fifo, message=message) -async def wait_fifo(fifo, anlascost=None, anlas=None): +async def wait_fifo(fifo, anlascost=None, anlas=None, message=""): # 创建队列 list_len = get_wait_num() - has_wait = f"排队中,你的前面还有{list_len}人" - no_wait = "请稍等,图片生成中" + has_wait = f"排队中,你的前面还有{list_len}人"+message + no_wait = "请稍等,图片生成中"+message if anlas: has_wait += f"\n本次生成消耗点数{anlascost},你的剩余点数为{anlas}" no_wait += f"\n本次生成消耗点数{anlascost},你的剩余点数为{anlas}" @@ -153,7 +165,7 @@ async def generate(fifo: FIFO): logger.exception("生成失败") message = f"生成失败," for i in e.args: - message += i + message += str(i) await bot.send_group_msg( message=message, group_id=fifo.group_id @@ -208,17 +220,19 @@ async def generate(fifo: FIFO): async def _run_gennerate(fifo: FIFO): # 处理单个请求 - img_bytes = await post(fifo) - if isinstance(img_bytes, str): - raise RuntimeError(img_bytes) - message = MessageSegment.text(fifo.format()) - + try: + img_bytes = await post(fifo) + except ClientConnectorError: + await sendtosuperuser(f"远程服务器拒绝连接,请检查配置是否正确,服务器是否已经启动") + raise RuntimeError(f"远程服务器拒绝连接,请检查配置是否正确,服务器是否已经启动") # 若启用ai检定,取消注释下行代码,并将构造消息体部分注释 # message = await check_safe_method(fifo, img_bytes, message) # 构造消息体并保存图片 for i in img_bytes: 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) diff --git a/nonebot-plugin-novelai/base/fifo.py b/nonebot-plugin-novelai/base/fifo.py new file mode 100644 index 0000000..18eb0eb --- /dev/null +++ b/nonebot-plugin-novelai/base/fifo.py @@ -0,0 +1,122 @@ +import base64 +from io import BytesIO +import time +from PIL import Image +from nonebot import get_driver +from ..utils.data import shapemap +from ..config import config +import random + + +class FIFO_BASE(): + model: str = "" + sampler: str = "" + + def __init__(self, + user_id: str, + group_id: str, + tags: list[str] = [], + seed: int = None, + scale: int = None, + strength: float = None, + steps: int = None, + count: int = None, + noise: float = None, + ntags: list[str] = [], + shape: str = "p", + width: int = None, + height: int = None, + **kwargs): + self.time = time.strftime("%Y-%m-%d %H:%M:%S") + self.user_id: str = user_id + self.tags: str = "".join([i+" " for i in 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.count: int = count or 1 + self.steps: int = steps or 28 + self.noise: float = noise or 0.2 + self.ntags: str = "".join([i+" " for i in ntags]) + self.img2img: bool = False + self.image: str = None + if width and height: + self.shape_set(width,height) + else: + self.width, self.height = shapemap.get(shape or "p") + # 数值合法检查 + if self.steps <= 0 or self.steps > 50: + self.steps = 28 + # 多图时随机填充剩余seed + for i in range(self.count-1): + self.seed.append(random.randint(0, 4294967295)) + # 计算cost + self.update_cost() + + def update_cost(self): + if config.novelai_paid == 1: + anlas = 0 + if (self.width * self.height > 409600) or self.image or self.count > 1: + anlas += round(self.width * self.height * + self.strength * self.count * self.steps / 2293750) + 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.count * self.steps / 2293750) + 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): + # 根据图片重写长宽 + tmpfile = BytesIO(image) + image = Image.open(tmpfile) + width, height = image.size + self.shape_set(width, height) + self.image = str(base64.b64encode(self.image), "utf-8") + self.steps = 50 + self.img2img = True + self.update_cost() + + def shape_set(self, width, height): + base = round(min(width,height)/64) + if base>16: + base=16 + if width >= height: + self.width = round(width / height * base) * 64 + self.height = 64*base + else: + self.height = round(height / width * base) * 64 + self.width = 64*base + + def body(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={dict_self['tags']}\n") + list.append(f"ntags={dict_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}\ncount={self.count}\n"+"".join(self.format()) + + def __str__(self): + return self.__repr__().replace("\n", ";") diff --git a/nonebot-plugin-novelai/base/post.py b/nonebot-plugin-novelai/base/post.py new file mode 100644 index 0000000..07eaa1c --- /dev/null +++ b/nonebot-plugin-novelai/base/post.py @@ -0,0 +1,26 @@ +import base64 +from io import BytesIO + +import aiohttp +from nonebot.log import logger +from .fifo import FIFO_BASE +from ..utils import png2jpg + +async def post_base(fifo: FIFO_BASE, header, post_api): + # 请求交互 + img_bytes = [] + async with aiohttp.ClientSession(headers=header) as session: + for i in range(fifo.count): + # 向服务器发送请求 + async with session.post(post_api, json=fifo.body(i)) as resp: + if resp.status not in [200, 201]: + raise RuntimeError("与服务器沟通时发生{resp.status}错误") + img = await resp.text() + img = img.split("data:")[1] + logger.debug(f"获取到返回图片,正在处理") + + # 将图片转化为jpg(BytesIO) + image = BytesIO(base64.b64decode(img)) + image_new = await png2jpg(image) + img_bytes.append(image_new) + return img_bytes \ No newline at end of file diff --git a/nonebot-plugin-novelai/config.py b/nonebot-plugin-novelai/config.py index aed10b9..822d935 100644 --- a/nonebot-plugin-novelai/config.py +++ b/nonebot-plugin-novelai/config.py @@ -13,28 +13,31 @@ class Config(BaseSettings): + # 服务器设置 novelai_token: str = "" # 官网的token - novelai_tags: str = "" # 内置的tag - novelai_ntags: str = "" # 内置的反tag - novelai_cd: int = 60 # 默认的cd - novelai_pure: bool = False # 是否启用简洁返回模式(只返回图片,不返回tag等数据) - novelai_limit: bool = True # 是否开启限速 - novelai_save_pic: bool = True # 是否保存图片至本地 - novelai_save_detail: bool = False # 是否保存图片信息至本地 - novelai_api_domain: str = "https://api.novelai.net/" - novelai_site_domain: str = "https://novelai.net/" novelai_mode: str = "novelai" + novelai_site: str = "" # 你的服务器地址(包含端口),不包含http头,例:127.0.0.1:0000 + # 后台设置 + novelai_save: int = 1 # 是否保存图片至本地,0为不保存,1保存,2同时保存追踪信息 novelai_paid: int = 0 # 0为禁用付费模式,1为点数制,2为不限制 - novelai_on: bool = True # 是否全局开启 + novelai_pure: bool = False # 是否启用简洁返回模式(只返回图片,不返回tag等数据) + novelai_limit: bool = True # 是否开启限速 + novelai_daylimit: int = 0 # 每日次数限制,0为禁用 novelai_h: bool = False # 是否允许H novelai_max: int = 3 # 每次能够生成的最大数量 + # 可运行更改的设置 + 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_pure", "novelai_revoke") + return ("novelai_cd", "novelai_tags", "novelai_on", "novelai_ntags", "novelai_revoke") def __getitem__(cls, item): return getattr(cls, item) @@ -83,20 +86,20 @@ async def __init_json(cls): async def get_value(cls, group_id, arg: str): # 获取设置值 - group_id=str(group_id) + 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_]) + 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) + group_id = str(group_id) await cls.__init_json() async with aiofiles.open(jsonpath, "r") as f: jsonraw = await f.read() @@ -104,7 +107,7 @@ async def get_groupconfig(cls, group_id): baseconfig = {} for i in cls.keys(): value = configdict.get(group_id, {}).get( - i,dict(cls)[i]) + i, dict(cls)[i]) baseconfig[i] = value logger.debug(baseconfig) return baseconfig diff --git a/nonebot-plugin-novelai/extension/anlas.py b/nonebot-plugin-novelai/extension/anlas.py index 4f36c91..4230415 100644 --- a/nonebot-plugin-novelai/extension/anlas.py +++ b/nonebot-plugin-novelai/extension/anlas.py @@ -1,29 +1,27 @@ from pathlib import Path import json import aiofiles -from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageSegment +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() -superusers = get_driver().config.superusers setanlas = on_command(".anlas") declare = f"\n该功能用于限制使用频率,请勿用于金钱交易。任何金钱交易行为与插件作者无关,谨防诈骗" @setanlas.handle() -async def anlas_handle(event: GroupMessageEvent, args: Message = CommandArg()): +async def anlas_handle(bot:Bot,event: GroupMessageEvent, args: Message = CommandArg()): atlist = [] - user_id=str(event.user_id) + 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: - await setanlas.finish(f"点数计算方法(四舍五入):分辨率*数量*强度/45875\n.anlas+数字+@某人 将自己的点数分给对方\n.anlas check 查看自己的点数"+declare) - if messageraw == "help": + if not messageraw or messageraw == "help": await setanlas.finish(f"点数计算方法(四舍五入):分辨率*数量*强度/45875\n.anlas+数字+@某人 将自己的点数分给对方\n.anlas check 查看自己的点数"+declare) elif messageraw == "check": - if str(user_id) in superusers: + if await SUPERUSER(bot,event): await setanlas.finish(f"Master不需要点数哦") else: anlas = await anlas_check(user_id) @@ -34,7 +32,7 @@ async def anlas_handle(event: GroupMessageEvent, args: Message = CommandArg()): anlas_change = int(messageraw) if anlas_change > 1000: await setanlas.finish(f"一次能给予的点数不超过1000") - if str(user_id) in superusers: + if await SUPERUSER(bot,event): _, result = await anlas_set(at, anlas_change) message = f"分配完成:" + \ MessageSegment.at(at)+f"的剩余点数为{result}"+declare @@ -56,10 +54,10 @@ async def anlas_handle(event: GroupMessageEvent, args: Message = CommandArg()): async def anlas_check(user_id): if not jsonpath.exists(): - jsonpath.parent.mkdir(parents=True,exist_ok=True) + 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: + async with aiofiles.open(jsonpath, "r") as f: jsonraw = await f.read() anlasdict: dict = json.loads(jsonraw) anlas = anlasdict.get(user_id, 0) @@ -67,17 +65,16 @@ async def anlas_check(user_id): async def anlas_set(user_id, change): - oldanlas=await anlas_check(user_id) - newanlas=oldanlas+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 = {} + async with aiofiles.open(jsonpath, "r") as f: + jsonraw = await f.read() anlasdict: dict = json.loads(jsonraw) - anlasdict[user_id]=newanlas + anlasdict[user_id] = newanlas async with aiofiles.open(jsonpath, "w+") as f: jsonnew = json.dumps(anlasdict) await f.write(jsonnew) return True, newanlas - diff --git a/nonebot-plugin-novelai/extension/daylimit.py b/nonebot-plugin-novelai/extension/daylimit.py new file mode 100644 index 0000000..fdcfa3b --- /dev/null +++ b/nonebot-plugin-novelai/extension/daylimit.py @@ -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 diff --git a/nonebot-plugin-novelai/extension/ramdomgirl.py b/nonebot-plugin-novelai/extension/ramdomgirl.py new file mode 100644 index 0000000..e69de29 diff --git a/nonebot-plugin-novelai/manage.py b/nonebot-plugin-novelai/manage.py index 40c929d..b5e5c9c 100644 --- a/nonebot-plugin-novelai/manage.py +++ b/nonebot-plugin-novelai/manage.py @@ -5,10 +5,10 @@ from nonebot import on_regex from nonebot.log import logger from .config import config -on = on_regex(f"[(?:\.aidraw)|(?:绘画)][ ]*(on|off|开启|关闭)", +on = on_regex(f"(?:\.aidraw|绘画|aidraw)[ ]*(on|off|开启|关闭)", priority=4, block=True) set = on_regex( - "(?:(?:.aidraw set)|(?:绘画设置))[ ]*([a-z]*)[ ]*(.*)", priority=4, block=True) + "(?:\.aidraw set|绘画设置|aidraw set)[ ]*([a-z]*)[ ]*(.*)", priority=4, block=True) @set.handle() diff --git a/nonebot-plugin-novelai/mode copy.py b/nonebot-plugin-novelai/mode copy.py new file mode 100644 index 0000000..d1029e4 --- /dev/null +++ b/nonebot-plugin-novelai/mode copy.py @@ -0,0 +1,10 @@ +from .config import config + +if config.novelai_mode=="novelai": + from .novelai.post import post,FIFO +elif config.novelai_mode=="naifu": + from .naifu.post import post,FIFO +elif config.novelai_mode=="sd": + pass +else: + raise RuntimeError(f"错误的mode设置,支持的字符串为'novelai','naifu','sd'") \ No newline at end of file diff --git a/nonebot-plugin-novelai/mode.py b/nonebot-plugin-novelai/mode.py new file mode 100644 index 0000000..798d1af --- /dev/null +++ b/nonebot-plugin-novelai/mode.py @@ -0,0 +1,6 @@ +from .config import config +count=0 +if count: + from .novelai.post import post,FIFO +else: + from .naifu.post import post,FIFO \ No newline at end of file diff --git a/nonebot-plugin-novelai/naifu/fifo.py b/nonebot-plugin-novelai/naifu/fifo.py new file mode 100644 index 0000000..e189cb2 --- /dev/null +++ b/nonebot-plugin-novelai/naifu/fifo.py @@ -0,0 +1,28 @@ +from ..base.fifo import FIFO_BASE + +class FIFO(FIFO_BASE): + """队列中的单个请求""" + sampler: str = "k_euler_ancestral" + + def body(self, i=0): + # 获取请求体 + 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 + }) + return parameters \ No newline at end of file diff --git a/nonebot-plugin-novelai/naifu/post.py b/nonebot-plugin-novelai/naifu/post.py new file mode 100644 index 0000000..3b5be48 --- /dev/null +++ b/nonebot-plugin-novelai/naifu/post.py @@ -0,0 +1,12 @@ +from ..config import config +from .fifo import FIFO +from ..base.post import post_base + +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="http://"+config.novelai_site + "/generate-stream" + +async def post(fifo: FIFO): + return await post_base(fifo, header, post_api) diff --git a/nonebot-plugin-novelai/novelai/__init__.py b/nonebot-plugin-novelai/novelai/__init__.py deleted file mode 100644 index f9db306..0000000 --- a/nonebot-plugin-novelai/novelai/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .post import post -from .fifo import FIFO \ No newline at end of file diff --git a/nonebot-plugin-novelai/novelai/fifo.py b/nonebot-plugin-novelai/novelai/fifo.py index 1d991a1..d6cb415 100644 --- a/nonebot-plugin-novelai/novelai/fifo.py +++ b/nonebot-plugin-novelai/novelai/fifo.py @@ -1,80 +1,10 @@ -import base64 -from io import BytesIO -import datetime -from PIL import Image -from nonebot import get_driver -from ..utils.data import shapemap from ..config import config -import random -header = { - "authorization": "Bearer " + config.novelai_token, - ":authority": config.novelai_api_domain, - ":path": "/ai/generate-image", - "content-type": "application/json", - "referer": config.novelai_site_domain, - "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", -} +from ..base.fifo import FIFO_BASE -# 自动切换模型 - - -class FIFO(): +class FIFO(FIFO_BASE): """队列中的单个请求""" model: str = "nai-diffusion" if config.novelai_h else "safe-diffusion" - samper: str = "k_euler_ancestral" - - def __init__(self, user_id, group_id, args): - self.time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - self.user_id: str = user_id - self.tags: str = "".join([i+" " for i in args.tags]) - self.seed: list[int] = [args.seed] - self.group_id: str = group_id - self.scale: int = int(args.scale or 11) - self.strength: float = args.strength or 0.7 - self.count: int = args.count - self.steps: int = args.steps or 28 - self.noise: float = args.noise or 0.2 - self.ntags: str = args.ntags or " " - self.img2img: bool = False - self.image: str = None - self.width, self.height = shapemap.get(args.shape, [512, 768]) - # 数值合法检查 - if self.steps <= 0 or self.steps > 50: - self.steps = 28 - # 多图时随机填充剩余seed - for i in range(self.count-1): - self.seed.append(random.randint(0, 4294967295)) - # 计算cost - self.update_cost() - - def update_cost(self): - if config.novelai_paid == 1: - anlas = 0 - if (self.width * self.height > 409600) or self.image or self.count > 1: - anlas += round(self.width * self.height * - self.strength * self.count * self.steps / 2293750) - 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): - # 根据图片重写长宽 - tmpfile = BytesIO(image) - image = Image.open(tmpfile) - width, height = image.size - if width >= height: - self.width = round(width / height * 8) * 64 - self.height = 512 - else: - self.height = round(height / width * 8) * 64 - self.width = 512 - self.image = str(base64.b64encode(self.image), "utf-8") - self.steps = 50 - self.img2img = True - self.update_cost() + sampler: str = "k_euler_ancestral" def body(self, i=0): # 获取请求体 @@ -83,7 +13,7 @@ def body(self, i=0): "height": self.height, "qualityToggle": False, "scale": self.scale, - "sampler": self.samper, + "sampler": self.sampler, "steps": self.steps, "seed": self.seed[i], "n_samples": 1, @@ -100,24 +30,4 @@ def body(self, i=0): "input": self.tags, "model": self.model, "parameters": parameters - } - - def keys(self): - return ( - "seed", "tags", "ntags", "scale", "strength", "noise", "samper", "model", "steps", "width", "height", "img2img") - - def __getitem__(self, item): - return getattr(self, item) - - def format(self): - dict_self = dict(self) - str = "" - for i, v in dict_self.items(): - str += f"{i}={v}\n" - return str - - def __repr__(self): - return f"time={self.time}\nuser_id={self.user_id}\ngroup_id={self.group_id}\ncost={self.cost}\ncount={self.count}\n"+self.format() - - def __str__(self): - return self.__repr__().replace("\n", ";") + } \ No newline at end of file diff --git a/nonebot-plugin-novelai/novelai/post.py b/nonebot-plugin-novelai/novelai/post.py index 7fa29b0..fd25335 100644 --- a/nonebot-plugin-novelai/novelai/post.py +++ b/nonebot-plugin-novelai/novelai/post.py @@ -1,30 +1,16 @@ -import base64 -from io import BytesIO - -import aiohttp -from nonebot.log import logger from ..config import config -from .fifo import FIFO, header -from ..utils import png2jpg +from .fifo import FIFO +from ..base.post import post_base +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" async def post(fifo: FIFO): - # novelai请求交互 - img_bytes = [] - async with aiohttp.ClientSession(headers=header) as session: - for i in range(fifo.count): - # 向novelai服务器发送请求 - async with session.post( - config.novelai_api_domain + "ai/generate-image", json=fifo.body(i) - ) as resp: - if resp.status != 201: - return f"与novelai服务器沟通时发生{resp.status}错误" - img = await resp.text() - img = img.split("data:")[1] - logger.debug(f"获取到novelai返回图片,正在处理") - - # 将图片转化为jpg(BytesIO) - image = BytesIO(base64.b64decode(img)) - image_new = await png2jpg(image) - img_bytes.append(image_new) - return img_bytes + return await post_base(fifo, header, post_api) diff --git a/nonebot-plugin-novelai/sd/fifo.py b/nonebot-plugin-novelai/sd/fifo.py new file mode 100644 index 0000000..e189cb2 --- /dev/null +++ b/nonebot-plugin-novelai/sd/fifo.py @@ -0,0 +1,28 @@ +from ..base.fifo import FIFO_BASE + +class FIFO(FIFO_BASE): + """队列中的单个请求""" + sampler: str = "k_euler_ancestral" + + def body(self, i=0): + # 获取请求体 + 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 + }) + return parameters \ No newline at end of file diff --git a/nonebot-plugin-novelai/sd/post.py b/nonebot-plugin-novelai/sd/post.py new file mode 100644 index 0000000..3b5be48 --- /dev/null +++ b/nonebot-plugin-novelai/sd/post.py @@ -0,0 +1,12 @@ +from ..config import config +from .fifo import FIFO +from ..base.post import post_base + +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="http://"+config.novelai_site + "/generate-stream" + +async def post(fifo: FIFO): + return await post_base(fifo, header, post_api) diff --git a/nonebot-plugin-novelai/utils/save.py b/nonebot-plugin-novelai/utils/save.py index a28ab4d..80acb99 100644 --- a/nonebot-plugin-novelai/utils/save.py +++ b/nonebot-plugin-novelai/utils/save.py @@ -6,12 +6,13 @@ path = Path("data/novelai/output").resolve() async def save_img(fifo, img_bytes: BytesIO, extra: str = "unknown"): # 存储图片 - if config.novelai_save_pic: + if config.novelai_save: path_ = path / extra path_.mkdir(parents=True, exist_ok=True) hash = hashlib.md5(img_bytes.getvalue()).hexdigest() file = (path_ / hash).resolve() async with aiofiles.open(str(file) + ".jpg", "wb") as f: await f.write(img_bytes.getvalue()) - async with aiofiles.open(str(file) + ".txt", "w") as f: - await f.write(repr(fifo)) + if config.novelai_save==2: + async with aiofiles.open(str(file) + ".txt", "w") as f: + await f.write(repr(fifo)) diff --git a/nonebot-plugin-novelai/version.py b/nonebot-plugin-novelai/version.py index 36cf949..adaac6d 100644 --- a/nonebot-plugin-novelai/version.py +++ b/nonebot-plugin-novelai/version.py @@ -19,7 +19,7 @@ def __init__(self): try: self.version = version(self.package) except: - self.version = "0.5.0" + self.version = "0.5.1" async def check_update(self): """检查更新,并推送"""