Compare commits

...

4 Commits

Author SHA1 Message Date
e09fa13d0f 修复 Notify 的通知信息
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-10-13 16:55:50 +08:00
990a622cf6 添加一些日志用于调试 Notify 功能
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-10-13 11:48:22 +08:00
6144563d4d 添加 giftool 倒放选项 2025-10-13 11:34:06 +08:00
a6413c9809 添加报错和日志
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-10-12 21:52:35 +08:00
6 changed files with 130 additions and 15 deletions

9
bot.py
View File

@ -7,6 +7,10 @@ from nonebot.adapters.discord import Adapter as DiscordAdapter
from nonebot.adapters.minecraft import Adapter as MinecraftAdapter from nonebot.adapters.minecraft import Adapter as MinecraftAdapter
from nonebot.adapters.onebot.v11 import Adapter as OnebotAdapter from nonebot.adapters.onebot.v11 import Adapter as OnebotAdapter
from konabot.common.log import init_logger
from konabot.common.nb.exc import BotExceptionMessage
from konabot.common.path import LOG_PATH
dotenv.load_dotenv() dotenv.load_dotenv()
env = os.environ.get("ENVIRONMENT", "prod") env = os.environ.get("ENVIRONMENT", "prod")
env_enable_console = os.environ.get("ENABLE_CONSOLE", "none") env_enable_console = os.environ.get("ENABLE_CONSOLE", "none")
@ -14,7 +18,12 @@ env_enable_qq = os.environ.get("ENABLE_QQ", "none")
env_enable_discord = os.environ.get("ENABLE_DISCORD", "none") env_enable_discord = os.environ.get("ENABLE_DISCORD", "none")
env_enable_minecraft = os.environ.get("ENABLE_MINECRAFT", "none") env_enable_minecraft = os.environ.get("ENABLE_MINECRAFT", "none")
def main(): def main():
init_logger(LOG_PATH, [
BotExceptionMessage,
])
nonebot.init() nonebot.init()
driver = nonebot.get_driver() driver = nonebot.get_driver()

79
konabot/common/log.py Normal file
View File

@ -0,0 +1,79 @@
import sys
from pathlib import Path
from typing import TYPE_CHECKING, List, Type
from loguru import logger
if TYPE_CHECKING:
from loguru import Record
def file_exception_filter(
record: "Record",
ignored_exceptions: tuple[Type[Exception], ...]
) -> bool:
"""
一个自定义的 Loguru 过滤器函数。
如果日志记录包含异常信息,并且该异常的类型在 ignored_exceptions 中,则返回 False忽略
否则,返回 True允许记录
"""
exception_info = record.get("exception")
if exception_info:
exception_type = exception_info[0]
if exception_type and issubclass(exception_type, ignored_exceptions):
return False
return True
def init_logger(
log_dir: Path,
ignored_exceptions: List[Type[Exception]]
) -> None:
"""
配置全局 Loguru Logger。
Args:
log_dir (Path): 存放日志文件的文件夹路径,会自动创建。
ignored_exceptions (List[Type[Exception]]): 在 WARNING 级别文件日志中需要忽略的异常类型列表。
"""
ignored_exceptions_tuple = tuple(ignored_exceptions)
logger.remove()
log_dir.mkdir(parents=True, exist_ok=True)
logger.add(
sys.stderr,
level="INFO",
colorize=True,
format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
)
info_log_path = log_dir / "log.log"
logger.add(
str(info_log_path),
level="INFO",
rotation="10 MB",
retention="7 days",
enqueue=True,
backtrace=False,
diagnose=False,
)
warning_error_log_path = log_dir / "error.log"
logger.add(
str(warning_error_log_path),
level="WARNING",
rotation="10 MB",
compression="zip",
enqueue=True,
filter=lambda record: file_exception_filter(record, ignored_exceptions_tuple),
backtrace=True,
diagnose=True,
)
logger.info("Loguru Logger 初始化完成!")
logger.info(f"控制台日志级别: INFO")

