Compare commits

...

3 Commits

Author SHA1 Message Date
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 117 additions and 11 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()
@ -204,16 +209,20 @@ async def _():
return return
NOTIFIED_FLAG["task_added"] = True NOTIFIED_FLAG["task_added"] = True
logger.info("第一次探测到 Bot 连接,等待 10 秒后开始通知")
await asyncio.sleep(10) await asyncio.sleep(10)
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:
cfg.version = 2 cfg.version = 2
else: else:
for notify in cfg.notifies: for notify in cfg.notifies:
tasks.append(create_notify_task(notify, fail2remove=False)) task = create_notify_task(notify, fail2remove=False)
tasks.add(task)
task.add_done_callback(lambda self: tasks.remove(self))
DATA_FILE_LOCK.release() DATA_FILE_LOCK.release()
await asyncio.gather(*tasks) await asyncio.gather(*tasks)