diff --git a/resources/image/shop_icon/mysterious_potion.png b/resources/image/shop_icon/mysterious_potion.png new file mode 100644 index 00000000..6393be13 Binary files /dev/null and b/resources/image/shop_icon/mysterious_potion.png differ diff --git a/resources/template/shop/main.css b/resources/template/shop/main.css new file mode 100644 index 00000000..c8ac81e6 --- /dev/null +++ b/resources/template/shop/main.css @@ -0,0 +1,321 @@ +@font-face { + font-family: fzrzFont; + /* 导入的字体文件 */ + src: url("../../font/fzrzExtraBold.ttf"); +} + +@font-face { + font-family: msyhFont; + /* 导入的字体文件 */ + src: url("../../font/msyh.ttf"); +} +@font-face { + font-family: hywhFont; + /* 导入的字体文件 */ + src: url("../../font/HYWenHei-85W.ttf"); +} +@font-face { + font-family: syhtFont; + /* 导入的字体文件 */ + src: url("../../font/syht.otf"); +} + +body { + position: absolute; + left: -8px; + top: -8px; +} + +.wrapper { + width: 850px; + font-family: "hywhFont"; + padding: 10px 0; + background-color: #fbe4e4; + box-sizing: border-box; +} + +.top-title { + color: #e87692; + font-size: 85px; + text-align: center; + font-family: "fzrzFont"; + margin-bottom: -30px; +} + +.split { + background-image: url("./res/img/split.png"); + background-repeat: no-repeat; + background-position: center; + height: 15px; + margin-top: 70px; + margin-bottom: 30px; +} + +.top-head { + background-image: url("./res/img/head.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + height: 586px; +} + +.top-aaaa { + font-family: 'syhtFont'; + font-size: 34px; + text-align: center; + color: #E87692; + height: 50px; +} + +.shop-border { + margin-bottom: 50px; +} + +.shop-item { + padding-top: 100px; + box-sizing: border-box; +} + +.shop-item-border { + display: flex; + position: relative; +} + +.shop-item-title { + background-image: url("./res/img/title-bk.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + height: 100px; + margin-bottom: 20px; + display: flex; + justify-content: center; +} + +.shop-item-title-text { + color: white; + font-size: 30px; + margin-top: 13px; +} + +.shop-item-left { + min-height: 460px; + position: relative; + width: 158px; +} + +.shop-item-left-qq { + position: absolute; + height: 100px; +} + +.left-img { + position: absolute; + left: 5px; + z-index: 10; +} + +.shop-item-right { + width: 210px; + position: relative; +} + +.shop-item-right-zx { + height: 460px; + position: absolute; + z-index: 3; + top: 10px; +} + +.right-img { + position: absolute; + right: -2px; + z-index: 10; +} + +.shop-item-mid { + width: 480px; + box-sizing: border-box; + padding-top: 20px; + position: relative; +} + +.shop-item-mid-bk-inner { + width: 520px; + box-sizing: border-box; + padding-top: 30px; + background-color: #be5972; + border: 1px solid #b14b5f; + border-radius: 10px; + position: absolute; + right: -50px; + top: 10px; + z-index: 1; + height: calc(100% - 20px); +} + +.shop-item-mid-bk-out { + width: 520px; + box-sizing: border-box; + background-color: #f096a8; + border: 1px solid #812528; + border-radius: 10px; + height: 100%; + z-index: 2; + position: relative; + padding: 30px; +} + +.goods-item { + background-color: #f8cfd8; + width: 100%; + min-height: 130px; + border-radius: 10px; + padding: 10px; + display: flex; + position: relative; + border: 1px solid #994446; + margin-bottom: 10px; +} + +.goods-id { + position: absolute; + color: white; + font-size: 15px; + border-top-left-radius: 10px; + top: 0; + left: 0; + border-right: 60px solid transparent; + border-bottom: 60px solid transparent; + border-top: 60px solid #ea7492; +} + +.goods-id-text { + position: absolute; + top: -54px; + left: 9px; + color: white; + font-size: 16px; + font-family: "fzrzFont"; + transform: rotate(-45deg); +} + +.goods-item-left { + height: 100%; + min-height: 130px; + display: flex; + flex-direction: column; + justify-content: space-between; + padding-top: 30px; + box-sizing: border-box; +} + +.goods-item-left-o { + height: 24px; + width: 20px; + background-color: #e99eab; + border-radius: 40%; + border: 1px solid #994446; +} + +.goods-item-icon { + background-color: #fefefe; + border: 1px solid #994446; + margin-left: 20px; + width: 120px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; +} + +.goods-item-icon-img { + height: 104px; + width: 104px; +} + +.goods-item-right-des { + width: 272px; + min-height: 80px; + background-color: #fefefe; + border: 1px solid #994446; + margin-left: 10px; + border-radius: 10px; + padding: 5px; + font-family: "msyhFont"; +} + +.goods-item-right-price { + min-height: 30px; + background-color: #fefefe; + border-radius: 30px; + border: 1px solid #994446; + height: 20px; + margin-left: 10px; + margin-top: 5px; + font-size: 15px; + font-family: "msyhFont"; + display: flex; + align-items: center; + padding-left: 10px; + position: relative; +} + +.goods-item-right-btn { + min-height: 30px; + background-color: #bf9ac6; + color: white; + border-radius: 30px; + position: absolute; + right: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 0 10px; +} + +.goods-item-right-btn-line { + width: 1px; + height: 20px; + background-color: #994446; + margin: 0 10px; +} + +.shop-item-mid-bag1 { + position: absolute; + width: 70px; + height: 78px; + bottom: -35px; + left: -35px; + z-index: 4; +} + +.shop-item-mid-bag2 { + position: absolute; + width: 121px; + height: 89px; + right: -35px; + bottom: -35px; + z-index: 4; +} + +.goods-item-name { + font-size: 18px; + font-family: "syhtFont"; +} + +.goods-item-name-line { + height: 2px; + width: 100%; + margin: 3px 0; + background-color: #731c1c; + border-radius: 10px; +} + +.create-text { + position: absolute; + bottom: 1px; + right: 10px; + color: #97979c; + font-size: 15px; +} \ No newline at end of file diff --git a/resources/template/shop/main.html b/resources/template/shop/main.html new file mode 100644 index 00000000..6534234c --- /dev/null +++ b/resources/template/shop/main.html @@ -0,0 +1,128 @@ + + + + + + + + test + + + + + +
+
+ {{name}}的神秘商店 +
+
“使用 '购买道具 [道具ID/道具名称]' 购买道具”
+
+
+
+ {% for data in data_list %} +
+
+ {{data.partition}} +
+
+
+
+
+
+
+ {% for goods in data['goods_list'] %} +
+
+
{{goods.id}}
+
+
+
+
+
+
+
+ +
+
+
+
+ {{goods.name}} +
+
+ {{goods.description}} +
+
+ {{goods.price}}金币 + + 立即购买 + + 限购: {{goods.daily_limit}} + +
+
+
+ {% endfor %} +
+ + +
+
+
+
+ + +
+ {% endfor %} +
+
+
Create By ZhenXun
+
+ + + + \ No newline at end of file diff --git a/resources/template/shop/main.js b/resources/template/shop/main.js new file mode 100644 index 00000000..10bae963 --- /dev/null +++ b/resources/template/shop/main.js @@ -0,0 +1,183 @@ +const leftElements = document.getElementsByClassName("shop-item-left") +const rightElements = document.getElementsByClassName("shop-item-right") + +const defaultList = [ + "1.png", + "2.png", + "3.png", + "4.png", + "5.png", + "qq.png", + "xx1.png", + "xx2.png", +] + +const leftRightImgList = ["1.png", "2.png", "3.png", "4.png", "5.png"] + +const leftRightImgList2 = ["qq.png", "xx1.png", "xx2.png"] + +var leftRightImg = null +var leftRightImg2 = null + +function randomImg() { + const randomIndex = Math.floor(Math.random() * leftRightImgList.length) + const randImg = leftRightImgList[randomIndex] + if (leftRightImg == randImg) { + return randomImg() + } + leftRightImg = randImg + return randImg +} + +function randomImg2() { + const randomIndex = Math.floor(Math.random() * leftRightImgList2.length) + const randImg = leftRightImgList2[randomIndex] + if (leftRightImg == randImg) { + return randomImg2() + } + leftRightImg2 = randImg + return randImg +} + +function getRandomInt(min, max) { + const mathMin = Math.ceil(min) + const mathMax = Math.floor(max) + return Math.floor(Math.random() * (mathMax - mathMin + 1)) + mathMin +} + +function createImgElement(is_qq, is_left, start_height, height) { + const imgElement = document.createElement("img") + const className = is_left ? "left-img" : "right-img" + if (is_qq) { + imgElement.className = "shop-item-left-qq " + className + imgElement.src = "./res/img/left_right/" + randomImg2() + imgElement.style.top = getRandomInt(start_height, height - 20) + "px" + if (is_left) { + imgElement.style.left = getRandomInt(10, 40) + "px" + } else { + imgElement.style.right = getRandomInt(10, 40) + "px" + } + imgElement.style.height = getRandomInt(80, 120) + "px" + imgElement.style.transform = "rotate(" + getRandomInt(0, 45) + "deg)" + } else { + imgElement.className = "shop-item-right-zx " + className + imgElement.src = "./res/img/left_right/" + randomImg() + imgElement.style.top = getRandomInt(start_height, height - 20) + "px" + } + + return imgElement +} + +function getTop(dom) { + return parseInt(dom.style.top.slice(0, -2)) +} + +const randomIndex = Math.floor(Math.random() * defaultList.length) +const leftImg = defaultList[randomIndex] + +var start = true + +if (["qq.png", "xx1.png", "xx2.png"].includes(leftImg)) { + start = true +} else { + start = false +} + +for (let i = 0; i < leftElements.length; i++) { + leftHeight = leftElements[i].offsetHeight + + if (leftHeight <= 1000) { + // 长度不够,只增加一个 + if (start) { + leftElements[i].appendChild( + createImgElement(true, true, 20, leftHeight - 50) + ) + rightElements[i].appendChild(createImgElement(false, false, 10, 60)) + } else { + leftElements[i].appendChild(createImgElement(false, true, 10, 60)) + rightElements[i].appendChild( + createImgElement(true, false, 20, leftHeight - 50) + ) + } + } else { + // 先添加一个气球 + const firstDom = createImgElement(true, true, 20, 200) + leftElements[i].appendChild(firstDom) + let startHeight = 100 + getTop(firstDom) + let endHeight = 300 + getTop(firstDom) + let firstIsQq = false + let inx = 0 + while (leftHeight - endHeight >= 200) { + // 避免过多重复 + rand = Math.random() + if (inx >= 2) { + rand = 0.4 + inx = 0 + } + if (inx <= -1) { + rand = 0.6 + inx = 0 + } + // 真寻和气球随机加 + if (rand > 0.5) { + firstIsQq = true + inx += 1 + const imgDom = createImgElement(true, true, startHeight, endHeight) + leftElements[i].appendChild(imgDom) + startHeight = getRandomInt(250, 350) + getTop(imgDom) + endHeight = getRandomInt(450, 500) + getTop(imgDom) + } else { + if (leftHeight - startHeight < 700) { + continue + } + inx -= 1 + const imgDom = createImgElement(false, true, startHeight, endHeight) + leftElements[i].appendChild(imgDom) + startHeight = getRandomInt(400, 700) + getTop(imgDom) + endHeight = getRandomInt(600, 900) + getTop(imgDom) + if (leftHeight - startHeight < 900) { + break + } + } + } + startHeight = 10 + endHeight = 200 + inx = 0 + while (leftHeight - endHeight >= 200) { + // 真寻和气球随机加 + rand = Math.random() + if (rand > 0.5 && firstIsQq) { + firstIsQq = false + rand = 0.4 + } + // 避免过多重复 + if (inx >= 2) { + rand = 0.4 + inx = 0 + } + if (inx <= -1) { + rand = 0.6 + inx = 0 + } + + if (rand > 0.5) { + inx += 1 + const imgDom = createImgElement(true, false, startHeight, endHeight) + rightElements[i].appendChild(imgDom) + startHeight = getRandomInt(250, 350) + getTop(imgDom) + endHeight = getRandomInt(450, 500) + getTop(imgDom) + } else { + if (leftHeight - startHeight < 700) { + continue + } + inx -= 1 + const imgDom = createImgElement(false, false, startHeight, endHeight) + rightElements[i].appendChild(imgDom) + startHeight = getRandomInt(400, 700) + getTop(imgDom) + endHeight = getRandomInt(600, 900) + getTop(imgDom) + } + } + } + + start = !start +} diff --git a/resources/template/shop/res/img/bag1.png b/resources/template/shop/res/img/bag1.png new file mode 100644 index 00000000..806b3316 Binary files /dev/null and b/resources/template/shop/res/img/bag1.png differ diff --git a/resources/template/shop/res/img/bag2.png b/resources/template/shop/res/img/bag2.png new file mode 100644 index 00000000..d02d1b20 Binary files /dev/null and b/resources/template/shop/res/img/bag2.png differ diff --git a/resources/template/shop/res/img/bag3.png b/resources/template/shop/res/img/bag3.png new file mode 100644 index 00000000..e8cff836 Binary files /dev/null and b/resources/template/shop/res/img/bag3.png differ diff --git a/resources/template/shop/res/img/head.png b/resources/template/shop/res/img/head.png new file mode 100644 index 00000000..e240972c Binary files /dev/null and b/resources/template/shop/res/img/head.png differ diff --git a/resources/template/shop/res/img/item-bk1.png b/resources/template/shop/res/img/item-bk1.png new file mode 100644 index 00000000..2a72b00a Binary files /dev/null and b/resources/template/shop/res/img/item-bk1.png differ diff --git a/resources/template/shop/res/img/item-bk2.png b/resources/template/shop/res/img/item-bk2.png new file mode 100644 index 00000000..c6480ce2 Binary files /dev/null and b/resources/template/shop/res/img/item-bk2.png differ diff --git a/resources/template/shop/res/img/item-bk3.png b/resources/template/shop/res/img/item-bk3.png new file mode 100644 index 00000000..29543cb1 Binary files /dev/null and b/resources/template/shop/res/img/item-bk3.png differ diff --git a/resources/template/shop/res/img/left_right/1.png b/resources/template/shop/res/img/left_right/1.png new file mode 100644 index 00000000..331ee5ea Binary files /dev/null and b/resources/template/shop/res/img/left_right/1.png differ diff --git a/resources/template/shop/res/img/left_right/2.png b/resources/template/shop/res/img/left_right/2.png new file mode 100644 index 00000000..f8c22f5e Binary files /dev/null and b/resources/template/shop/res/img/left_right/2.png differ diff --git a/resources/template/shop/res/img/left_right/3.png b/resources/template/shop/res/img/left_right/3.png new file mode 100644 index 00000000..5490b4dc Binary files /dev/null and b/resources/template/shop/res/img/left_right/3.png differ diff --git a/resources/template/shop/res/img/left_right/4.png b/resources/template/shop/res/img/left_right/4.png new file mode 100644 index 00000000..f617d3b2 Binary files /dev/null and b/resources/template/shop/res/img/left_right/4.png differ diff --git a/resources/template/shop/res/img/left_right/5.png b/resources/template/shop/res/img/left_right/5.png new file mode 100644 index 00000000..8a012d9d Binary files /dev/null and b/resources/template/shop/res/img/left_right/5.png differ diff --git a/resources/template/shop/res/img/left_right/qq.png b/resources/template/shop/res/img/left_right/qq.png new file mode 100644 index 00000000..2faf9e7c Binary files /dev/null and b/resources/template/shop/res/img/left_right/qq.png differ diff --git a/resources/template/shop/res/img/left_right/xx1.png b/resources/template/shop/res/img/left_right/xx1.png new file mode 100644 index 00000000..859a6738 Binary files /dev/null and b/resources/template/shop/res/img/left_right/xx1.png differ diff --git a/resources/template/shop/res/img/left_right/xx2.png b/resources/template/shop/res/img/left_right/xx2.png new file mode 100644 index 00000000..c2c279e7 Binary files /dev/null and b/resources/template/shop/res/img/left_right/xx2.png differ diff --git a/resources/template/shop/res/img/split.png b/resources/template/shop/res/img/split.png new file mode 100644 index 00000000..129e05d9 Binary files /dev/null and b/resources/template/shop/res/img/split.png differ diff --git a/resources/template/shop/res/img/title-bk.png b/resources/template/shop/res/img/title-bk.png new file mode 100644 index 00000000..6c49f190 Binary files /dev/null and b/resources/template/shop/res/img/title-bk.png differ diff --git a/zhenxun/builtin_plugins/shop/__init__.py b/zhenxun/builtin_plugins/shop/__init__.py index bc663e28..4b9cb36a 100644 --- a/zhenxun/builtin_plugins/shop/__init__.py +++ b/zhenxun/builtin_plugins/shop/__init__.py @@ -16,7 +16,7 @@ from nonebot_plugin_alconna import ( ) from nonebot_plugin_uninfo import Uninfo -from zhenxun.configs.utils import BaseBlock, PluginExtraData +from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.depends import UserName from zhenxun.utils.enum import BlockType, PluginType @@ -25,6 +25,7 @@ from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import PlatformUtils from ._data_source import ShopManage, gold_rank +from .goods_register import * # noqa: F403 __plugin_meta__ = PluginMetadata( name="商店", @@ -45,6 +46,14 @@ __plugin_meta__ = PluginMetadata( plugin_type=PluginType.NORMAL, menu_type="商店", limits=[BaseBlock(check_type=BlockType.GROUP)], + configs=[ + RegisterConfig( + key="style", + value="zhenxun", + help="商店样式类型,[normal, zhenxun]", + default_value="zhenxun", + ) + ], ).dict(), ) @@ -108,7 +117,7 @@ _matcher.shortcut( @_matcher.assign("$main") async def _(session: Uninfo, arparma: Arparma): - image = await ShopManage.build_shop_image() + image = await ShopManage.get_shop_image() logger.info("查看商店", arparma.header_result, session=session) await MessageUtils.build_message(image).send() diff --git a/zhenxun/builtin_plugins/shop/_data_source.py b/zhenxun/builtin_plugins/shop/_data_source.py index bc62e015..578fd461 100644 --- a/zhenxun/builtin_plugins/shop/_data_source.py +++ b/zhenxun/builtin_plugins/shop/_data_source.py @@ -11,7 +11,6 @@ from nonebot_plugin_alconna import UniMessage, UniMsg from nonebot_plugin_uninfo import Uninfo from pydantic import BaseModel, create_model -from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.models.friend_user import FriendUser from zhenxun.models.goods_info import GoodsInfo from zhenxun.models.group_member_info import GroupInfoUser @@ -20,19 +19,12 @@ from zhenxun.models.user_gold_log import UserGoldLog from zhenxun.models.user_props_log import UserPropsLog from zhenxun.services.log import logger from zhenxun.utils.enum import GoldHandle, PropHandle -from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image +from zhenxun.utils.image_utils import BuildImage, ImageTemplate from zhenxun.utils.platform import PlatformUtils -ICON_PATH = IMAGE_PATH / "shop_icon" - -RANK_ICON_PATH = IMAGE_PATH / "_icon" - -PLATFORM_PATH = { - "dodo": RANK_ICON_PATH / "dodo.png", - "discord": RANK_ICON_PATH / "discord.png", - "kaiheila": RANK_ICON_PATH / "kook.png", - "qq": RANK_ICON_PATH / "qq.png", -} +from .config import ICON_PATH, PLATFORM_PATH, base_config +from .html_image import html_image +from .normal_image import normal_image class Goods(BaseModel): @@ -138,6 +130,12 @@ async def gold_rank( class ShopManage: uuid2goods: dict[str, Goods] = {} # noqa: RUF012 + @classmethod + async def get_shop_image(cls) -> bytes: + if base_config.get("style") == "zhenxun": + return await html_image() + return await normal_image() + @classmethod def __build_params( cls, @@ -492,200 +490,3 @@ class ShopManage: """ user = await UserConsole.get_user(user_id, platform) return user.gold - - @classmethod - async def build_shop_image(cls) -> BuildImage: - """制作商店图片 - - 返回: - BuildImage: 商店图片 - """ - goods_lst = await GoodsInfo.get_all_goods() - h = 10 - _list: list[GoodsInfo] = [ - goods - for goods in goods_lst - if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time - ] - # A = BuildImage(1100, h, color="#f9f6f2") - total_n = 0 - image_list = [] - for idx, goods in enumerate(_list): - name_image = BuildImage( - 580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf" - ) - await name_image.text( - (15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height" - ) - await name_image.line((380, -5, 280, 45), "#a29ad6", 5) - await name_image.text((390, 0), "售价:", center_type="height") - if goods.goods_discount != 1: - discount_price = int(goods.goods_discount * goods.goods_price) - old_price_image = await BuildImage.build_text_image( - str(goods.goods_price), font_color=(194, 194, 194), size=15 - ) - await old_price_image.line( - ( - 0, - int(old_price_image.height / 2), - old_price_image.width + 1, - int(old_price_image.height / 2), - ), - (0, 0, 0), - ) - await name_image.paste(old_price_image, (440, 0)) - await name_image.text((440, 15), str(discount_price), (255, 255, 255)) - else: - await name_image.text( - (440, 0), - str(goods.goods_price), - (255, 255, 255), - center_type="height", - ) - _tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25) - await name_image.text( - ( - 440 + _tmp.width, - 0, - ), - " 金币", - center_type="height", - ) - des_image = None - font_img = BuildImage(600, 80, font_size=20, color="#a29ad6") - p = font_img.getsize("简介:")[0] + 20 - if goods.goods_description: - des_list = goods.goods_description.split("\n") - desc = "" - for des in des_list: - if font_img.getsize(des)[0] > font_img.width - p - 20: - msg = "" - tmp = "" - for i in range(len(des)): - if font_img.getsize(tmp)[0] < font_img.width - p - 20: - tmp += des[i] - else: - msg += tmp + "\n" - tmp = des[i] - desc += msg - if tmp: - desc += tmp - else: - desc += des + "\n" - if desc[-1] == "\n": - desc = desc[:-1] - des_image = await text2image(desc, color="#a29ad6") - goods_image = BuildImage( - 600, - (50 + des_image.height) if des_image else 50, - font_size=20, - color="#a29ad6", - font="CJGaoDeGuo.otf", - ) - if des_image: - await goods_image.text((15, 50), "简介:") - await goods_image.paste(des_image, (p, 50)) - await name_image.circle_corner(5) - await goods_image.paste(name_image, (0, 5), center_type="width") - await goods_image.circle_corner(20) - bk = BuildImage( - 1180, - (50 + des_image.height) if des_image else 50, - font_size=15, - color="#f9f6f2", - font="CJGaoDeGuo.otf", - ) - if goods.icon and (ICON_PATH / goods.icon).exists(): - icon = BuildImage(70, 70, background=ICON_PATH / goods.icon) - await bk.paste(icon) - await bk.paste(goods_image, (70, 0)) - n = 0 - _w = 650 - # 添加限时图标和时间 - if goods.goods_limit_time > 0: - n += 140 - _limit_time_logo = BuildImage( - 40, 40, background=f"{IMAGE_PATH}/other/time.png" - ) - await bk.paste(_limit_time_logo, (_w + 50, 0)) - _time_img = await BuildImage.build_text_image("限时!", size=23) - await bk.paste( - _time_img, - (_w + 90, 10), - ) - limit_time = time.strftime( - "%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time) - ).split() - y_m_d = limit_time[0] - _h_m = limit_time[1].split(":") - h_m = f"{_h_m[0]}时 {_h_m[1]}分" - await bk.text((_w + 55, 38), str(y_m_d)) - await bk.text((_w + 65, 57), str(h_m)) - _w += 140 - if goods.goods_discount != 1: - n += 140 - _discount_logo = BuildImage( - 30, 30, background=f"{IMAGE_PATH}/other/discount.png" - ) - await bk.paste(_discount_logo, (_w + 50, 10)) - _tmp = await BuildImage.build_text_image("折扣!", size=23) - await bk.paste(_tmp, (_w + 90, 15)) - _tmp = await BuildImage.build_text_image( - f"{10 * goods.goods_discount:.1f} 折", - size=30, - font_color=(85, 156, 75), - ) - await bk.paste(_tmp, (_w + 50, 44)) - _w += 140 - if goods.daily_limit != 0: - n += 140 - _daily_limit_logo = BuildImage( - 35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png" - ) - await bk.paste(_daily_limit_logo, (_w + 50, 10)) - _tmp = await BuildImage.build_text_image( - "限购!", - size=23, - ) - await bk.paste(_tmp, (_w + 90, 20)) - _tmp = await BuildImage.build_text_image( - f"{goods.daily_limit}", size=30 - ) - await bk.paste(_tmp, (_w + 72, 45)) - total_n = max(total_n, n) - if n: - await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5) - # await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5) - - # 添加限时图标和时间 - image_list.append(bk) - # await A.apaste(bk, (0, current_h), True) - # current_h += 90 - current_h = 0 - h = sum(img.height + 10 for img in image_list) or 400 - A = BuildImage(1100, h, color="#f9f6f2") - for img in image_list: - await A.paste(img, (0, current_h)) - current_h += img.height + 10 - w = 950 - if total_n: - w += total_n - h = A.height + 230 + 100 - h = max(h, 1000) - shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png") - shop = BuildImage(w, h, font_size=20, color="#f9f6f2") - await shop.paste(A, (20, 230)) - await shop.paste(shop_logo, (450, 30)) - tip = "注【通过 购买道具 序号 或者 商品名称 购买】" - await shop.text( - ( - int((1000 - shop.getsize(tip)[0]) / 2), - 170, - ), - "注【通过 序号 或者 商品名称 购买】", - ) - await shop.text( - (20, h - 100), - "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~", - ) - return shop diff --git a/zhenxun/builtin_plugins/shop/config.py b/zhenxun/builtin_plugins/shop/config.py new file mode 100644 index 00000000..7fa13a5d --- /dev/null +++ b/zhenxun/builtin_plugins/shop/config.py @@ -0,0 +1,20 @@ +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH + +base_config = Config.get("shop") + +ICON_PATH = IMAGE_PATH / "shop_icon" + + +RANK_ICON_PATH = IMAGE_PATH / "_icon" + +PLATFORM_PATH = { + "dodo": RANK_ICON_PATH / "dodo.png", + "discord": RANK_ICON_PATH / "discord.png", + "kaiheila": RANK_ICON_PATH / "kook.png", + "qq": RANK_ICON_PATH / "qq.png", +} + +LEFT_RIGHT_IMAGE = ["1.png", "2.png", "qq.png"] + +LEFT_RIGHT_PATH = TEMPLATE_PATH / "shop" / "res" / "img" diff --git a/zhenxun/builtin_plugins/shop/goods_register.py b/zhenxun/builtin_plugins/shop/goods_register.py new file mode 100644 index 00000000..1f1eab68 --- /dev/null +++ b/zhenxun/builtin_plugins/shop/goods_register.py @@ -0,0 +1,161 @@ +from zhenxun.models.user_console import UserConsole +from zhenxun.utils.decorator.shop import shop_register + + +@shop_register( + name="神秘药水", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="小秘密", + icon="mysterious_potion.png", +) +async def _(user_id: str): + await UserConsole.add_gold( + user_id, + 1000000, + "shop", + ) + return "使用道具神秘药水成功!你滴金币+1000000!" + + +@shop_register( + name="测试道具A1", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A2", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A3", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A4", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A5", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A6", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A7", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A8", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A9", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A10", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A11", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A12", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass + + +@shop_register( + name="测试道具A13", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="TEST", + icon="mysterious_potion.png", +) +async def _(user_id: str): + pass diff --git a/zhenxun/builtin_plugins/shop/html_image.py b/zhenxun/builtin_plugins/shop/html_image.py new file mode 100644 index 00000000..49f3f046 --- /dev/null +++ b/zhenxun/builtin_plugins/shop/html_image.py @@ -0,0 +1,63 @@ +import time + +from nonebot_plugin_htmlrender import template_to_pic +from pydantic import BaseModel + +from zhenxun.configs.config import BotConfig +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.goods_info import GoodsInfo +from zhenxun.utils._build_image import BuildImage + +from .config import ICON_PATH + + +class GoodsItem(BaseModel): + goods_list: list[dict] + """商品列表""" + partition: str + """分区名称""" + + +async def html_image() -> bytes: + """构建图片""" + goods_list: list[tuple[int, GoodsInfo]] = [ + (i + 1, goods) + for i, goods in enumerate(await GoodsInfo.get_all_goods()) + if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time + ] + partition_dict: dict[str, list[dict]] = {} + for goods in goods_list: + if not goods[1].partition: + goods[1].partition = "默认分区" + if goods[1].partition not in partition_dict: + partition_dict[goods[1].partition] = [] + partition_dict[goods[1].partition].append( + { + "id": goods[0], + "price": goods[1].goods_price, + "daily_limit": goods[1].daily_limit or "∞", + "name": goods[1].goods_name, + "icon": "data:image/png;base64," + + BuildImage.open(ICON_PATH / goods[1].icon).pic2bs4()[9:], + "description": goods[1].goods_description, + } + ) + data_list = [] + for partition in partition_dict: + data_list.append( + GoodsItem( + goods_list=partition_dict[partition], + partition=partition, + ) + ) + + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "shop").absolute()), + template_name="main.html", + templates={"name": BotConfig.self_nickname, "data_list": data_list}, + pages={ + "viewport": {"width": 850, "height": 1024}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/builtin_plugins/shop/normal_image.py b/zhenxun/builtin_plugins/shop/normal_image.py new file mode 100644 index 00000000..76420a24 --- /dev/null +++ b/zhenxun/builtin_plugins/shop/normal_image.py @@ -0,0 +1,203 @@ +import time + +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.goods_info import GoodsInfo +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.image_utils import text2image + +from .config import ICON_PATH + + +async def normal_image() -> bytes: + """制作商店图片 + + 返回: + BuildImage: 商店图片 + """ + goods_lst = await GoodsInfo.get_all_goods() + h = 10 + _list: list[GoodsInfo] = [ + goods + for goods in goods_lst + if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time + ] + # A = BuildImage(1100, h, color="#f9f6f2") + total_n = 0 + image_list = [] + for idx, goods in enumerate(_list): + name_image = BuildImage( + 580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf" + ) + await name_image.text( + (15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height" + ) + await name_image.line((380, -5, 280, 45), "#a29ad6", 5) + await name_image.text((390, 0), "售价:", center_type="height") + if goods.goods_discount != 1: + discount_price = int(goods.goods_discount * goods.goods_price) + old_price_image = await BuildImage.build_text_image( + str(goods.goods_price), font_color=(194, 194, 194), size=15 + ) + await old_price_image.line( + ( + 0, + int(old_price_image.height / 2), + old_price_image.width + 1, + int(old_price_image.height / 2), + ), + (0, 0, 0), + ) + await name_image.paste(old_price_image, (440, 0)) + await name_image.text((440, 15), str(discount_price), (255, 255, 255)) + else: + await name_image.text( + (440, 0), + str(goods.goods_price), + (255, 255, 255), + center_type="height", + ) + _tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25) + await name_image.text( + ( + 440 + _tmp.width, + 0, + ), + " 金币", + center_type="height", + ) + des_image = None + font_img = BuildImage(600, 80, font_size=20, color="#a29ad6") + p = font_img.getsize("简介:")[0] + 20 + if goods.goods_description: + des_list = goods.goods_description.split("\n") + desc = "" + for des in des_list: + if font_img.getsize(des)[0] > font_img.width - p - 20: + msg = "" + tmp = "" + for i in range(len(des)): + if font_img.getsize(tmp)[0] < font_img.width - p - 20: + tmp += des[i] + else: + msg += tmp + "\n" + tmp = des[i] + desc += msg + if tmp: + desc += tmp + else: + desc += des + "\n" + if desc[-1] == "\n": + desc = desc[:-1] + des_image = await text2image(desc, color="#a29ad6") + goods_image = BuildImage( + 600, + (50 + des_image.height) if des_image else 50, + font_size=20, + color="#a29ad6", + font="CJGaoDeGuo.otf", + ) + if des_image: + await goods_image.text((15, 50), "简介:") + await goods_image.paste(des_image, (p, 50)) + await name_image.circle_corner(5) + await goods_image.paste(name_image, (0, 5), center_type="width") + await goods_image.circle_corner(20) + bk = BuildImage( + 1180, + (50 + des_image.height) if des_image else 50, + font_size=15, + color="#f9f6f2", + font="CJGaoDeGuo.otf", + ) + if goods.icon and (ICON_PATH / goods.icon).exists(): + icon = BuildImage(70, 70, background=ICON_PATH / goods.icon) + await bk.paste(icon) + await bk.paste(goods_image, (70, 0)) + n = 0 + _w = 650 + # 添加限时图标和时间 + if goods.goods_limit_time > 0: + n += 140 + _limit_time_logo = BuildImage( + 40, 40, background=f"{IMAGE_PATH}/other/time.png" + ) + await bk.paste(_limit_time_logo, (_w + 50, 0)) + _time_img = await BuildImage.build_text_image("限时!", size=23) + await bk.paste( + _time_img, + (_w + 90, 10), + ) + limit_time = time.strftime( + "%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time) + ).split() + y_m_d = limit_time[0] + _h_m = limit_time[1].split(":") + h_m = f"{_h_m[0]}时 {_h_m[1]}分" + await bk.text((_w + 55, 38), str(y_m_d)) + await bk.text((_w + 65, 57), str(h_m)) + _w += 140 + if goods.goods_discount != 1: + n += 140 + _discount_logo = BuildImage( + 30, 30, background=f"{IMAGE_PATH}/other/discount.png" + ) + await bk.paste(_discount_logo, (_w + 50, 10)) + _tmp = await BuildImage.build_text_image("折扣!", size=23) + await bk.paste(_tmp, (_w + 90, 15)) + _tmp = await BuildImage.build_text_image( + f"{10 * goods.goods_discount:.1f} 折", + size=30, + font_color=(85, 156, 75), + ) + await bk.paste(_tmp, (_w + 50, 44)) + _w += 140 + if goods.daily_limit != 0: + n += 140 + _daily_limit_logo = BuildImage( + 35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png" + ) + await bk.paste(_daily_limit_logo, (_w + 50, 10)) + _tmp = await BuildImage.build_text_image( + "限购!", + size=23, + ) + await bk.paste(_tmp, (_w + 90, 20)) + _tmp = await BuildImage.build_text_image(f"{goods.daily_limit}", size=30) + await bk.paste(_tmp, (_w + 72, 45)) + total_n = max(total_n, n) + if n: + await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5) + # await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5) + + # 添加限时图标和时间 + image_list.append(bk) + # await A.apaste(bk, (0, current_h), True) + # current_h += 90 + current_h = 0 + h = sum(img.height + 10 for img in image_list) or 400 + A = BuildImage(1100, h, color="#f9f6f2") + for img in image_list: + await A.paste(img, (0, current_h)) + current_h += img.height + 10 + w = 950 + if total_n: + w += total_n + h = A.height + 230 + 100 + h = max(h, 1000) + shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png") + shop = BuildImage(w, h, font_size=20, color="#f9f6f2") + await shop.paste(A, (20, 230)) + await shop.paste(shop_logo, (450, 30)) + tip = "注【通过 购买道具 序号 或者 商品名称 购买】" + await shop.text( + ( + int((1000 - shop.getsize(tip)[0]) / 2), + 170, + ), + "注【通过 序号 或者 商品名称 购买】", + ) + await shop.text( + (20, h - 100), + "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~", + ) + return shop.pic2bytes() diff --git a/zhenxun/models/goods_info.py b/zhenxun/models/goods_info.py index 4560d903..6fba55fe 100644 --- a/zhenxun/models/goods_info.py +++ b/zhenxun/models/goods_info.py @@ -27,10 +27,12 @@ class GoodsInfo(Model): """每日限购""" is_passive = fields.BooleanField(default=False) """是否为被动道具""" + partition = fields.CharField(255, null=True) + """分区名称""" icon = fields.TextField(null=True) """图标路径""" - class Meta: + class Meta: # type: ignore table = "goods_info" table_description = "商品数据表" @@ -44,6 +46,7 @@ class GoodsInfo(Model): goods_limit_time: int = 0, daily_limit: int = 0, is_passive: bool = False, + partition: str | None = None, icon: str | None = None, ) -> str: """添加商品 @@ -56,6 +59,7 @@ class GoodsInfo(Model): goods_limit_time: 商品限时 daily_limit: 每日购买限制 is_passive: 是否为被动道具 + partition: 分区名称 icon: 图标 """ if not await cls.exists(goods_name=goods_name): @@ -69,6 +73,7 @@ class GoodsInfo(Model): goods_limit_time=goods_limit_time, daily_limit=daily_limit, is_passive=is_passive, + partition=partition, icon=icon, ) return str(uuid_) @@ -159,4 +164,5 @@ class GoodsInfo(Model): "ALTER TABLE goods_info ADD icon VARCHAR(255);", # 删除 daily_purchase_limit 字段 "ALTER TABLE goods_info DROP daily_purchase_limit;", + "ALTER TABLE goods_info ADD partition VARCHAR(255);", ] diff --git a/zhenxun/utils/decorator/shop.py b/zhenxun/utils/decorator/shop.py index 61cfa320..f84225b5 100644 --- a/zhenxun/utils/decorator/shop.py +++ b/zhenxun/utils/decorator/shop.py @@ -17,6 +17,7 @@ class Goods(BaseModel): daily_limit: int icon: str | None = None is_passive: bool + partition: str | None func: Callable kwargs: dict[str, str] = {} send_success_msg: bool @@ -73,6 +74,7 @@ class ShopRegister(dict): load_status: tuple[bool, ...], daily_limit: tuple[int, ...], is_passive: tuple[bool, ...], + partition: tuple[str, ...], icon: tuple[str, ...], send_success_msg: tuple[bool, ...], max_num_limit: tuple[int, ...], @@ -89,6 +91,7 @@ class ShopRegister(dict): load_status: 是否加载 daily_limit: 每日限购 is_passive: 是否被动道具 + partition: 分区名称 icon: 图标 send_success_msg: 成功时发送消息 max_num_limit: 单次最大使用次数 @@ -97,7 +100,7 @@ class ShopRegister(dict): def add_register_item(func: Callable): if name in self._data.keys(): raise ValueError("该商品已注册,请替换其他名称!") - for n, p, d, dd, lmt, s, dl, pa, i, ssm, mnl in zip( + for n, p, d, dd, lmt, s, dl, pa, par, i, ssm, mnl in zip( name, price, des, @@ -106,6 +109,7 @@ class ShopRegister(dict): load_status, daily_limit, is_passive, + partition, icon, send_success_msg, max_num_limit, @@ -124,6 +128,7 @@ class ShopRegister(dict): limit_time=lmt, daily_limit=dl, is_passive=pa, + partition=par, func=func, send_success_msg=ssm, max_num_limit=mnl, @@ -135,6 +140,7 @@ class ShopRegister(dict): goods.daily_limit = dl goods.icon = i goods.is_passive = pa + goods.partition = par goods.func = func goods.kwargs = _temp_kwargs goods.send_success_msg = ssm @@ -162,6 +168,7 @@ class ShopRegister(dict): goods.limit_time, goods.daily_limit, goods.is_passive, + goods.partition, goods.icon, ) if uuid: @@ -186,6 +193,7 @@ class ShopRegister(dict): load_status: bool | tuple[bool, ...] = True, daily_limit: int | tuple[int, ...] = 0, is_passive: bool | tuple[bool, ...] = False, + partition: str | tuple[str, ...] | None = None, icon: str | tuple[str, ...] = "", send_success_msg: bool | tuple[bool, ...] = True, max_num_limit: int | tuple[int, ...] = 1, @@ -202,11 +210,11 @@ class ShopRegister(dict): load_status: 是否加载 daily_limit: 每日限购 is_passive: 是否被动道具 + partition: 分区名称 icon: 图标 send_success_msg: 成功时发送消息 max_num_limit: 单次最大使用次数 """ - _tuple_list = [] _current_len = -1 for x in [name, price, des, discount, limit_time, load_status]: if isinstance(x, tuple): @@ -226,6 +234,7 @@ class ShopRegister(dict): _load_status = self.__get(load_status, _current_len) _daily_limit = self.__get(daily_limit, _current_len) _is_passive = self.__get(is_passive, _current_len) + _partition = self.__get(partition, _current_len) _icon = self.__get(icon, _current_len) _send_success_msg = self.__get(send_success_msg, _current_len) _max_num_limit = self.__get(max_num_limit, _current_len) @@ -238,6 +247,7 @@ class ShopRegister(dict): _load_status, _daily_limit, _is_passive, + _partition, _icon, _send_success_msg, _max_num_limit,