View File

@ -4,6 +4,8 @@ ASSETS_PATH = Path(__file__).resolve().parent.parent.parent / "assets"
FONTS_PATH = ASSETS_PATH / "fonts" FONTS_PATH = ASSETS_PATH / "fonts"
SRC_PATH = Path(__file__).resolve().parent.parent SRC_PATH = Path(__file__).resolve().parent.parent
DATA_PATH = SRC_PATH.parent / "data"
LOG_PATH = DATA_PATH / "logs"
DOCS_PATH = SRC_PATH / "docs" DOCS_PATH = SRC_PATH / "docs"
DOCS_PATH_MAN1 = DOCS_PATH / "user" DOCS_PATH_MAN1 = DOCS_PATH / "user"

View File

@ -5,17 +5,17 @@
giftool [图片] [选项] giftool [图片] [选项]
示例 示例
回复一张 GIF 并发送: 回复一张 GIF 并发送:
`giftool --ss 1.5 -t 2.0` `giftool --ss 1.5 -t 2.0`
从 1.5 秒处开始,截取 2 秒长度的片段。 从 1.5 秒处开始,截取 2 秒长度的片段。
`giftool [图片] --ss 0:10 -to 0:15` `giftool [图片] --ss 0:10 -to 0:15`
截取从 10 秒到 15 秒之间的片段(支持 MM:SS 或 HH:MM:SS 格式)。 截取从 10 秒到 15 秒之间的片段(支持 MM:SS 或 HH:MM:SS 格式)。
`giftool [图片] --frames:v 10` `giftool [图片] --frames:v 10`
将整张 GIF 均匀抽帧,最终保留 10 帧。 将整张 GIF 均匀抽帧,最终保留 10 帧。
`giftool [图片] --ss 2 --frames:v 5` `giftool [图片] --ss 2 --frames:v 5`
从第 2 秒开始截取,并将结果抽帧为 5 帧。 从第 2 秒开始截取,并将结果抽帧为 5 帧。
参数说明 参数说明
@ -46,7 +46,7 @@
- 若原始帧数 ≤ 指定帧数,则保留全部帧。 - 若原始帧数 ≤ 指定帧数,则保留全部帧。
--s <速度>(可选) --s <速度>(可选)
- 调整 gif 图的速度 - 调整 gif 图的速度。若为负数,则代表倒放
使用方式 使用方式
1. 发送指令前,请确保: 1. 发送指令前,请确保:

View File

