Compare commits

..
25 Commits
Author SHA1 Message Date
HibiKier d6110e3c6f update bilibili_sub 2022-07-03 22:00:49 +08:00
HibiKier 4c5302bd4c fix shop update goods 2022-07-03 16:47:19 +08:00
HibiKier 21ee3d5c3e update send_setu 2022-07-03 16:33:14 +08:00
HibiKier e5c036cee8 update shop image 2022-07-03 13:55:26 +08:00
HibiKier 6f82488af7 Merge pull request #879 from VirtualTowel/main
修复图库内图片无法被连续删除的问题
2022-07-03 13:53:49 +08:00
VirtualTowel 57a7903c63 修复图库内图片无法被连续删除的问题 2022-07-01 20:12:39 +08:00
HibiKier 27b89b333a Merge pull request #866 from Excalibur5100/main
修复色图插件api修改无法搜索图片
2022-06-28 20:48:33 +08:00
Excalibur5100 ce2c4e7492 修复色图插件最近加tag参数后搜不到图的问题
插件所用API更新,最大数量num改为20
https://api.lolicon.app/#/setu
2022-06-28 19:48:59 +08:00
HibiKier cad98a2a33 Update goods_info.py 2022-06-26 22:23:19 +08:00
HibiKier 40da48e685 update models 2022-06-26 19:29:51 +08:00
HibiKier 1c393e5c3e update shop daily_limit 2022-06-26 03:08:23 +08:00
HibiKier 667c143c74 update pix 2022-06-26 01:37:47 +08:00
HibiKier bc089c18ce update bt 2022-06-26 01:26:14 +08:00
HibiKier 24b730c0e2 Merge pull request #847 from po-lan/main
对WSL虚拟机 代理 添加写法
2022-06-24 20:59:28 +08:00
po-lan 9ed9e9b973 对WSL虚拟机 代理 添加写法 2022-06-23 17:57:47 +08:00
HibiKier 5b0027aa3d Update README.md 2022-06-22 22:49:26 +08:00
HibiKier 810c5e4e8b Update README.md 2022-06-21 21:59:41 +08:00
HibiKier 907534958f Merge pull request #840 from yajiwa/main
update parse_bilibili
2022-06-21 21:59:23 +08:00
HibiKier f08a18ab07 Update data_class.py 2022-06-21 21:01:14 +08:00
github-actions[bot] 40549d5ca7 Auto generate poetry.lock 2022-06-21 00:56:57 +00:00
yajiwa 3e67edc506 update pyproject.toml 2022-06-21 08:53:49 +08:00
yajiwa 9c56a81f65 update parse_bilibili 2022-06-21 08:50:05 +08:00
HibiKier a9a636c9ae Update plugins2settings_manager.py 2022-06-20 22:31:57 +08:00
HibiKier 98374dcd74 Update README.md 2022-06-20 22:07:48 +08:00
HibiKier 10ca6eec91 update webui 2022-06-20 21:54:10 +08:00
29 changed files with 863 additions and 941 deletions
+31 -1
View File
@@ -243,6 +243,33 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
## 更新 ## 更新
### 2022/7/3
* BagUser提供方法`check_props`方法,用于判断用户是否含有该道具并是否使用
* 优化商品显示图片,提供限制,限购,折扣提示
* 修复图库内图片无法被连续删除的问题 [@pull/879](https://github.com/HibiKier/zhenxun_bot/pull/879)
* 色图提供配置项`MAX_ONCE_NUM2FORWARD`:群聊中单次发送图片数量达到指定时使用合并转发
* 优化修复了商品修改命令
* B站订阅UP动态改为详情页截图
### 2022/6/28
* 修复色图插件api修改无法搜索图片 [@pull/866](https://github.com/HibiKier/zhenxun_bot/pull/866)
### 2022/6/26
* 替换了bt URL
* PIX当使用pid查询图片时,会发送该pid下所有图片
* 商店提供了每日购买次数限制
### 2022/6/24
* 对WSL虚拟机 代理 添加写法 [@pull/847](https://github.com/HibiKier/zhenxun_bot/pull/847)
### 2022/6/21
* update parse_bilibili [@pull/840](https://github.com/HibiKier/zhenxun_bot/pull/840)
### 2022/6/19 \[v0.1.6.0] ### 2022/6/19 \[v0.1.6.0]
* 暂时使用hook修复webui中plugins2setting修改时会改变plugins2setting.cmd为字符串 * 暂时使用hook修复webui中plugins2setting修改时会改变plugins2setting.cmd为字符串
@@ -654,10 +681,13 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
__..... 更多更新信息请查看文档__ __..... 更多更新信息请查看文档__
## Todo ## Todo
- [ ] web管理 - [ ] web管理
## 爱发电
<img width="365px" height="450px" src="https://user-images.githubusercontent.com/45528451/175059389-cfeb8174-fa07-4939-80ab-a039087a50f6.png">
## 感谢 ## 感谢
[botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议 [botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议
[Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp的golang实现,轻量、原生跨平台. [Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp的golang实现,轻量、原生跨平台.
-1
View File
@@ -6,7 +6,6 @@ from models.group_info import GroupInfo
from models.friend_user import FriendUser from models.friend_user import FriendUser
from nonebot.adapters.onebot.v11 import ActionFailed from nonebot.adapters.onebot.v11 import ActionFailed
from configs.config import NICKNAME, Config from configs.config import NICKNAME, Config
from utils.manager import group_manager
from pathlib import Path from pathlib import Path
import shutil import shutil
+8
View File
@@ -112,6 +112,14 @@ async def _():
"ALTER TABLE chat_history ADD plain_text Text;", "ALTER TABLE chat_history ADD plain_text Text;",
"chat_history" "chat_history"
), # 新增纯文本 ), # 新增纯文本
(
"ALTER TABLE goods_info ADD daily_limit Integer DEFAULT 0;",
"goods_info"
), # 新增纯文本
(
"ALTER TABLE goods_info ADD daily_purchase_limit Json DEFAULT '{}';",
"goods_info"
), # 新增纯文本
] ]
for sql in sql_str: for sql in sql_str:
try: try:
+4
View File
@@ -77,7 +77,11 @@ async def _(event: GroupMessageEvent, arg: Message = CommandArg()):
await BagUser.get_gold(event.user_id, event.group_id) await BagUser.get_gold(event.user_id, event.group_id)
) < goods.goods_price * num * goods.goods_discount: ) < goods.goods_price * num * goods.goods_discount:
await buy.finish("您的金币好像不太够哦", at_sender=True) await buy.finish("您的金币好像不太够哦", at_sender=True)
flag, n = await GoodsInfo.check_user_daily_purchase(goods, event.user_id, event.group_id, num)
if flag:
await buy.finish(f"该次购买将超过每日次数限制,目前该道具还可以购买{n}次哦", at_sender=True)
if await BagUser.buy_property(event.user_id, event.group_id, goods, num): if await BagUser.buy_property(event.user_id, event.group_id, goods, num):
await GoodsInfo.add_user_daily_purchase(goods, event.user_id, event.group_id, num)
await buy.send( await buy.send(
f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 成功!", f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 成功!",
at_sender=True, at_sender=True,
+15 -3
View File
@@ -1,10 +1,10 @@
from .data_source import create_shop_help, delete_goods, update_goods, register_goods, parse_goods_info from .data_source import create_shop_help, delete_goods, update_goods, register_goods, parse_goods_info, GoodsInfo
from nonebot.adapters.onebot.v11 import MessageEvent, Message from nonebot.adapters.onebot.v11 import MessageEvent, Message
from nonebot import on_command from nonebot import on_command
from configs.path_config import IMAGE_PATH from configs.path_config import IMAGE_PATH
from utils.message_builder import image from utils.message_builder import image
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from utils.utils import is_number from utils.utils import is_number, scheduler
from nonebot.params import CommandArg from nonebot.params import CommandArg
from nonebot.plugin import export from nonebot.plugin import export
from services.log import logger from services.log import logger
@@ -128,6 +128,18 @@ async def _(event: MessageEvent, arg: Message = CommandArg()):
await shop_update_goods.send(f"修改商品 {name} 成功了...\n{text}", at_sender=True) await shop_update_goods.send(f"修改商品 {name} 成功了...\n{text}", at_sender=True)
logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 成功") logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 成功")
else: else:
await shop_update_goods.send(f"修改商品 {name} 失败了...", at_sender=True) await shop_update_goods.send(name, at_sender=True)
logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 失败") logger.info(f"USER {event.user_id} 修改商品 {name} 数据 {text} 失败")
@scheduler.scheduled_job(
"cron",
hour=0,
minute=0,
)
async def _():
try:
await GoodsInfo.reset_daily_purchase()
logger.info("商品每日限购次数重置成功...")
except Exception as e:
logger.error(f"商品每日限购次数重置发生错误 {type(e)}:{e}")
+102 -38
View File
@@ -1,9 +1,11 @@
from PIL import Image
from models.goods_info import GoodsInfo from models.goods_info import GoodsInfo
from utils.image_utils import BuildImage from utils.image_utils import BuildImage
from models.sign_group_user import SignGroupUser from models.sign_group_user import SignGroupUser
from utils.utils import is_number from utils.utils import is_number
from configs.path_config import IMAGE_PATH from configs.path_config import IMAGE_PATH
from typing import Optional, Union from typing import Optional, Union, Tuple
from configs.config import Config from configs.config import Config
from nonebot import Driver from nonebot import Driver
from nonebot.plugin import require from nonebot.plugin import require
@@ -32,6 +34,7 @@ async def init_default_shop_goods():
"下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)", "下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)",
), ),
load_status=Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS"), load_status=Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS"),
daily_limit=(10, 20, 30),
** {"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, ** {"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3},
) )
async def sign_card(user_id: int, group_id: int, prob: float): async def sign_card(user_id: int, group_id: int, prob: float):
@@ -62,8 +65,9 @@ async def create_shop_help() -> str:
_list.append(goods) _list.append(goods)
A = BuildImage(1000, h, color="#f9f6f2") A = BuildImage(1000, h, color="#f9f6f2")
current_h = 0 current_h = 0
total_n = 0
for goods in _list: for goods in _list:
bk = BuildImage(700, 80, font_size=15, color="#f9f6f2", font="CJGaoDeGuo.otf") bk = BuildImage(1180, 80, font_size=15, color="#f9f6f2", font="CJGaoDeGuo.otf")
goods_image = BuildImage( goods_image = BuildImage(
600, 80, font_size=20, color="#a29ad6", font="CJGaoDeGuo.otf" 600, 80, font_size=20, color="#a29ad6", font="CJGaoDeGuo.otf"
) )
@@ -75,16 +79,27 @@ async def create_shop_help() -> str:
) )
await name_image.aline((380, -5, 280, 45), "#a29ad6", 5) await name_image.aline((380, -5, 280, 45), "#a29ad6", 5)
await name_image.atext((390, 0), "售价:", center_type="by_height") await name_image.atext((390, 0), "售价:", center_type="by_height")
await name_image.atext( if goods.goods_discount != 1:
(440, 0), str(goods.goods_price), (255, 255, 255), center_type="by_height" discount_price = int(goods.goods_discount * goods.goods_price)
) old_price_image = BuildImage(0, 0, plain_text=str(goods.goods_price), font_color=(194, 194, 194), font="CJGaoDeGuo.otf", font_size=15)
await old_price_image.aline((0, int(old_price_image.h / 2), old_price_image.w + 1, int(old_price_image.h / 2)), (0, 0, 0))
await name_image.apaste(
old_price_image, (440, 0), True
)
await name_image.atext(
(440, 15), str(discount_price), (255, 255, 255)
)
else:
await name_image.atext(
(440, 0), str(goods.goods_price), (255, 255, 255), center_type="by_height"
)
await name_image.atext( await name_image.atext(
( (
440 440
+ BuildImage(0, 0, plain_text=str(goods.goods_price), font_size=25).w, + BuildImage(0, 0, plain_text=str(goods.goods_price), font_size=25).w,
0, 0,
), ),
" 金币", f" 金币",
center_type="by_height", center_type="by_height",
) )
await name_image.acircle_corner(5) await name_image.acircle_corner(5)
@@ -92,15 +107,18 @@ async def create_shop_help() -> str:
await goods_image.atext((15, 50), f"简介:{goods.goods_description}") await goods_image.atext((15, 50), f"简介:{goods.goods_description}")
await goods_image.acircle_corner(20) await goods_image.acircle_corner(20)
await bk.apaste(goods_image, alpha=True) await bk.apaste(goods_image, alpha=True)
n = 0
_w = 550
# 添加限时图标和时间 # 添加限时图标和时间
if goods.goods_limit_time > 0: if goods.goods_limit_time > 0:
n += 140
_limit_time_logo = BuildImage( _limit_time_logo = BuildImage(
40, 40, background=f"{IMAGE_PATH}/other/time.png" 40, 40, background=f"{IMAGE_PATH}/other/time.png"
) )
await bk.apaste(_limit_time_logo, (600, 0), True) await bk.apaste(_limit_time_logo, (_w + 50, 0), True)
await bk.apaste( await bk.apaste(
BuildImage(0, 0, plain_text="限时!", font_size=23, font="CJGaoDeGuo.otf"), BuildImage(0, 0, plain_text="限时!", font_size=23, font="CJGaoDeGuo.otf"),
(640, 10), (_w + 90, 10),
True, True,
) )
limit_time = time.strftime( limit_time = time.strftime(
@@ -109,28 +127,66 @@ async def create_shop_help() -> str:
y_m_d = limit_time[0] y_m_d = limit_time[0]
_h_m = limit_time[1].split(":") _h_m = limit_time[1].split(":")
h_m = _h_m[0] + "时 " + _h_m[1] + "分" h_m = _h_m[0] + "时 " + _h_m[1] + "分"
await bk.atext((605, 38), str(y_m_d)) await bk.atext((_w + 55, 38), str(y_m_d))
await bk.atext((615, 57), str(h_m)) await bk.atext((_w + 65, 57), str(h_m))
await bk.aline((550, -1, 710, -1), "#a29ad6", 5) _w += 140
await bk.aline((550, 80, 710, 80), "#a29ad6", 5) if goods.goods_discount != 1:
n += 140
_discount_logo = BuildImage(30, 30, background=f"{IMAGE_PATH}/other/discount.png")
await bk.apaste(_discount_logo, (_w + 50, 10), True)
await bk.apaste(
BuildImage(0, 0, plain_text="折扣!", font_size=23, font="CJGaoDeGuo.otf"),
(_w + 90, 15),
True,
)
await bk.apaste(
BuildImage(0, 0, plain_text=f"{10 * goods.goods_discount:.1f} 折", font_size=30, font="CJGaoDeGuo.otf", font_color=(85, 156, 75)),
(_w + 50, 44),
True,
)
_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.apaste(_daily_limit_logo, (_w + 50, 10), True)
await bk.apaste(
BuildImage(0, 0, plain_text="限购!", font_size=23, font="CJGaoDeGuo.otf"),
(_w + 90, 20),
True,
)
await bk.apaste(
BuildImage(0, 0, plain_text=f"{goods.daily_limit}", font_size=30, font="CJGaoDeGuo.otf"),
(_w + 72, 45),
True,
)
if total_n < n:
total_n = n
if n:
await bk.aline((550, -1, 550 + n, -1), "#a29ad6", 5)
await bk.aline((550, 80, 550 + n, 80), "#a29ad6", 5)
# 添加限时图标和时间
idx += 1 idx += 1
await A.apaste(bk, (0, current_h), True) await A.apaste(bk, (0, current_h), True)
current_h += 90 current_h += 90
w = 1000 w = 850
if total_n:
w += total_n
h = A.h + 230 + 100 h = A.h + 230 + 100
h = 1000 if h < 1000 else h h = 1000 if h < 1000 else h
shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png") shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png")
shop = BuildImage(w, h, font_size=20, color="#f9f6f2") shop = BuildImage(w, h, font_size=20, color="#f9f6f2")
shop.paste(A, (20, 230)) zx_img = BuildImage(0, 0, background=f"{IMAGE_PATH}/zhenxun/toukan_3.png")
zx_img = BuildImage(0, 0, background=f"{IMAGE_PATH}/zhenxun/toukan.png") zx_img.transpose(Image.FLIP_LEFT_RIGHT)
zx_img.replace_color_tran(((240, 240, 240), (255, 255, 255)), (249, 246, 242)) zx_img.replace_color_tran(((240, 240, 240), (255, 255, 255)), (249, 246, 242))
await shop.apaste(zx_img, (780, 100)) await shop.apaste(zx_img, (0, 100))
shop.paste(A, (20 + zx_img.w, 230))
await shop.apaste(shop_logo, (450, 30), True) await shop.apaste(shop_logo, (450, 30), True)
shop.text( shop.text(
(int((1000 - shop.getsize("注【通过 序号 或者 商品名称 购买】")[0]) / 2), 170), (int((1000 - shop.getsize("注【通过 序号 或者 商品名称 购买】")[0]) / 2), 170),
"注【通过 序号 或者 商品名称 购买】", "注【通过 序号 或者 商品名称 购买】",
) )
shop.text((20, h - 100), "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~") shop.text((20 + zx_img.w, h - 100), "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~")
return shop.pic2bs4() return shop.pic2bs4()
@@ -140,6 +196,7 @@ async def register_goods(
des: str, des: str,
discount: Optional[float] = 1, discount: Optional[float] = 1,
limit_time: Optional[int] = 0, limit_time: Optional[int] = 0,
daily_limit: Optional[int] = 0,
) -> bool: ) -> bool:
""" """
添加商品 添加商品
@@ -151,6 +208,7 @@ async def register_goods(
:param des: 商品简介 :param des: 商品简介
:param discount: 商品折扣 :param discount: 商品折扣
:param limit_time: 商品限时销售时间,单位为小时 :param limit_time: 商品限时销售时间,单位为小时
:param daily_limit: 每日购买次数限制
:return: 是否添加成功 :return: 是否添加成功
""" """
if not await GoodsInfo.get_goods_info(name): if not await GoodsInfo.get_goods_info(name):
@@ -162,7 +220,7 @@ async def register_goods(
else 0 else 0
) )
return await GoodsInfo.add_goods( return await GoodsInfo.add_goods(
name, int(price), des, float(discount), limit_time name, int(price), des, float(discount), limit_time, daily_limit
) )
return False return False
@@ -192,7 +250,7 @@ async def delete_goods(name: str, id_: int) -> "str, str, int":
# 更新商品信息 # 更新商品信息
async def update_goods(**kwargs) -> "str, str, int": async def update_goods(**kwargs) -> Tuple[bool, str, str]:
""" """
更新商品信息 更新商品信息
:param kwargs: kwargs :param kwargs: kwargs
@@ -202,17 +260,18 @@ async def update_goods(**kwargs) -> "str, str, int":
goods_lst = await GoodsInfo.get_all_goods() goods_lst = await GoodsInfo.get_all_goods()
if is_number(kwargs["name"]): if is_number(kwargs["name"]):
if int(kwargs["name"]) < 1 or int(kwargs["name"]) > len(goods_lst): if int(kwargs["name"]) < 1 or int(kwargs["name"]) > len(goods_lst):
return "序号错误,没有该序号的商品...", "", 999 return False, "序号错误,没有该序号的商品...", ""
goods = goods_lst[int(kwargs["name"]) - 1] goods = goods_lst[int(kwargs["name"]) - 1]
else: else:
goods = await GoodsInfo.get_goods_info(kwargs["name"]) goods = await GoodsInfo.get_goods_info(kwargs["name"])
if not goods: if not goods:
return "名称错误,没有该名称的商品...", "", 999 return False, "名称错误,没有该名称的商品...", ""
name = goods.goods_name name: str = goods.goods_name
price = goods.goods_price price = goods.goods_price
des = goods.goods_description des = goods.goods_description
discount = goods.goods_discount discount = goods.goods_discount
limit_time = goods.goods_limit_time limit_time = goods.goods_limit_time
daily_limit = goods.daily_limit
new_time = 0 new_time = 0
tmp = "" tmp = ""
if kwargs.get("price"): if kwargs.get("price"):
@@ -229,24 +288,25 @@ async def update_goods(**kwargs) -> "str, str, int":
new_time = time.strftime( new_time = time.strftime(
"%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S",
time.localtime(time.time() + kwargs["limit_time"] * 60 * 60), time.localtime(time.time() + kwargs["limit_time"] * 60 * 60),
) ) if kwargs["limit_time"] != 0 else 0
tmp += f"限时至: {new_time}\n" tmp += f"限时至: {new_time}\n" if new_time else "取消了限时\n"
limit_time = kwargs["limit_time"] limit_time = kwargs["limit_time"]
return ( if kwargs.get("daily_limit"):
await GoodsInfo.update_goods( tmp += f'每日购买限制:{daily_limit} --> {kwargs["daily_limit"]}\n' if daily_limit else "取消了购买限制\n"
name, daily_limit = int(kwargs["daily_limit"])
int(price), await GoodsInfo.update_goods(
des,
float(discount),
int(
time.time() + limit_time * 60 * 60
if limit_time != 0 and new_time
else 0
),
),
name, name,
tmp[:-1], int(price),
des,
float(discount),
int(
time.time() + limit_time * 60 * 60
if limit_time != 0 and new_time
else 0
),
daily_limit
) )
return True, name, tmp[:-1],
def parse_goods_info(msg: str) -> Union[dict, str]: def parse_goods_info(msg: str) -> Union[dict, str]:
@@ -276,6 +336,10 @@ def parse_goods_info(msg: str) -> Union[dict, str]:
data["discount"] = sp[1] data["discount"] = sp[1]
elif sp[0] == "limit_time": elif sp[0] == "limit_time":
if not is_number(sp[1]) or float(sp[1]) < 0: if not is_number(sp[1]) or float(sp[1]) < 0:
return "limit_time参数不合法,必须大于0!" return "limit_time参数不合法,必须为数字且大于0!"
data["limit_time"] = sp[1] data["limit_time"] = sp[1]
elif sp[0] == "daily_limit":
if not is_number(sp[1]) or float(sp[1]) < 0:
return "daily_limit参数不合法,必须为数字且大于0!"
data["daily_limit"] = sp[1]
return data return data
+5
View File
@@ -2,6 +2,10 @@ from typing import Optional
from pathlib import Path from pathlib import Path
from .utils import ConfigsManager from .utils import ConfigsManager
import platform
if platform.system() == "Linux":
import os
hostip = os.popen("cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'").read().replace("\n","")
# 回复消息名称 # 回复消息名称
NICKNAME: str = "小真寻" NICKNAME: str = "小真寻"
@@ -18,6 +22,7 @@ port: str = "" # 数据库端口
database: str = "" # 数据库名称 database: str = "" # 数据库名称
# 代理,例如 "http://127.0.0.1:7890" # 代理,例如 "http://127.0.0.1:7890"
# 如果是WLS 可以 f"http://{hostip}:7890" 使用寄主机的代理
SYSTEM_PROXY: Optional[str] = None # 全局代理 SYSTEM_PROXY: Optional[str] = None # 全局代理
+1 -1
View File
@@ -209,7 +209,7 @@ class BagUser(db.Model):
return True return True
except Exception as e: except Exception as e:
logger.error(f"buy_property 发生错误 {type(e)}:{e}") logger.error(f"buy_property 发生错误 {type(e)}:{e}")
return False return False
@classmethod @classmethod
async def get_all_users(cls, group_id: Optional[int] = None) -> List["BagUser"]: async def get_all_users(cls, group_id: Optional[int] = None) -> List["BagUser"]:
+93 -26
View File
@@ -1,5 +1,5 @@
from services.db_context import db from services.db_context import db
from typing import Optional, List from typing import Optional, List, Tuple
from services.log import logger from services.log import logger
@@ -12,17 +12,22 @@ class GoodsInfo(db.Model):
goods_description = db.Column(db.TEXT(), nullable=False) # 商品描述 goods_description = db.Column(db.TEXT(), nullable=False) # 商品描述
goods_discount = db.Column(db.Numeric(scale=3, asdecimal=False), default=1) # 打折 goods_discount = db.Column(db.Numeric(scale=3, asdecimal=False), default=1) # 打折
goods_limit_time = db.Column(db.BigInteger(), default=0) # 限时 goods_limit_time = db.Column(db.BigInteger(), default=0) # 限时
daily_limit = db.Column(db.Integer(), nullable=False, default=0) # 每日购买限制
daily_purchase_limit = db.Column(
db.JSON(), nullable=False, default={}
) # 每日购买限制数据存储
_idx1 = db.Index("goods_group_users_idx1", "goods_name", unique=True) _idx1 = db.Index("goods_group_users_idx1", "goods_name", unique=True)
@classmethod @classmethod
async def add_goods( async def add_goods(
cls, cls,
goods_name: str, goods_name: str,
goods_price: int, goods_price: int,
goods_description: str, goods_description: str,
goods_discount: float = 1, goods_discount: float = 1,
goods_limit_time: int = 0, goods_limit_time: int = 0,
daily_limit: int = 0,
) -> bool: ) -> bool:
""" """
说明: 说明:
@@ -33,6 +38,7 @@ class GoodsInfo(db.Model):
:param goods_description: 商品简介 :param goods_description: 商品简介
:param goods_discount: 商品折扣 :param goods_discount: 商品折扣
:param goods_limit_time: 商品限时 :param goods_limit_time: 商品限时
:param daily_limit: 每日购买限制
""" """
try: try:
if not await cls.get_goods_info(goods_name): if not await cls.get_goods_info(goods_name):
@@ -42,6 +48,7 @@ class GoodsInfo(db.Model):
goods_description=goods_description, goods_description=goods_description,
goods_discount=goods_discount, goods_discount=goods_discount,
goods_limit_time=goods_limit_time, goods_limit_time=goods_limit_time,
daily_limit=daily_limit,
) )
return True return True
except Exception as e: except Exception as e:
@@ -58,8 +65,8 @@ class GoodsInfo(db.Model):
""" """
query = ( query = (
await cls.query.where(cls.goods_name == goods_name) await cls.query.where(cls.goods_name == goods_name)
.with_for_update() .with_for_update()
.gino.first() .gino.first()
) )
if not query: if not query:
return False return False
@@ -68,12 +75,13 @@ class GoodsInfo(db.Model):
@classmethod @classmethod
async def update_goods( async def update_goods(
cls, cls,
goods_name: str, goods_name: str,
goods_price: Optional[int] = None, goods_price: Optional[int] = None,
goods_description: Optional[str] = None, goods_description: Optional[str] = None,
goods_discount: Optional[float] = None, goods_discount: Optional[float] = None,
goods_limit_time: Optional[int] = None, goods_limit_time: Optional[int] = None,
daily_limit: Optional[int] = None
) -> bool: ) -> bool:
""" """
说明: 说明:
@@ -84,27 +92,27 @@ class GoodsInfo(db.Model):
:param goods_description: 商品简介 :param goods_description: 商品简介
:param goods_discount: 商品折扣 :param goods_discount: 商品折扣
:param goods_limit_time: 商品限时时间 :param goods_limit_time: 商品限时时间
:param daily_limit: 每日次数限制
""" """
try: try:
query = ( query = (
await cls.query.where(cls.goods_name == goods_name) await cls.query.where(cls.goods_name == goods_name)
.with_for_update() .with_for_update()
.gino.first() .gino.first()
) )
if not query: if not query:
return False return False
if goods_price: await query.update(
await query.update(goods_price=goods_price).apply() goods_price=goods_price or query.goods_price,
if goods_description: goods_description=goods_description or query.goods_description,
await query.update(goods_description=goods_description).apply() goods_discount=goods_discount or query.goods_discount,
if goods_discount: goods_limit_time=goods_limit_time if goods_limit_time is not None else query.goods_limit_time,
await query.update(goods_discount=goods_discount).apply() daily_limit=daily_limit if daily_limit is not None else query.daily_limit,
if goods_limit_time: ).apply()
await query.update(goods_limit_time=goods_limit_time).apply()
return True return True
except Exception as e: except Exception as e:
logger.error(f"GoodsInfo update_goods 发生错误 {type(e)}:{e}") logger.error(f"GoodsInfo update_goods 发生错误 {type(e)}:{e}")
return False return False
@classmethod @classmethod
async def get_goods_info(cls, goods_name: str) -> "GoodsInfo": async def get_goods_info(cls, goods_name: str) -> "GoodsInfo":
@@ -130,3 +138,62 @@ class GoodsInfo(db.Model):
goods_lst.append([x for x in query if x.id == min_id][0]) goods_lst.append([x for x in query if x.id == min_id][0])
id_lst.remove(min_id) id_lst.remove(min_id)
return goods_lst return goods_lst
@classmethod
async def add_user_daily_purchase(
cls, goods: "GoodsInfo", user_id: int, group_id: int, num: int = 1
):
"""
说明:
添加用户明日购买限制
参数:
:param goods: 商品
:param user_id: 用户id
:param group_id: 群号
:param num: 数量
"""
user_id = str(user_id)
group_id = str(group_id)
if goods and goods.daily_limit and goods.daily_limit > 0:
if not goods.daily_purchase_limit.get(group_id):
goods.daily_purchase_limit[group_id] = {}
if not goods.daily_purchase_limit[group_id].get(user_id):
goods.daily_purchase_limit[group_id][user_id] = 0
goods.daily_purchase_limit[group_id][user_id] += num
await goods.update(daily_purchase_limit=goods.daily_purchase_limit).apply()
@classmethod
async def check_user_daily_purchase(
cls, goods: "GoodsInfo", user_id: int, group_id: int, num: int = 1
) -> Tuple[bool, int]:
"""
说明:
检测用户每日购买上限
参数:
:param goods: 商品
:param user_id: 用户id
:param group_id: 群号
:param num: 数量
"""
user_id = str(user_id)
group_id = str(group_id)
if goods:
if (
not goods.daily_limit
or not goods.daily_purchase_limit.get(group_id)
or not goods.daily_purchase_limit[group_id].get(user_id)
):
return goods.daily_limit - num < 0, goods.daily_limit
if goods.daily_purchase_limit[group_id][user_id] + num > goods.daily_limit:
return (
True,
goods.daily_limit - goods.daily_purchase_limit[group_id][user_id],
)
return False, 0
@classmethod
async def reset_daily_purchase(cls):
"""
重置每次次数限制
"""
await cls.update.values(daily_purchase_limit={}).gino.status()
+25 -7
View File
@@ -1,4 +1,6 @@
from bilireq.exceptions import ResponseCodeError from bilireq.exceptions import ResponseCodeError
from nonebot.adapters.onebot.v11 import MessageSegment
from utils.manager import resources_manager from utils.manager import resources_manager
from asyncio.exceptions import TimeoutError from asyncio.exceptions import TimeoutError
from .model import BilibiliSub from .model import BilibiliSub
@@ -8,7 +10,7 @@ from utils.message_builder import image
from bilireq.user import get_user_info from bilireq.user import get_user_info
from bilireq import dynamic from bilireq import dynamic
from .utils import get_videos from .utils import get_videos
from typing import Optional from typing import Optional, Tuple
from configs.path_config import IMAGE_PATH from configs.path_config import IMAGE_PATH
from datetime import datetime from datetime import datetime
from utils.browser import get_browser from utils.browser import get_browser
@@ -304,7 +306,7 @@ async def _get_season_status(id_) -> Optional[str]:
async def get_user_dynamic( async def get_user_dynamic(
uid: int, local_user: BilibiliSub uid: int, local_user: BilibiliSub
) -> "Optional[MessageSegment], int": ) -> Tuple[Optional[MessageSegment], int]:
""" """
获取用户动态 获取用户动态
:param uid: 用户uid :param uid: 用户uid
@@ -317,14 +319,15 @@ async def get_user_dynamic(
if dynamic_info.get("cards") and browser: if dynamic_info.get("cards") and browser:
dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"]
if local_user.dynamic_upload_time < dynamic_upload_time: if local_user.dynamic_upload_time < dynamic_upload_time:
page = await browser.new_page() context = await browser.new_context()
page = await context.new_page()
try: try:
await page.goto( await page.goto(
f"https://space.bilibili.com/{local_user.uid}/dynamic", f"https://space.bilibili.com/{local_user.uid}/dynamic",
wait_until="networkidle", wait_until="networkidle",
timeout=10000, timeout=10000,
) )
await page.set_viewport_size({"width": 2560, "height": 1080, "timeout": 10000*20}) # timeout: 200s # await page.set_viewport_size({"width": 2560, "height": 1080, "timeout": 10000*20}) # timeout: 200s
# 删除置顶 # 删除置顶
await page.evaluate( await page.evaluate(
""" """
@@ -334,15 +337,30 @@ async def get_user_dynamic(
} }
""" """
) )
card = page.locator(".bili-dyn-list__item").first async with page.expect_popup() as popup_info:
await page.locator(".bili-rich-text__content").click()
details_page = await popup_info.value
await details_page.set_viewport_size(
{"width": 2560, "height": 1080, "timeout": 10000 * 20}
)
await details_page.wait_for_selector(".panel-area")
await details_page.evaluate(
"""
xs = document.getElementById('internationalHeader');
xs.remove();
xs = document.getElementsByClassName('panel-area')
xs[0].remove();
"""
)
card = details_page.locator(".detail-card")
await card.wait_for() await card.wait_for()
# 截图并保存
await card.screenshot( await card.screenshot(
path=dynamic_path / f"{local_user.sub_id}_{dynamic_upload_time}.jpg", path=dynamic_path / f"{local_user.sub_id}_{dynamic_upload_time}.jpg",
) )
except Exception as e: except Exception as e:
logger.error(f"B站订阅:获取用户动态 发送错误 {type(e)}:{e}") logger.error(f"B站订阅:获取用户动态 发送错误 {type(e)}:{e}")
finally: finally:
await context.close()
await page.close() await page.close()
return ( return (
image( image(
@@ -351,7 +369,7 @@ async def get_user_dynamic(
), ),
dynamic_upload_time, dynamic_upload_time,
) )
return None, None return None, 0
class SubManager: class SubManager:
+1 -1
View File
@@ -9,7 +9,7 @@ import platform
# asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
url = "http://www.eclzz.one/" url = "http://www.eclzz.zone/"
async def get_bt_info(keyword: str, page: int): async def get_bt_info(keyword: str, page: int):
@@ -65,8 +65,8 @@ async def arg_handle(
if int(img_id) > max_id or int(img_id) < 0: if int(img_id) > max_id or int(img_id) < 0:
await delete_img.finish(f"Id超过上下限,上限:{max_id}", at_sender=True) await delete_img.finish(f"Id超过上下限,上限:{max_id}", at_sender=True)
try: try:
if (TEMP_PATH / "delete.jpg").exists(): if (TEMP_PATH / f"{event.user_id}_delete.jpg").exists():
(TEMP_PATH / "delete.jpg").unlink() (TEMP_PATH / f"{event.user_id}_delete.jpg").unlink()
logger.info(f"删除{cn2py(state['path'])}图片 {img_id}.jpg 成功") logger.info(f"删除{cn2py(state['path'])}图片 {img_id}.jpg 成功")
except Exception as e: except Exception as e:
logger.warning(f"删除图片 delete.jpg 失败 e{e}") logger.warning(f"删除图片 delete.jpg 失败 e{e}")
+164 -165
View File
@@ -1,165 +1,164 @@
# from nonebot import on_message from nonebot import on_message
# from services.log import logger from services.log import logger
# from nonebot.adapters.onebot.v11 import GroupMessageEvent, ActionFailed from nonebot.adapters.onebot.v11 import GroupMessageEvent, ActionFailed
# from utils.utils import get_message_json, get_local_proxy, is_number, get_message_text from utils.utils import get_message_json, get_local_proxy, is_number, get_message_text
# from nonebot.adapters.onebot.v11.permission import GROUP from nonebot.adapters.onebot.v11.permission import GROUP
# from utils.message_builder import image from utils.message_builder import image
# from utils.image_utils import BuildImage from utils.image_utils import BuildImage
# from utils.browser import get_browser from utils.browser import get_browser
# from configs.path_config import IMAGE_PATH from configs.path_config import IMAGE_PATH
# from utils.http_utils import AsyncHttpx from utils.http_utils import AsyncHttpx
# from configs.config import Config from configs.config import Config
# from utils.user_agent import get_user_agent from utils.user_agent import get_user_agent
# import aiohttp import aiohttp
# import asyncio import asyncio
# import time import time
# import ujson as json import ujson as json
# from bilireq import video
# __zx_plugin_name__ = "B站转发解析"
# __zx_plugin_name__ = "B站转发解析" __plugin_usage__ = """
# __plugin_usage__ = """ usage:
# usage: B站转发解析,解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv,且5分钟内不解析相同url
# B站转发解析,解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv,且5分钟内不解析相同url """.strip()
# """.strip() __plugin_des__ = "B站转发解析"
# __plugin_des__ = "B站转发解析" __plugin_type__ = ("其他",)
# __plugin_type__ = ("其他",) __plugin_version__ = 0.1
# __plugin_version__ = 0.1 __plugin_author__ = "HibiKier"
# __plugin_author__ = "HibiKier" __plugin_task__ = {"bilibili_parse": "b站转发解析"}
# __plugin_task__ = {"bilibili_parse": "b站转发解析"} Config.add_plugin_config(
# Config.add_plugin_config( "_task",
# "_task", "DEFAULT_BILIBILI_PARSE",
# "DEFAULT_BILIBILI_PARSE", True,
# True, help_="被动 B站转发解析 进群默认开关状态",
# help_="被动 B站转发解析 进群默认开关状态", default_value=True,
# default_value=True, )
# )
#
# parse_bilibili_json = on_message(priority=1, permission=GROUP, block=False)
# parse_bilibili_json = on_message(priority=1, permission=GROUP, block=False)
# _tmp = {}
# _tmp = {}
#
# @parse_bilibili_json.handle()
# @parse_bilibili_json.handle() async def _(event: GroupMessageEvent):
# async def _(event: GroupMessageEvent): vd_info = None
# vd_info = None url = None
# url = None if get_message_json(event.json()):
# if get_message_json(event.json()): try:
# try: data = json.loads(get_message_json(event.json())[0]["data"])
# data = json.loads(get_message_json(event.json())[0]["data"]) except (IndexError, KeyError):
# except (IndexError, KeyError): data = None
# data = None if data:
# if data: # 转发视频
# # 转发视频 if data.get("desc") == "哔哩哔哩":
# if data.get("desc") == "哔哩哔哩": async with aiohttp.ClientSession(
# async with aiohttp.ClientSession( headers=get_user_agent()
# headers=get_user_agent() ) as session:
# ) as session: async with session.get(
# async with session.get( data["meta"]["detail_1"]["qqdocurl"],
# data["meta"]["detail_1"]["qqdocurl"], proxy=get_local_proxy(),
# proxy=get_local_proxy(), timeout=7,
# timeout=7, ) as response:
# ) as response: url = str(response.url).split("?")[0]
# url = str(response.url).split("?")[0] bvid = url.split("/")[-1]
# bvid = url.split("/")[-1] vd_info = await video.get_video_base_info(bvid)
# vd_info = await video.Video(bvid=bvid).get_info() # response = await AsyncHttpx.get(
# # response = await AsyncHttpx.get( # data["meta"]["detail_1"]["qqdocurl"], timeout=7
# # data["meta"]["detail_1"]["qqdocurl"], timeout=7 # )
# # ) # url = str(response.url).split("?")[0]
# # url = str(response.url).split("?")[0] # bvid = url.split("/")[-1]
# # bvid = url.split("/")[-1] # vd_info = await video.Video(bvid=bvid).get_info()
# # vd_info = await video.Video(bvid=bvid).get_info() # 转发专栏
# # 转发专栏 if (
# if ( data.get("meta")
# data.get("meta") and data["meta"].get("news")
# and data["meta"].get("news") and data["meta"]["news"].get("desc") == "哔哩哔哩专栏"
# and data["meta"]["news"].get("desc") == "哔哩哔哩专栏" ):
# ): url = data["meta"]["news"]["jumpUrl"]
# url = data["meta"]["news"]["jumpUrl"] page = None
# page = None try:
# try: browser = await get_browser()
# browser = await get_browser() if not browser:
# if not browser: return
# return page = await browser.new_page(
# page = await browser.new_page( user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
# user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" " (KHTML, like Gecko) Chrome/93.0.4530.0 Safari/537.36"
# " (KHTML, like Gecko) Chrome/93.0.4530.0 Safari/537.36" )
# ) await page.goto(url, wait_until="networkidle", timeout=10000)
# await page.goto(url, wait_until="networkidle", timeout=10000) await page.set_viewport_size({"width": 2560, "height": 1080})
# await page.set_viewport_size({"width": 2560, "height": 1080}) await page.click("#app > div")
# await page.click("#app > div") div = await page.query_selector("#app > div")
# div = await page.query_selector("#app > div") await div.screenshot(
# await div.screenshot( path=f"{IMAGE_PATH}/temp/cv_{event.user_id}.png",
# path=f"{IMAGE_PATH}/temp/cv_{event.user_id}.png", timeout=100000,
# timeout=100000, )
# ) await asyncio.get_event_loop().run_in_executor(
# await asyncio.get_event_loop().run_in_executor( None, resize, f"{IMAGE_PATH}/temp/cv_{event.user_id}.png"
# None, resize, f"{IMAGE_PATH}/temp/cv_{event.user_id}.png" )
# ) await parse_bilibili_json.send(
# await parse_bilibili_json.send( "[[_task|bilibili_parse]]" + image(f"cv_{event.user_id}.png", "temp")
# "[[_task|bilibili_parse]]" + image(f"cv_{event.user_id}.png", "temp") )
# ) await page.close()
# await page.close() logger.info(
# logger.info( f"USER {event.user_id} GROUP {event.group_id} 解析bilibili转发 {url}"
# f"USER {event.user_id} GROUP {event.group_id} 解析bilibili转发 {url}" )
# ) except Exception as e:
# except Exception as e: logger.error(f"尝试解析bilibili专栏 {url} 失败 {type(e)}:{e}")
# logger.error(f"尝试解析bilibili专栏 {url} 失败 {type(e)}:{e}") if page:
# if page: await page.close()
# await page.close() return
# return # BV
# # BV if msg := get_message_text(event.json()):
# if msg := get_message_text(event.json()): if "BV" in msg:
# if "BV" in msg: index = msg.find("BV")
# index = msg.find("BV") if len(msg[index + 2 :]) >= 10:
# if len(msg[index + 2 :]) >= 10: msg = msg[index : index + 12]
# msg = msg[index : index + 12] url = f"https://www.bilibili.com/video/{msg}"
# url = f"https://www.bilibili.com/video/{msg}" vd_info = await video.get_video_base_info(msg)
# vd_info = await video.Video(bvid=msg).get_info() elif "av" in msg:
# elif "av" in msg: index = msg.find("av")
# index = msg.find("av") if len(msg[index + 2 :]) >= 9:
# if len(msg[index + 2 :]) >= 9: msg = msg[index + 2 : index + 11]
# msg = msg[index + 2 : index + 11] if is_number(msg):
# if is_number(msg): url = f"https://www.bilibili.com/video/{msg}"
# url = f"https://www.bilibili.com/video/{msg}" vd_info = await video.get_video_base_info(msg)
# vd_info = await video.Video(aid=int(msg)).get_info() elif "https://b23.tv" in msg:
# elif "https://b23.tv" in msg: url = "https://" + msg[msg.find("b23.tv") : msg.find("b23.tv") + 13]
# url = "https://" + msg[msg.find("b23.tv") : msg.find("b23.tv") + 13] res = await AsyncHttpx.get(url, timeout=7)
# res = await AsyncHttpx.get(url, timeout=7) url = str(res.url).split("?")[0]
# url = str(res.url).split("?")[0] bvid = url.split("/")[-1]
# bvid = url.split("/")[-1] vd_info = await video.get_video_base_info(bvid)
# vd_info = await video.Video(bvid=bvid).get_info() if vd_info:
# if vd_info: if (
# if ( url in _tmp.keys() and time.time() - _tmp[url] > 30
# url in _tmp.keys() and time.time() - _tmp[url] > 30 ) or url not in _tmp.keys():
# ) or url not in _tmp.keys(): _tmp[url] = time.time()
# _tmp[url] = time.time() aid = vd_info["aid"]
# aid = vd_info["aid"] title = vd_info["title"]
# title = vd_info["title"] author = vd_info["owner"]["name"]
# author = vd_info["owner"]["name"] reply = vd_info["stat"]["reply"] # 回复
# reply = vd_info["stat"]["reply"] # 回复 favorite = vd_info["stat"]["favorite"] # 收藏
# favorite = vd_info["stat"]["favorite"] # 收藏 coin = vd_info["stat"]["coin"] # 投币
# coin = vd_info["stat"]["coin"] # 投币 # like = vd_info['stat']['like'] # 点赞
# # like = vd_info['stat']['like'] # 点赞 # danmu = vd_info['stat']['danmaku'] # 弹幕
# # danmu = vd_info['stat']['danmaku'] # 弹幕 date = time.strftime("%Y-%m-%d", time.localtime(vd_info["ctime"]))
# date = time.strftime("%Y-%m-%d", time.localtime(vd_info["ctime"])) try:
# try: await parse_bilibili_json.send(
# await parse_bilibili_json.send( "[[_task|bilibili_parse]]" +
# "[[_task|bilibili_parse]]" + image(vd_info["pic"]) + f"\nav{aid}\n标题:{title}\n"
# image(vd_info["pic"]) + f"\nav{aid}\n标题:{title}\n" f"UP:{author}\n"
# f"UP:{author}\n" f"上传日期:{date}\n"
# f"上传日期:{date}\n" f"回复:{reply},收藏:{favorite},投币:{coin}\n"
# f"回复:{reply},收藏:{favorite},投币:{coin}\n" f"{url}"
# f"{url}" )
# ) except ActionFailed:
# except ActionFailed: logger.warning(f"{event.group_id} 发送bilibili解析失败")
# logger.warning(f"{event.group_id} 发送bilibili解析失败") else:
# else: logger.info(
# logger.info( f"USER {event.user_id} GROUP {event.group_id} 解析bilibili转发 {url}"
# f"USER {event.user_id} GROUP {event.group_id} 解析bilibili转发 {url}" )
# )
#
# def resize(path: str):
# def resize(path: str): A = BuildImage(0, 0, background=path, ratio=0.5)
# A = BuildImage(0, 0, background=path, ratio=0.5) A.save(path)
# A.save(path)
+1
View File
@@ -132,6 +132,7 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()):
all_image = await OmegaPixivIllusts.query_images( all_image = await OmegaPixivIllusts.query_images(
pid=int(pid), nsfw_tag=nsfw_tag pid=int(pid), nsfw_tag=nsfw_tag
) )
num = len(all_image)
else: else:
tmp = await Pixiv.query_images( tmp = await Pixiv.query_images(
x, r18=1 if nsfw_tag == 2 else 0, num=pix_num x, r18=1 if nsfw_tag == 2 else 0, num=pix_num
+51 -23
View File
@@ -6,9 +6,10 @@ from nonebot.message import run_postprocessor
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
from typing import Optional, Type, Any from typing import Optional, Type, Any
from gino.exceptions import UninitializedError from gino.exceptions import UninitializedError
from utils.message_builder import custom_forward_msg
from utils.utils import ( from utils.utils import (
is_number, is_number,
get_message_img,
) )
from nonebot.typing import T_State from nonebot.typing import T_State
from nonebot.adapters.onebot.v11 import ( from nonebot.adapters.onebot.v11 import (
@@ -98,6 +99,11 @@ __plugin_configs__ = {
"TIMEOUT": {"value": 10, "help": "色图下载超时限制(秒)", "default_value": 10}, "TIMEOUT": {"value": 10, "help": "色图下载超时限制(秒)", "default_value": 10},
"SHOW_INFO": {"value": True, "help": "是否显示色图的基本信息,如PID等", "default_value": True}, "SHOW_INFO": {"value": True, "help": "是否显示色图的基本信息,如PID等", "default_value": True},
"ALLOW_GROUP_R18": {"value": False, "help": "在群聊中启用R18权限", "default_value": False}, "ALLOW_GROUP_R18": {"value": False, "help": "在群聊中启用R18权限", "default_value": False},
"MAX_ONCE_NUM2FORWARD": {
"value": None,
"help": "单次发送的图片数量达到指定值时转发为合并消息",
"default_value": None,
},
} }
Config.add_plugin_config("pixiv", "PIXIV_NGINX_URL", "i.pixiv.re", help_="Pixiv反向代理") Config.add_plugin_config("pixiv", "PIXIV_NGINX_URL", "i.pixiv.re", help_="Pixiv反向代理")
@@ -133,7 +139,10 @@ setu_reg = on_regex("(.*)[份|发|张|个|次|点](.*)[瑟|色|涩]图$", priori
@setu.handle() @setu.handle()
async def _( async def _(
event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg() bot: Bot,
event: MessageEvent,
cmd: Tuple[str, ...] = Command(),
arg: Message = CommandArg(),
): ):
msg = arg.extract_plain_text().strip() msg = arg.extract_plain_text().strip()
if isinstance(event, GroupMessageEvent): if isinstance(event, GroupMessageEvent):
@@ -149,10 +158,7 @@ async def _(
if cmd[0] == "色图r" and isinstance(event, PrivateMessageEvent): if cmd[0] == "色图r" and isinstance(event, PrivateMessageEvent):
r18 = 1 r18 = 1
num = 10 num = 10
elif ( elif cmd[0] == "色图r" and isinstance(event, GroupMessageEvent):
cmd[0] == "色图r"
and isinstance(event, GroupMessageEvent)
):
if not Config.get_config("send_setu", "ALLOW_GROUP_R18"): if not Config.get_config("send_setu", "ALLOW_GROUP_R18"):
await setu.finish( await setu.finish(
random.choice(["这种不好意思的东西怎么可能给这么多人看啦", "羞羞脸!给我滚出克私聊!", "变态变态变态变态大变态!"]) random.choice(["这种不好意思的东西怎么可能给这么多人看啦", "羞羞脸!给我滚出克私聊!", "变态变态变态变态大变态!"])
@@ -178,7 +184,7 @@ async def _(
Config.get_config("send_setu", "WITHDRAW_SETU_MESSAGE"), Config.get_config("send_setu", "WITHDRAW_SETU_MESSAGE"),
) )
return return
await send_setu_handle(setu, event, cmd[0], msg, num, r18) await send_setu_handle(bot, setu, event, cmd[0], msg, num, r18)
num_key = { num_key = {
@@ -197,7 +203,7 @@ num_key = {
@setu_reg.handle() @setu_reg.handle()
async def _(event: MessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()): async def _(bot: Bot, event: MessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
if isinstance(event, GroupMessageEvent): if isinstance(event, GroupMessageEvent):
impression = ( impression = (
await SignGroupUser.ensure(event.user_id, event.group_id) await SignGroupUser.ensure(event.user_id, event.group_id)
@@ -214,10 +220,11 @@ async def _(event: MessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
num = int(num) num = int(num)
except ValueError: except ValueError:
num = 1 num = 1
await send_setu_handle(setu_reg, event, "色图", tags, num, 0) await send_setu_handle(bot, setu_reg, event, "色图", tags, num, 0)
async def send_setu_handle( async def send_setu_handle(
bot: Bot,
matcher: Type[Matcher], matcher: Type[Matcher],
event: MessageEvent, event: MessageEvent,
command: str, command: str,
@@ -300,27 +307,48 @@ async def send_setu_handle(
if code != 200: if code != 200:
await matcher.finish(setu_list[0], at_sender=True) await matcher.finish(setu_list[0], at_sender=True)
# 开始发图 # 开始发图
forward_list = []
for _ in range(num): for _ in range(num):
if not setu_list: if not setu_list:
await setu.finish("坏了,已经没图了,被榨干了!") await setu.finish("坏了,已经没图了,被榨干了!")
setu_image = random.choice(setu_list) setu_image = random.choice(setu_list)
setu_list.remove(setu_image) setu_list.remove(setu_image)
try: if (
msg_id = await matcher.send( Config.get_config("send_setu", "MAX_ONCE_NUM2FORWARD")
and num >= Config.get_config("send_setu", "MAX_ONCE_NUM2FORWARD")
and isinstance(event, GroupMessageEvent)
):
forward_list.append(
Message( Message(
gen_message(setu_image) gen_message(setu_image)
+ (await check_local_exists_or_download(setu_image))[0] + (await check_local_exists_or_download(setu_image))[0]
) )
) )
withdraw_message_manager.withdraw_message( else:
event, try:
msg_id["message_id"], msg_id = await matcher.send(
Config.get_config("send_setu", "WITHDRAW_SETU_MESSAGE"), Message(
) gen_message(setu_image)
logger.info( + (await check_local_exists_or_download(setu_image))[0]
f"(USER {event.user_id}, GROUP " )
f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" )
f" 发送本地色图 {setu_image.local_id}.png" withdraw_message_manager.withdraw_message(
) event,
except ActionFailed: msg_id["message_id"],
await matcher.finish("坏了,这张图色过头了,我自己看看就行了!", at_sender=True) Config.get_config("send_setu", "WITHDRAW_SETU_MESSAGE"),
)
logger.info(
f"(USER {event.user_id}, GROUP "
f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f" 发送本地色图 {setu_image.local_id}.png"
)
except ActionFailed:
await matcher.finish("坏了,这张图色过头了,我自己看看就行了!", at_sender=True)
if forward_list:
msg_id = await bot.send_group_forward_msg(
group_id=event.group_id,
messages=custom_forward_msg(forward_list, bot.self_id),
)
withdraw_message_manager.withdraw_message(
event, msg_id, Config.get_config("send_setu", "WITHDRAW_SETU_MESSAGE")
)
+1 -1
View File
@@ -32,7 +32,7 @@ async def get_setu_urls(
params = { params = {
"r18": r18, # 添加r18参数 0为否,1为是,2为混合 "r18": r18, # 添加r18参数 0为否,1为是,2为混合
"tag": tags, # 若指定tag "tag": tags, # 若指定tag
"num": 100, # 一次返回的结果数量 "num": 20, # 一次返回的结果数量
"size": ["original"], "size": ["original"],
} }
for count in range(3): for count in range(3):
+6
View File
@@ -21,10 +21,16 @@ gConfig.add_plugin_config("web-ui", "password", None, name="web-ui", help_="前
# 先使用hook修复 # 先使用hook修复
@run_preprocessor @run_preprocessor
async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State): async def _(matcher: Matcher, bot: Bot, event: MessageEvent, state: T_State):
flag = False
for module in plugins2settings_manager.keys(): for module in plugins2settings_manager.keys():
if isinstance(plugins2settings_manager.get_plugin_data(module).get("cmd"), str): if isinstance(plugins2settings_manager.get_plugin_data(module).get("cmd"), str):
plugins2settings_manager.set_module_data( plugins2settings_manager.set_module_data(
module, module,
"cmd", "cmd",
plugins2settings_manager.get_plugin_data(module).get("cmd").split(","), plugins2settings_manager.get_plugin_data(module).get("cmd").split(","),
False
) )
flag = True
if flag:
plugins2settings_manager.save()
Generated
+276 -646
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -19,7 +19,7 @@ aiohttp = "3.7.4.post0"
beautifulsoup4 = "4.9.3" beautifulsoup4 = "4.9.3"
feedparser = "^6.0.8" feedparser = "^6.0.8"
gino = "^1.0.1" gino = "^1.0.1"
httpx = "^0.22.0" httpx = "^0.23.0"
ImageHash = "^4.2.1" ImageHash = "^4.2.1"
jieba = "^0.42.1" jieba = "^0.42.1"
lxml = "4.6.5" lxml = "4.6.5"
@@ -39,7 +39,7 @@ dateparser = "^1.1.0"
cn2an = "^0.5.16" cn2an = "^0.5.16"
python-jose = "^3.3.0" python-jose = "^3.3.0"
python-multipart = "^0.0.5" python-multipart = "^0.0.5"
bilireq = "^0.1.2" bilireq = "^0.2.0"
emoji = "^1.7.0" emoji = "^1.7.0"
wordcloud = "^1.8.1" wordcloud = "^1.8.1"
rich = "^12.4.3" rich = "^12.4.3"
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

+2 -1
View File
@@ -10,7 +10,8 @@
"poetry.lock", "poetry.lock",
"pyproject.toml", "pyproject.toml",
"resources/font", "resources/font",
"bot.py" "resources/image/zhenxun",
"resources/image/other"
], ],
"add_file": [], "add_file": [],
"delete_file": [] "delete_file": []
+2 -7
View File
@@ -2,14 +2,9 @@ import asyncio
from typing import Optional from typing import Optional
from nonebot.log import logger from nonebot.log import logger
from playwright.async_api import Browser, async_playwright from playwright.async_api import Browser, async_playwright
import nonebot
from nonebot import Driver
from services.log import logger from services.log import logger
driver: Driver = nonebot.get_driver()
_browser: Optional[Browser] = None _browser: Optional[Browser] = None
@@ -20,8 +15,8 @@ async def init(**kwargs) -> Optional[Browser]:
_browser = await browser.chromium.launch(**kwargs) _browser = await browser.chromium.launch(**kwargs)
return _browser return _browser
except Exception as e: except Exception as e:
logger.warning(f"启动chromium发生错误 {type(e)}:{e}") # logger.warning(f"启动chromium发生错误 {type(e)}:{e}")
asyncio.get_event_loop().run_in_executor(None, install) await asyncio.get_event_loop().run_in_executor(None, install)
_browser = await browser.chromium.launch(**kwargs) _browser = await browser.chromium.launch(**kwargs)
return None return None
+30 -4
View File
@@ -20,12 +20,15 @@ class ShopRegister(dict):
discount: Tuple[float, ...], discount: Tuple[float, ...],
limit_time: Tuple[int, ...], limit_time: Tuple[int, ...],
load_status: Tuple[bool, ...], load_status: Tuple[bool, ...],
daily_limit: Tuple[int, ...],
**kwargs, **kwargs,
): ):
def add_register_item(func: Callable): def add_register_item(func: Callable):
if name in self._data.keys(): if name in self._data.keys():
raise ValueError("该商品已注册,请替换其他名称!") raise ValueError("该商品已注册,请替换其他名称!")
for n, p, d, dd, l, s in zip(name, price, des, discount, limit_time, load_status): for n, p, d, dd, l, s, dl in zip(
name, price, des, discount, limit_time, load_status, daily_limit
):
if s: if s:
_temp_kwargs = {} _temp_kwargs = {}
for key, value in kwargs.items(): for key, value in kwargs.items():
@@ -38,6 +41,7 @@ class ShopRegister(dict):
"des": d, "des": d,
"discount": dd, "discount": dd,
"limit_time": l, "limit_time": l,
"daily_limit": dl,
"func": func, "func": func,
"kwargs": _temp_kwargs, "kwargs": _temp_kwargs,
} }
@@ -52,7 +56,12 @@ class ShopRegister(dict):
self._flag = False self._flag = False
for name in self._data.keys(): for name in self._data.keys():
await shop.register_goods( await shop.register_goods(
name, self._data[name]["price"], self._data[name]["des"], self._data[name]["discount"], self._data[name]["limit_time"] name,
self._data[name]["price"],
self._data[name]["des"],
self._data[name]["discount"],
self._data[name]["limit_time"],
self._data[name]["daily_limit"],
) )
use.register_use( use.register_use(
name, self._data[name]["func"], **self._data[name]["kwargs"] name, self._data[name]["func"], **self._data[name]["kwargs"]
@@ -66,6 +75,7 @@ class ShopRegister(dict):
discount: Union[float, Tuple[float, ...]] = 1, discount: Union[float, Tuple[float, ...]] = 1,
limit_time: Union[int, Tuple[int, ...]] = 0, limit_time: Union[int, Tuple[int, ...]] = 0,
load_status: Union[bool, Tuple[bool, ...]] = True, load_status: Union[bool, Tuple[bool, ...]] = True,
daily_limit: Union[int, Tuple[int, ...]] = 0,
**kwargs, **kwargs,
): ):
_tuple_list = [] _tuple_list = []
@@ -75,7 +85,9 @@ class ShopRegister(dict):
if _current_len == -1: if _current_len == -1:
_current_len = len(x) _current_len = len(x)
if _current_len != len(x): if _current_len != len(x):
raise ValueError(f"注册商品 {name} 中 name,price,des,discount,limit_time,load_status 数量不符!") raise ValueError(
f"注册商品 {name} 中 name,price,des,discount,limit_time,load_status,daily_limit 数量不符!"
)
_current_len = _current_len if _current_len > -1 else 1 _current_len = _current_len if _current_len > -1 else 1
_name = name if isinstance(name, tuple) else (name,) _name = name if isinstance(name, tuple) else (name,)
_price = ( _price = (
@@ -101,7 +113,21 @@ class ShopRegister(dict):
if isinstance(load_status, tuple) if isinstance(load_status, tuple)
else tuple([load_status for _ in range(_current_len)]) else tuple([load_status for _ in range(_current_len)])
) )
return self.register(_name, _price, _des, _discount, _limit_time, _load_status, **kwargs) _daily_limit = (
daily_limit
if isinstance(daily_limit, tuple)
else tuple([daily_limit for _ in range(_current_len)])
)
return self.register(
_name,
_price,
_des,
_discount,
_limit_time,
_load_status,
_daily_limit,
**kwargs,
)
def __setitem__(self, key, value): def __setitem__(self, key, value):
self._data[key] = value self._data[key] = value
+15 -1
View File
@@ -6,7 +6,7 @@ from pathlib import Path
from httpx import Response from httpx import Response
from asyncio.exceptions import TimeoutError from asyncio.exceptions import TimeoutError
from nonebot.adapters.onebot.v11 import MessageSegment from nonebot.adapters.onebot.v11 import MessageSegment
from playwright.async_api import Page from playwright.async_api import Page, BrowserContext
from .message_builder import image from .message_builder import image
from httpx import ConnectTimeout from httpx import ConnectTimeout
from .browser import get_browser from .browser import get_browser
@@ -135,6 +135,7 @@ class AsyncHttpx:
:param url: url :param url: url
:param path: 存储路径 :param path: 存储路径
:param params: params :param params: params
:param verify: verify
:param use_proxy: 使用代理 :param use_proxy: 使用代理
:param proxy: 指定代理 :param proxy: 指定代理
:param headers: 请求头 :param headers: 请求头
@@ -297,6 +298,19 @@ class AsyncPlaywright:
return await browser.new_page(user_agent=user_agent, **kwargs) return await browser.new_page(user_agent=user_agent, **kwargs)
raise BrowserIsNone("获取Browser失败...") raise BrowserIsNone("获取Browser失败...")
@classmethod
async def new_context(cls, user_agent: Optional[str] = None, **kwargs) -> BrowserContext:
"""
说明:
获取一个新上下文
参数:
:param user_agent: 请求头
"""
browser = await get_browser()
if browser:
return await browser.new_context(user_agent=user_agent, **kwargs)
raise BrowserIsNone("获取Browser失败...")
@classmethod @classmethod
async def goto( async def goto(
cls, cls,
+12 -7
View File
@@ -1,10 +1,11 @@
from typing import Union, Optional from typing import Union, Optional
from pathlib import Path from pathlib import Path
from ruamel.yaml import YAML from ruamel.yaml import YAML
from ruamel import yaml
import ujson as json import ujson as json
import copy import copy
yaml = YAML(typ="safe") _yaml = YAML(typ="safe")
class StaticData: class StaticData:
@@ -26,16 +27,17 @@ class StaticData:
if f.read().strip(): if f.read().strip():
raise ValueError(f"{file} 文件加载错误,请检查文件内容格式.") raise ValueError(f"{file} 文件加载错误,请检查文件内容格式.")
elif file.name.endswith("yaml"): elif file.name.endswith("yaml"):
self._data = yaml.load(f) self._data = _yaml.load(f)
def set(self, key, value): def set(self, key, value):
self._data[key] = value self._data[key] = value
self.save() self.save()
def set_module_data(self, module, key, value): def set_module_data(self, module, key, value, auto_save: bool = True):
if module in self._data.keys(): if module in self._data.keys():
self._data[module][key] = value self._data[module][key] = value
self.save() if auto_save:
self.save()
def get(self, key): def get(self, key):
return self._data.get(key) return self._data.get(key)
@@ -51,19 +53,22 @@ class StaticData:
return copy.deepcopy(self._data) return copy.deepcopy(self._data)
def save(self, path: Union[str, Path] = None): def save(self, path: Union[str, Path] = None):
path = path if path else self.file path = path or self.file
if isinstance(path, str): if isinstance(path, str):
path = Path(path) path = Path(path)
if path: if path:
with open(path, "w", encoding="utf8") as f: with open(path, "w", encoding="utf8") as f:
json.dump(self._data, f, ensure_ascii=False, indent=4) if path.name.endswith("yaml"):
yaml.dump(self._data, f, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True)
else:
json.dump(self._data, f, ensure_ascii=False, indent=4)
def reload(self): def reload(self):
if self.file.exists(): if self.file.exists():
if self.file.name.endswith("json"): if self.file.name.endswith("json"):
self._data: dict = json.load(open(self.file, "r", encoding="utf8")) self._data: dict = json.load(open(self.file, "r", encoding="utf8"))
elif self.file.name.endswith("yaml"): elif self.file.name.endswith("yaml"):
self._data: dict = yaml.load(open(self.file, "r", encoding="utf8")) self._data: dict = _yaml.load(open(self.file, "r", encoding="utf8"))
def is_exists(self): def is_exists(self):
return self.file.exists() return self.file.exists()
+13 -3
View File
@@ -2,8 +2,10 @@ from typing import List, Optional, Union, Tuple
from .data_class import StaticData from .data_class import StaticData
from pathlib import Path from pathlib import Path
from ruamel.yaml import YAML from ruamel.yaml import YAML
from ruamel import yaml
yaml = YAML(typ="safe")
_yaml = YAML(typ="safe")
class Plugins2settingsManager(StaticData): class Plugins2settingsManager(StaticData):
@@ -16,7 +18,7 @@ class Plugins2settingsManager(StaticData):
super().__init__(None) super().__init__(None)
if file.exists(): if file.exists():
with open(file, "r", encoding="utf8") as f: with open(file, "r", encoding="utf8") as f:
self._data = yaml.load(f) self._data = _yaml.load(f)
if self._data: if self._data:
if "PluginSettings" in self._data.keys(): if "PluginSettings" in self._data.keys():
self._data = ( self._data = (
@@ -91,11 +93,19 @@ class Plugins2settingsManager(StaticData):
return key return key
return keys return keys
def save(self, path: Union[str, Path] = None):
path = path or self.file
if isinstance(path, str):
path = Path(path)
if path:
with open(path, "w", encoding="utf8") as f:
yaml.dump({"PluginSettings": self._data}, f, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True)
def reload(self): def reload(self):
""" """
重载本地数据 重载本地数据
""" """
if self.file.exists(): if self.file.exists():
with open(self.file, "r", encoding="utf8") as f: with open(self.file, "r", encoding="utf8") as f:
self._data: dict = yaml.load(f) self._data: dict = _yaml.load(f)
self._data = self._data["PluginSettings"] self._data = self._data["PluginSettings"]
+1 -1
View File
@@ -157,7 +157,7 @@ def music(type_: str, id_: int) -> MessageSegment:
def custom_forward_msg( def custom_forward_msg(
msg_list: List[str], uin: Union[int, str], name: str = f"这里是{NICKNAME}" msg_list: List[Union[str, Message]], uin: Union[int, str], name: str = f"这里是{NICKNAME}"
) -> List[dict]: ) -> List[dict]:
""" """
生成自定义合并消息 生成自定义合并消息