@ -71,8 +71,11 @@ async def _(
raise BotExceptionMessage("错误:出点时间小于入点") raise BotExceptionMessage("错误:出点时间小于入点")
if frame_count is not None and frame_count <= 0: if frame_count is not None and frame_count <= 0:
raise BotExceptionMessage("错误:帧数量应该大于 0") raise BotExceptionMessage("错误:帧数量应该大于 0")
if speed_factor <= 0: if speed_factor == 0:
raise BotExceptionMessage("错误:--speed 必须大于 0") raise BotExceptionMessage("错误:速度不能为 0")
is_rev = speed_factor < 0
speed_factor = abs(speed_factor)
if not getattr(image, "is_animated", False): if not getattr(image, "is_animated", False):
raise BotExceptionMessage("错误输入的不是动图GIF") raise BotExceptionMessage("错误输入的不是动图GIF")
@ -185,6 +188,10 @@ async def _(
if transparency_flag: if transparency_flag:
tf['transparency'] = 0 tf['transparency'] = 0
if is_rev:
rframes = rframes[::-1]
rdur = rdur[::-1]
if rframes: if rframes:
rframes[0].save( rframes[0].save(
output_img, output_img,

View File

@ -107,11 +107,16 @@ async def notify_now(notify: Notify):
return True return True
async def create_notify_task(notify: Notify, fail2remove: bool = True): def create_notify_task(notify: Notify, fail2remove: bool = True):
async def mission(): async def mission():
begin_time = datetime.datetime.now() begin_time = datetime.datetime.now()
if begin_time < notify.notify_time: if begin_time < notify.notify_time:
await asyncio.sleep((notify.notify_time - begin_time).total_seconds()) await asyncio.sleep((notify.notify_time - begin_time).total_seconds())
else:
logger.warning(
f"期望在 {notify.notify_time} 在平台 {notify.platform} {notify.target_env}"
f"{notify.target} 的代办通知 {notify.notify_msg} 已经超时,将会直接通知!"
)
res = await notify_now(notify) res = await notify_now(notify)
if fail2remove or res: if fail2remove or res:
await DATA_FILE_LOCK.acquire() await DATA_FILE_LOCK.acquire()
@ -143,6 +148,7 @@ async def _(msg: UniMsg, mEvt: Event):
# target_time = get_target_time(notify_time) # target_time = get_target_time(notify_time)
try: try:
target_time = ptimeparse.parse(notify_time) target_time = ptimeparse.parse(notify_time)
logger.info(f"{notify_time} 解析出了时间:{target_time}")
except Exception: except Exception:
logger.info(f"无法从 {notify_time} 中解析出时间") logger.info(f"无法从 {notify_time} 中解析出时间")
return return
@ -154,7 +160,7 @@ async def _(msg: UniMsg, mEvt: Event):
await DATA_FILE_LOCK.acquire() await DATA_FILE_LOCK.acquire()
cfg = load_notify_config() cfg = load_notify_config()
if isinstance(mEvt, ConsoleMessageEvent): if isinstance(mEvt, ConsoleMessageEvent):
platform = "console" platform = "console"
target = mEvt.get_user_id() target = mEvt.get_user_id()
@ -181,7 +187,7 @@ async def _(msg: UniMsg, mEvt: Event):
notify_time=target_time, notify_time=target_time,
notify_msg=notify_text, notify_msg=notify_text,
) )
await create_notify_task(notify) task = create_notify_task(notify)
cfg.notifies.append(notify) cfg.notifies.append(notify)
save_notify_config(cfg) save_notify_config(cfg)
@ -189,6 +195,7 @@ async def _(msg: UniMsg, mEvt: Event):
await evt.send(await UniMessage().at(mEvt.get_user_id()).text( await evt.send(await UniMessage().at(mEvt.get_user_id()).text(
f" 了解啦!将会在 {notify.notify_time} 提醒你哦~").export()) f" 了解啦!将会在 {notify.notify_time} 提醒你哦~").export())
logger.info(f"创建了一条于 {notify.notify_time} 的代办提醒")
driver = nonebot.get_driver() driver = nonebot.get_driver()
@ -205,15 +212,26 @@ async def _():
NOTIFIED_FLAG["task_added"] = True NOTIFIED_FLAG["task_added"] = True
await asyncio.sleep(10) DELTA = 2
logger.info(f"第一次探测到 Bot 连接,等待 {DELTA} 秒后开始通知")
await asyncio.sleep(DELTA)
await DATA_FILE_LOCK.acquire() await DATA_FILE_LOCK.acquire()
tasks = []
tasks: set[asyncio.Task[Any]] = set()
cfg = load_notify_config() cfg = load_notify_config()
if cfg.version == 1: if cfg.version == 1:
logger.info("将配置文件的版本升级为 2")
cfg.version = 2 cfg.version = 2
else: else:
for notify in cfg.notifies: counter = 0
tasks.append(create_notify_task(notify, fail2remove=False)) for notify in [*cfg.notifies]:
task = create_notify_task(notify, fail2remove=False)
tasks.add(task)
task.add_done_callback(lambda self: tasks.remove(self))
counter += 1
logger.info(f"成功创建了 {counter} 条代办事项")
save_notify_config(cfg)
DATA_FILE_LOCK.release() DATA_FILE_LOCK.release()
await asyncio.gather(*tasks) await asyncio.gather(*tasks)