Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0c55545ec | |||
| 164305e81f | |||
| 96679033f3 | |||
| afda0680ec | |||
| 021133954e | |||
| 7baa04dbc2 | |||
| e55bdbdf4a | |||
| a30c7b8093 | |||
| 3da2c2266f | |||
| 96e3c3fe17 | |||
| 851c9eb3c7 | |||
| 11269b2a5a | |||
| 875e0efc2f | |||
| 4f43312663 | |||
| b2f4768573 |
6
bot.py
6
bot.py
@ -20,9 +20,13 @@ env_enable_minecraft = os.environ.get("ENABLE_MINECRAFT", "none")
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
if env.upper() == 'DEBUG' or env.upper() == 'DEV':
|
||||||
|
console_log_level = 'DEBUG'
|
||||||
|
else:
|
||||||
|
console_log_level = 'INFO'
|
||||||
init_logger(LOG_PATH, [
|
init_logger(LOG_PATH, [
|
||||||
BotExceptionMessage,
|
BotExceptionMessage,
|
||||||
])
|
], console_log_level=console_log_level)
|
||||||
|
|
||||||
nonebot.init()
|
nonebot.init()
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ def file_exception_filter(
|
|||||||
否则,返回 True(允许记录)。
|
否则,返回 True(允许记录)。
|
||||||
"""
|
"""
|
||||||
exception_info = record.get("exception")
|
exception_info = record.get("exception")
|
||||||
|
|
||||||
if exception_info:
|
if exception_info:
|
||||||
exception_type = exception_info[0]
|
exception_type = exception_info[0]
|
||||||
|
|
||||||
@ -29,8 +29,9 @@ def file_exception_filter(
|
|||||||
|
|
||||||
|
|
||||||
def init_logger(
|
def init_logger(
|
||||||
log_dir: Path,
|
log_dir: Path,
|
||||||
ignored_exceptions: List[Type[Exception]]
|
ignored_exceptions: List[Type[Exception]],
|
||||||
|
console_log_level: str = "INFO",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
配置全局 Loguru Logger。
|
配置全局 Loguru Logger。
|
||||||
@ -47,7 +48,7 @@ def init_logger(
|
|||||||
|
|
||||||
logger.add(
|
logger.add(
|
||||||
sys.stderr,
|
sys.stderr,
|
||||||
level="INFO",
|
level=console_log_level,
|
||||||
colorize=True,
|
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>",
|
format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
||||||
)
|
)
|
||||||
@ -76,4 +77,4 @@ def init_logger(
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.info("Loguru Logger 初始化完成!")
|
logger.info("Loguru Logger 初始化完成!")
|
||||||
logger.info(f"控制台日志级别: INFO")
|
logger.info(f"控制台日志级别: {console_log_level}")
|
||||||
|
|||||||
@ -25,6 +25,7 @@ from pydantic import BaseModel, ValidationError
|
|||||||
from .path import DATA_PATH
|
from .path import DATA_PATH
|
||||||
|
|
||||||
LONGTASK_DATA_DIR = DATA_PATH / "longtasks.json"
|
LONGTASK_DATA_DIR = DATA_PATH / "longtasks.json"
|
||||||
|
QQ_PRIVATE_CHAT_CHANNEL_PREFIX = "_CHANNEL_QQ_PRIVATE_"
|
||||||
|
|
||||||
|
|
||||||
class LongTaskTarget(BaseModel):
|
class LongTaskTarget(BaseModel):
|
||||||
@ -65,7 +66,7 @@ class LongTaskTarget(BaseModel):
|
|||||||
} BOT_CLASS={bot.__class__.__name__}"
|
} BOT_CLASS={bot.__class__.__name__}"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
if self.channel_id == "":
|
if self.channel_id.startswith(QQ_PRIVATE_CHAT_CHANNEL_PREFIX) or not self.channel_id.strip():
|
||||||
# 私聊模式
|
# 私聊模式
|
||||||
await bot.send_private_msg(
|
await bot.send_private_msg(
|
||||||
user_id=int(self.target_id),
|
user_id=int(self.target_id),
|
||||||
@ -118,18 +119,18 @@ class LongTask(BaseModel):
|
|||||||
target: LongTaskTarget
|
target: LongTaskTarget
|
||||||
callback: str
|
callback: str
|
||||||
deadline: datetime.datetime
|
deadline: datetime.datetime
|
||||||
canceled: bool = False
|
|
||||||
|
|
||||||
_aio_task: asynkio.Task | None = None
|
_aio_task: asynkio.Task | None = None
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
if self.deadline < now and not self.canceled:
|
if self.deadline < now:
|
||||||
await self._run_task()
|
await self._run_task()
|
||||||
return
|
return
|
||||||
await asynkio.sleep((self.deadline - now).total_seconds())
|
await asynkio.sleep((self.deadline - now).total_seconds())
|
||||||
if self.canceled:
|
async with longtask_data() as data:
|
||||||
return
|
if self.uuid not in data.to_handle[self.callback]:
|
||||||
|
return
|
||||||
await self._run_task()
|
await self._run_task()
|
||||||
|
|
||||||
async def _run_task(self):
|
async def _run_task(self):
|
||||||
@ -139,11 +140,7 @@ class LongTask(BaseModel):
|
|||||||
f"Callback {self.callback} 未曾被注册,但是被期待调用,已忽略"
|
f"Callback {self.callback} 未曾被注册,但是被期待调用,已忽略"
|
||||||
)
|
)
|
||||||
async with longtask_data() as datafile:
|
async with longtask_data() as datafile:
|
||||||
datafile.to_handle[self.callback] = [
|
del datafile.to_handle[self.callback][self.uuid]
|
||||||
t
|
|
||||||
for t in datafile.to_handle.get(self.callback, [])
|
|
||||||
if t.uuid != self.uuid
|
|
||||||
]
|
|
||||||
datafile.unhandled.setdefault(self.callback, []).append(self)
|
datafile.unhandled.setdefault(self.callback, []).append(self)
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -154,9 +151,7 @@ class LongTask(BaseModel):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
async with longtask_data() as datafile:
|
async with longtask_data() as datafile:
|
||||||
datafile.to_handle[self.callback] = [
|
del datafile.to_handle[self.callback][self.uuid]
|
||||||
t for t in datafile.to_handle[self.callback] if t.uuid != self.uuid
|
|
||||||
]
|
|
||||||
if not success:
|
if not success:
|
||||||
datafile.unhandled.setdefault(self.callback, []).append(self)
|
datafile.unhandled.setdefault(self.callback, []).append(self)
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -180,7 +175,7 @@ class LongTask(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class LongTaskModuleData(BaseModel):
|
class LongTaskModuleData(BaseModel):
|
||||||
to_handle: dict[str, list[LongTask]]
|
to_handle: dict[str, dict[str, LongTask]]
|
||||||
unhandled: dict[str, list[LongTask]]
|
unhandled: dict[str, list[LongTask]]
|
||||||
|
|
||||||
|
|
||||||
@ -196,7 +191,7 @@ async def get_long_task_target(event: BaseEvent, bot: BaseBot) -> LongTaskTarget
|
|||||||
return LongTaskTarget(
|
return LongTaskTarget(
|
||||||
platform="qq",
|
platform="qq",
|
||||||
self_id=str(event.self_id),
|
self_id=str(event.self_id),
|
||||||
channel_id="",
|
channel_id=f"{QQ_PRIVATE_CHAT_CHANNEL_PREFIX}{event.self_id}",
|
||||||
target_id=str(event.user_id),
|
target_id=str(event.user_id),
|
||||||
)
|
)
|
||||||
if isinstance(event, ConsoleMessageEvent):
|
if isinstance(event, ConsoleMessageEvent):
|
||||||
@ -278,7 +273,7 @@ async def create_longtask(
|
|||||||
await task.start()
|
await task.start()
|
||||||
|
|
||||||
async with longtask_data() as d:
|
async with longtask_data() as d:
|
||||||
d.to_handle.setdefault(handler, []).append(task)
|
d.to_handle.setdefault(handler, {})[task.uuid] = task
|
||||||
|
|
||||||
return task
|
return task
|
||||||
|
|
||||||
@ -289,7 +284,7 @@ async def init_longtask():
|
|||||||
|
|
||||||
async with longtask_data() as data:
|
async with longtask_data() as data:
|
||||||
for v in data.to_handle.values():
|
for v in data.to_handle.values():
|
||||||
for t in v:
|
for t in v.values():
|
||||||
await t.start()
|
await t.start()
|
||||||
counter += 1
|
counter += 1
|
||||||
req.add(t.callback)
|
req.add(t.callback)
|
||||||
|
|||||||
2
konabot/docs/concepts/罗文.txt
Normal file
2
konabot/docs/concepts/罗文.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
关于罗文和洛温:
|
||||||
|
AdoreLowen 希望和洛温阿特金森区分,所以最好就不要叫他洛温了!此方 BOT 会在一些群提醒叫错了的人。
|
||||||
15
konabot/docs/user/ntfy.txt
Normal file
15
konabot/docs/user/ntfy.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
指令介绍
|
||||||
|
ntfy - 配置使用 ntfy 来更好地为你通知此方 BOT 代办
|
||||||
|
|
||||||
|
指令示例
|
||||||
|
`ntfy 创建`
|
||||||
|
创建一个随机的 ntfy 订阅主题来提醒代办,此方 Bot 将会给你使用指引。你可以前往 https://ntfy.sh/ 官网下载 ntfy APP,或者使用网页版 ntfy。
|
||||||
|
|
||||||
|
`ntfy 创建 kagami-notice`
|
||||||
|
创建一个名字含有 kagami-notice 的 ntfy 订阅主题
|
||||||
|
|
||||||
|
`ntfy 删除`
|
||||||
|
清除并不再使用 ntfy 向你通知
|
||||||
|
|
||||||
|
另见
|
||||||
|
提醒我(1) 查询提醒(1) 删除提醒(1)
|
||||||
8
konabot/docs/user/删除提醒.txt
Normal file
8
konabot/docs/user/删除提醒.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
指令介绍
|
||||||
|
删除提醒 - 删除在`查询提醒(1)`中查到的提醒
|
||||||
|
|
||||||
|
指令示例
|
||||||
|
`删除提醒 1` 在查询提醒后,删除编号为 1 的提醒
|
||||||
|
|
||||||
|
另见
|
||||||
|
提醒我(1) 查询提醒(1) ntfy(1)
|
||||||
15
konabot/docs/user/提醒我.txt
Normal file
15
konabot/docs/user/提醒我.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
指令介绍
|
||||||
|
提醒我 - 在指定的时间提醒人事项的工具
|
||||||
|
|
||||||
|
使用示例
|
||||||
|
`下午五点提醒我吃饭`
|
||||||
|
创建一个下午五点的提醒,提醒你吃饭
|
||||||
|
|
||||||
|
`两分钟后提醒我睡觉`
|
||||||
|
创建一个相对于现在推迟 2 分钟的提醒,提醒你睡觉
|
||||||
|
|
||||||
|
`2026年4月25日20点整提醒我生日快乐`
|
||||||
|
创建一个指定日期和时间的提醒
|
||||||
|
|
||||||
|
另见
|
||||||
|
查询提醒(1) 删除提醒(1) ntfy(1)
|
||||||
9
konabot/docs/user/查询提醒.txt
Normal file
9
konabot/docs/user/查询提醒.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
指令介绍
|
||||||
|
查询提醒 - 查询已经创建的提醒
|
||||||
|
|
||||||
|
指令格式
|
||||||
|
`查询提醒` 查询提醒
|
||||||
|
`查询提醒 2` 查询第二页提醒
|
||||||
|
|
||||||
|
另见
|
||||||
|
提醒我(1) 删除提醒(1) ntfy(1)
|
||||||
@ -68,17 +68,19 @@ class TryStopState(Enum):
|
|||||||
|
|
||||||
class TryVerifyState(Enum):
|
class TryVerifyState(Enum):
|
||||||
VERIFIED = 0
|
VERIFIED = 0
|
||||||
NOT_IDIOM = 1
|
VERIFIED_AND_REAL = 1
|
||||||
WRONG_FIRST_CHAR = 2
|
NOT_IDIOM = 2
|
||||||
VERIFIED_BUT_NO_NEXT = 3
|
WRONG_FIRST_CHAR = 3
|
||||||
VERIFIED_GAME_END = 4
|
VERIFIED_BUT_NO_NEXT = 4
|
||||||
|
VERIFIED_GAME_END = 5
|
||||||
|
|
||||||
|
|
||||||
class IdiomGame:
|
class IdiomGame:
|
||||||
ALL_WORDS = [] # 所有四字词语
|
ALL_WORDS = [] # 所有四字词语
|
||||||
ALL_IDIOMS = [] # 所有成语
|
ALL_IDIOMS = [] # 所有成语
|
||||||
INSTANCE_LIST: dict[str, "IdiomGame"] = {} # 群号对应的游戏实例
|
INSTANCE_LIST: dict[str, "IdiomGame"] = {} # 群号对应的游戏实例
|
||||||
IDIOM_FIRST_CHAR = {} # 成语首字字典
|
IDIOM_FIRST_CHAR = {} # 所有成语包括词语的首字字典
|
||||||
|
AVALIABLE_IDIOM_FIRST_CHAR = {} # 真正有效的成语首字字典
|
||||||
|
|
||||||
__inited = False
|
__inited = False
|
||||||
|
|
||||||
@ -181,7 +183,7 @@ class IdiomGame:
|
|||||||
"""
|
"""
|
||||||
判断是否有成语可以接
|
判断是否有成语可以接
|
||||||
"""
|
"""
|
||||||
return last_char in IdiomGame.IDIOM_FIRST_CHAR
|
return last_char in IdiomGame.AVALIABLE_IDIOM_FIRST_CHAR
|
||||||
|
|
||||||
def _verify_idiom(self, idiom: str, user_id: str) -> TryVerifyState:
|
def _verify_idiom(self, idiom: str, user_id: str) -> TryVerifyState:
|
||||||
# 新成语的首字应与上一条成语的尾字相同
|
# 新成语的首字应与上一条成语的尾字相同
|
||||||
@ -193,6 +195,8 @@ class IdiomGame:
|
|||||||
self.last_idiom = idiom
|
self.last_idiom = idiom
|
||||||
self.last_char = idiom[-1]
|
self.last_char = idiom[-1]
|
||||||
self.add_score(user_id, 1)
|
self.add_score(user_id, 1)
|
||||||
|
if idiom in IdiomGame.ALL_IDIOMS:
|
||||||
|
self.add_score(user_id, 4) # 再加 4 分
|
||||||
self.remain_rounds -= 1
|
self.remain_rounds -= 1
|
||||||
if self.remain_rounds <= 0:
|
if self.remain_rounds <= 0:
|
||||||
self.now_playing = False
|
self.now_playing = False
|
||||||
@ -201,6 +205,8 @@ class IdiomGame:
|
|||||||
# 没有成语可以接了,自动跳过
|
# 没有成语可以接了,自动跳过
|
||||||
self._skip_idiom_async()
|
self._skip_idiom_async()
|
||||||
return TryVerifyState.VERIFIED_BUT_NO_NEXT
|
return TryVerifyState.VERIFIED_BUT_NO_NEXT
|
||||||
|
if idiom in IdiomGame.ALL_IDIOMS:
|
||||||
|
return TryVerifyState.VERIFIED_AND_REAL # 真实成语
|
||||||
return TryVerifyState.VERIFIED
|
return TryVerifyState.VERIFIED
|
||||||
|
|
||||||
def get_user_score(self, user_id: str) -> float:
|
def get_user_score(self, user_id: str) -> float:
|
||||||
@ -223,6 +229,13 @@ class IdiomGame:
|
|||||||
|
|
||||||
def get_last_char(self) -> str:
|
def get_last_char(self) -> str:
|
||||||
return self.last_char
|
return self.last_char
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def random_idiom_starting_with(cls, first_char: str) -> Optional[str]:
|
||||||
|
cls.init_lexicon()
|
||||||
|
if first_char not in cls.IDIOM_FIRST_CHAR:
|
||||||
|
return None
|
||||||
|
return secrets.choice(cls.IDIOM_FIRST_CHAR[first_char])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def init_lexicon(cls):
|
def init_lexicon(cls):
|
||||||
@ -288,6 +301,12 @@ class IdiomGame:
|
|||||||
cls.IDIOM_FIRST_CHAR[idiom[0]] = []
|
cls.IDIOM_FIRST_CHAR[idiom[0]] = []
|
||||||
cls.IDIOM_FIRST_CHAR[idiom[0]].append(idiom)
|
cls.IDIOM_FIRST_CHAR[idiom[0]].append(idiom)
|
||||||
|
|
||||||
|
# 根据真正的成语大表,划分出有效成语首字字典
|
||||||
|
for idiom in cls.ALL_IDIOMS:
|
||||||
|
if idiom[0] not in cls.AVALIABLE_IDIOM_FIRST_CHAR:
|
||||||
|
cls.AVALIABLE_IDIOM_FIRST_CHAR[idiom[0]] = []
|
||||||
|
cls.AVALIABLE_IDIOM_FIRST_CHAR[idiom[0]].append(idiom)
|
||||||
|
|
||||||
|
|
||||||
evt = on_alconna(
|
evt = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
@ -421,7 +440,8 @@ async def _(target: DepLongTaskTarget):
|
|||||||
instance = IdiomGame.INSTANCE_LIST.get(group_id)
|
instance = IdiomGame.INSTANCE_LIST.get(group_id)
|
||||||
if not instance or not instance.get_playing_state():
|
if not instance or not instance.get_playing_state():
|
||||||
return
|
return
|
||||||
await evt.send(await UniMessage().text("你们太菜了!全部扣100分!").export())
|
avaliable_idiom = IdiomGame.random_idiom_starting_with(instance.get_last_char())
|
||||||
|
await evt.send(await UniMessage().text(f"你们太菜了,全部扣100分!明明还可以接「{avaliable_idiom}」的!").export())
|
||||||
idiom = await instance.skip_idiom(-100)
|
idiom = await instance.skip_idiom(-100)
|
||||||
await evt.send(
|
await evt.send(
|
||||||
await UniMessage().text(f"重新开始,下一个成语是「{idiom}」").export()
|
await UniMessage().text(f"重新开始,下一个成语是「{idiom}」").export()
|
||||||
@ -458,16 +478,24 @@ async def _(event: BaseEvent, msg: UniMsg, target: DepLongTaskTarget):
|
|||||||
await evt.send(
|
await evt.send(
|
||||||
await UniMessage()
|
await UniMessage()
|
||||||
.at(user_id)
|
.at(user_id)
|
||||||
.text("接不上!这个不一样!你被扣了 0.1 分!")
|
.text(" 接不上!这个不一样!你被扣了 0.1 分!")
|
||||||
.export()
|
.export()
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
await evt.send(
|
if state == TryVerifyState.VERIFIED:
|
||||||
await UniMessage()
|
await evt.send(
|
||||||
.at(user_id)
|
await UniMessage()
|
||||||
.text(f"接对了!你有 {instance.get_user_score(user_id)} 分!")
|
.at(user_id)
|
||||||
.export()
|
.text(f" 接上了,喜提 1 分!你有 {instance.get_user_score(user_id)} 分!")
|
||||||
)
|
.export()
|
||||||
|
)
|
||||||
|
elif state == TryVerifyState.VERIFIED_AND_REAL:
|
||||||
|
await evt.send(
|
||||||
|
await UniMessage()
|
||||||
|
.at(user_id)
|
||||||
|
.text(f" 接上了,这是个真实成语,喜提 5 分!你有 {instance.get_user_score(user_id)} 分!")
|
||||||
|
.export()
|
||||||
|
)
|
||||||
if state == TryVerifyState.VERIFIED_GAME_END:
|
if state == TryVerifyState.VERIFIED_GAME_END:
|
||||||
await evt.send(await UniMessage().text("全部回合结束!").export())
|
await evt.send(await UniMessage().text("全部回合结束!").export())
|
||||||
await end_game(event, group_id)
|
await end_game(event, group_id)
|
||||||
|
|||||||
@ -53,7 +53,7 @@ async def _(
|
|||||||
if doc is None:
|
if doc is None:
|
||||||
# 检索模式
|
# 检索模式
|
||||||
if section is None:
|
if section is None:
|
||||||
section_set = {1}
|
section_set = {1, 7}
|
||||||
else:
|
else:
|
||||||
section_set = {section}
|
section_set = {section}
|
||||||
if 1 in section_set and is_admin(event):
|
if 1 in section_set and is_admin(event):
|
||||||
@ -75,7 +75,7 @@ async def _(
|
|||||||
else:
|
else:
|
||||||
# 查阅模式
|
# 查阅模式
|
||||||
if section is None:
|
if section is None:
|
||||||
section_set = {1}
|
section_set = {1, 7}
|
||||||
else:
|
else:
|
||||||
section_set = {section}
|
section_set = {section}
|
||||||
if 1 in section_set and is_admin(event):
|
if 1 in section_set and is_admin(event):
|
||||||
|
|||||||
44
konabot/plugins/no_luowen.py
Normal file
44
konabot/plugins/no_luowen.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import nonebot
|
||||||
|
|
||||||
|
from nonebot.adapters.onebot.v11.bot import Bot
|
||||||
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
||||||
|
from nonebot_plugin_alconna import UniMsg, UniMessage
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class NoLuowenConfig(BaseModel):
|
||||||
|
plugin_noluowen_qqid: int = -1
|
||||||
|
plugin_noluowen_enable_group: list[int] = []
|
||||||
|
|
||||||
|
config = nonebot.get_plugin_config(NoLuowenConfig)
|
||||||
|
|
||||||
|
|
||||||
|
async def is_luowen_mentioned(evt: GroupMessageEvent, msg: UniMsg) -> bool:
|
||||||
|
if config.plugin_noluowen_qqid <= 0:
|
||||||
|
return False
|
||||||
|
if evt.user_id == config.plugin_noluowen_qqid:
|
||||||
|
return False
|
||||||
|
if evt.group_id not in config.plugin_noluowen_enable_group:
|
||||||
|
return False
|
||||||
|
txt = msg.extract_plain_text()
|
||||||
|
if "洛温" not in txt:
|
||||||
|
return False
|
||||||
|
if "罗文" in txt:
|
||||||
|
return False
|
||||||
|
if "阿特金森" in txt:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
evt_luowen_mentioned = nonebot.on_message(rule=is_luowen_mentioned)
|
||||||
|
|
||||||
|
|
||||||
|
@evt_luowen_mentioned.handle()
|
||||||
|
async def _(evt: GroupMessageEvent, bot: Bot):
|
||||||
|
msg = (
|
||||||
|
UniMessage()
|
||||||
|
.reply(str(evt.message_id))
|
||||||
|
.at(str(config.plugin_noluowen_qqid))
|
||||||
|
.text(" 好像有人念错了你的 ID")
|
||||||
|
)
|
||||||
|
await evt_luowen_mentioned.send(await msg.export(bot=bot))
|
||||||
|
|
||||||
@ -1,27 +1,24 @@
|
|||||||
|
import aiohttp
|
||||||
import asyncio as asynkio
|
import asyncio as asynkio
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
from math import ceil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Literal, cast
|
from typing import Any, Literal
|
||||||
|
|
||||||
import signal
|
import nanoid
|
||||||
import nonebot
|
import nonebot
|
||||||
import ptimeparse
|
import ptimeparse
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from nonebot import on_message
|
from nonebot import get_plugin_config, on_message
|
||||||
from nonebot.adapters import Event
|
from nonebot.adapters import Bot, Event
|
||||||
from nonebot.adapters.console import Bot as ConsoleBot
|
from nonebot.adapters.onebot.v11 import Bot as OBBot
|
||||||
from nonebot.adapters.console.event import MessageEvent as ConsoleMessageEvent
|
from nonebot.adapters.console import Bot as CBot
|
||||||
from nonebot.adapters.discord import Bot as DiscordBot
|
from nonebot.adapters.discord import Bot as DCBot
|
||||||
from nonebot.adapters.discord.event import MessageEvent as DiscordMessageEvent
|
from nonebot_plugin_alconna import Alconna, Args, Subcommand, UniMessage, UniMsg, on_alconna
|
||||||
from nonebot.adapters.onebot.v11 import Bot as OnebotV11Bot
|
|
||||||
from nonebot.adapters.onebot.v11.event import (
|
|
||||||
GroupMessageEvent as OnebotV11GroupMessageEvent,
|
|
||||||
)
|
|
||||||
from nonebot.adapters.onebot.v11.event import MessageEvent as OnebotV11MessageEvent
|
|
||||||
from nonebot_plugin_alconna import UniMessage, UniMsg
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from konabot.common.longtask import DepLongTaskTarget, LongTask, LongTaskTarget, create_longtask, handle_long_task, longtask_data
|
||||||
|
|
||||||
evt = on_message()
|
evt = on_message()
|
||||||
|
|
||||||
(Path(__file__).parent.parent.parent.parent / "data").mkdir(exist_ok=True)
|
(Path(__file__).parent.parent.parent.parent / "data").mkdir(exist_ok=True)
|
||||||
@ -29,6 +26,14 @@ DATA_FILE_PATH = Path(__file__).parent.parent.parent.parent / "data" / "notify.j
|
|||||||
DATA_FILE_LOCK = asynkio.Lock()
|
DATA_FILE_LOCK = asynkio.Lock()
|
||||||
|
|
||||||
ASYNK_TASKS: set[asynkio.Task[Any]] = set()
|
ASYNK_TASKS: set[asynkio.Task[Any]] = set()
|
||||||
|
LONG_TASK_NAME = "TASK_SIMPLE_NOTIFY"
|
||||||
|
PAGE_SIZE = 6
|
||||||
|
|
||||||
|
FMT_STRING = "%Y年%m月%d日 %H:%M:%S"
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyMessage(BaseModel):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
class Notify(BaseModel):
|
class Notify(BaseModel):
|
||||||
@ -43,14 +48,64 @@ class Notify(BaseModel):
|
|||||||
notify_time: datetime.datetime
|
notify_time: datetime.datetime
|
||||||
notify_msg: str
|
notify_msg: str
|
||||||
|
|
||||||
def get_str(self):
|
|
||||||
return f"{self.target}-{self.target_env}-{self.platform}-{self.notify_time}"
|
|
||||||
|
|
||||||
|
|
||||||
class NotifyConfigFile(BaseModel):
|
class NotifyConfigFile(BaseModel):
|
||||||
version: int = 2
|
version: int = 2
|
||||||
notifies: list[Notify] = []
|
notifies: list[Notify] = []
|
||||||
unsent: list[Notify] = []
|
unsent: list[Notify] = []
|
||||||
|
notify_channels: dict[str, str] = {}
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyPluginConfig(BaseModel):
|
||||||
|
plugin_notify_enable_ntfy: bool = False
|
||||||
|
plugin_notify_base_url: str = ""
|
||||||
|
plugin_notify_access_token: str = ""
|
||||||
|
plugin_notify_prefix: str = "kona-notice-"
|
||||||
|
|
||||||
|
|
||||||
|
config = get_plugin_config(NotifyPluginConfig)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_notify_to_ntfy_instance(msg: str, channel: str):
|
||||||
|
if not config.plugin_notify_enable_ntfy:
|
||||||
|
return
|
||||||
|
url = f"{config.plugin_notify_base_url}/{channel}"
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
session.headers["Authorization"] = f"Bearer {config.plugin_notify_access_token}"
|
||||||
|
session.headers["Title"] = "🔔 此方 BOT 提醒"
|
||||||
|
async with session.post(url, data=msg) as response:
|
||||||
|
logger.info(f"访问 {url} 的结果是 {response.status}")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_bot_of(_type: type[Bot]):
|
||||||
|
for bot in nonebot.get_bots().values():
|
||||||
|
if isinstance(bot, _type):
|
||||||
|
return bot.self_id
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_target_from_notify(notify: Notify) -> LongTaskTarget:
|
||||||
|
if notify.platform == "console":
|
||||||
|
return LongTaskTarget(
|
||||||
|
platform="console",
|
||||||
|
self_id=_get_bot_of(CBot),
|
||||||
|
channel_id=notify.target_env or "",
|
||||||
|
target_id=notify.target,
|
||||||
|
)
|
||||||
|
if notify.platform == "discord":
|
||||||
|
return LongTaskTarget(
|
||||||
|
platform="discord",
|
||||||
|
self_id=_get_bot_of(DCBot),
|
||||||
|
channel_id=notify.target_env or "",
|
||||||
|
target_id=notify.target,
|
||||||
|
)
|
||||||
|
return LongTaskTarget(
|
||||||
|
platform="qq",
|
||||||
|
self_id=_get_bot_of(OBBot),
|
||||||
|
channel_id=notify.target_env or "",
|
||||||
|
target_id=notify.target,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_notify_config() -> NotifyConfigFile:
|
def load_notify_config() -> NotifyConfigFile:
|
||||||
@ -67,87 +122,8 @@ def save_notify_config(config: NotifyConfigFile):
|
|||||||
DATA_FILE_PATH.write_text(config.model_dump_json(indent=4))
|
DATA_FILE_PATH.write_text(config.model_dump_json(indent=4))
|
||||||
|
|
||||||
|
|
||||||
async def notify_now(notify: Notify):
|
|
||||||
if notify.platform == "console":
|
|
||||||
bot = [b for b in nonebot.get_bots().values() if isinstance(b, ConsoleBot)]
|
|
||||||
if len(bot) != 1:
|
|
||||||
logger.warning(f"提醒未成功发送出去:{nonebot.get_bots()} {notify}")
|
|
||||||
return False
|
|
||||||
bot = bot[0]
|
|
||||||
await bot.send_private_message(notify.target, f"代办通知:{notify.notify_msg}")
|
|
||||||
elif notify.platform == "discord":
|
|
||||||
bot = [b for b in nonebot.get_bots().values() if isinstance(b, DiscordBot)]
|
|
||||||
if len(bot) != 1:
|
|
||||||
logger.warning(f"提醒未成功发送出去:{nonebot.get_bots()} {notify}")
|
|
||||||
return False
|
|
||||||
bot = bot[0]
|
|
||||||
channel = await bot.create_DM(recipient_id=int(notify.target))
|
|
||||||
await bot.send_to(channel.id, f"代办通知:{notify.notify_msg}")
|
|
||||||
elif notify.platform == "qq":
|
|
||||||
bot = [b for b in nonebot.get_bots().values() if isinstance(b, OnebotV11Bot)]
|
|
||||||
if len(bot) != 1:
|
|
||||||
logger.warning(f"提醒未成功发送出去:{nonebot.get_bots()} {notify}")
|
|
||||||
return False
|
|
||||||
bot = bot[0]
|
|
||||||
if notify.target_env is None:
|
|
||||||
await bot.send_private_msg(
|
|
||||||
user_id=int(notify.target),
|
|
||||||
message=cast(
|
|
||||||
Any,
|
|
||||||
await UniMessage.text(f"代办通知:{notify.notify_msg}").export(
|
|
||||||
bot=bot,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await bot.send_group_msg(
|
|
||||||
group_id=int(notify.target_env),
|
|
||||||
message=cast(
|
|
||||||
Any,
|
|
||||||
await UniMessage()
|
|
||||||
.at(notify.target)
|
|
||||||
.text(f" 代办通知:{notify.notify_msg}")
|
|
||||||
.export(bot=bot),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(f"提醒未成功发送出去:{notify}")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def create_notify_task(notify: Notify, fail2remove: bool = True):
|
|
||||||
async def mission():
|
|
||||||
begin_time = datetime.datetime.now()
|
|
||||||
if begin_time < notify.notify_time:
|
|
||||||
try:
|
|
||||||
await asynkio.sleep((notify.notify_time - begin_time).total_seconds())
|
|
||||||
except asynkio.CancelledError:
|
|
||||||
logger.debug("代办提醒被信号中止,任务退出")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"期望在 {notify.notify_time} 在平台 {notify.platform} {notify.target_env}"
|
|
||||||
f" {notify.target} 的代办通知 {notify.notify_msg} 已经超时,将会直接通知!"
|
|
||||||
)
|
|
||||||
res = await notify_now(notify)
|
|
||||||
if fail2remove or res:
|
|
||||||
async with DATA_FILE_LOCK:
|
|
||||||
cfg = load_notify_config()
|
|
||||||
cfg.notifies = [
|
|
||||||
n for n in cfg.notifies if n.get_str() != notify.get_str()
|
|
||||||
]
|
|
||||||
if not res:
|
|
||||||
cfg.unsent.append(notify)
|
|
||||||
save_notify_config(cfg)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return asynkio.create_task(mission())
|
|
||||||
|
|
||||||
|
|
||||||
@evt.handle()
|
@evt.handle()
|
||||||
async def _(msg: UniMsg, mEvt: Event):
|
async def _(msg: UniMsg, mEvt: Event, target: DepLongTaskTarget):
|
||||||
if mEvt.get_user_id() in nonebot.get_bots():
|
if mEvt.get_user_id() in nonebot.get_bots():
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -160,62 +136,26 @@ async def _(msg: UniMsg, mEvt: Event):
|
|||||||
return
|
return
|
||||||
|
|
||||||
notify_time, notify_text = segments
|
notify_time, notify_text = segments
|
||||||
# target_time = get_target_time(notify_time)
|
|
||||||
try:
|
try:
|
||||||
# target_time = ptimeparse.parse(notify_time)
|
|
||||||
target_time = ptimeparse.Parser().parse(notify_time)
|
target_time = ptimeparse.Parser().parse(notify_time)
|
||||||
logger.info(f"从 {notify_time} 解析出了时间:{target_time}")
|
logger.info(f"从 {notify_time} 解析出了时间:{target_time}")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.info(f"无法从 {notify_time} 中解析出时间")
|
logger.info(f"无法从 {notify_time} 中解析出时间")
|
||||||
return
|
return
|
||||||
# if target_time is None:
|
|
||||||
# logger.info(f"无法从 {notify_time} 中解析出时间")
|
|
||||||
# return
|
|
||||||
if not notify_text:
|
if not notify_text:
|
||||||
return
|
return
|
||||||
|
|
||||||
await DATA_FILE_LOCK.acquire()
|
await create_longtask(
|
||||||
cfg = load_notify_config()
|
LONG_TASK_NAME,
|
||||||
|
{ "message": notify_text },
|
||||||
if isinstance(mEvt, ConsoleMessageEvent):
|
target,
|
||||||
platform = "console"
|
target_time,
|
||||||
target = mEvt.get_user_id()
|
|
||||||
target_env = None
|
|
||||||
elif isinstance(mEvt, OnebotV11MessageEvent):
|
|
||||||
platform = "qq"
|
|
||||||
target = mEvt.get_user_id()
|
|
||||||
if isinstance(mEvt, OnebotV11GroupMessageEvent):
|
|
||||||
target_env = str(mEvt.group_id)
|
|
||||||
else:
|
|
||||||
target_env = None
|
|
||||||
elif isinstance(mEvt, DiscordMessageEvent):
|
|
||||||
platform = "discord"
|
|
||||||
target = mEvt.get_user_id()
|
|
||||||
target_env = None
|
|
||||||
else:
|
|
||||||
logger.warning(f"Notify 遇到不支持的平台:{type(mEvt).__name__}")
|
|
||||||
return
|
|
||||||
|
|
||||||
notify = Notify(
|
|
||||||
platform=platform,
|
|
||||||
target=target,
|
|
||||||
target_env=target_env,
|
|
||||||
notify_time=target_time,
|
|
||||||
notify_msg=notify_text,
|
|
||||||
)
|
)
|
||||||
create_notify_task(notify)
|
|
||||||
|
|
||||||
cfg.notifies.append(notify)
|
await target.send_message(
|
||||||
save_notify_config(cfg)
|
UniMessage().text(f"了解啦!将会在 {target_time.strftime(FMT_STRING)} 提醒你哦~")
|
||||||
DATA_FILE_LOCK.release()
|
|
||||||
|
|
||||||
await evt.send(
|
|
||||||
await UniMessage()
|
|
||||||
.at(mEvt.get_user_id())
|
|
||||||
.text(f" 了解啦!将会在 {notify.notify_time} 提醒你哦~")
|
|
||||||
.export()
|
|
||||||
)
|
)
|
||||||
logger.info(f"创建了一条于 {notify.notify_time} 的代办提醒")
|
logger.info(f"创建了一条于 {target_time} 的代办提醒")
|
||||||
|
|
||||||
|
|
||||||
driver = nonebot.get_driver()
|
driver = nonebot.get_driver()
|
||||||
@ -238,35 +178,152 @@ async def _():
|
|||||||
|
|
||||||
await DATA_FILE_LOCK.acquire()
|
await DATA_FILE_LOCK.acquire()
|
||||||
|
|
||||||
# tasks: set[asynkio.Task[Any]] = set()
|
|
||||||
cfg = load_notify_config()
|
cfg = load_notify_config()
|
||||||
if cfg.version == 1:
|
if cfg.version == 1:
|
||||||
logger.info("将配置文件的版本升级为 2")
|
logger.info("将配置文件的版本升级为 2")
|
||||||
cfg.version = 2
|
cfg.version = 2
|
||||||
else:
|
else:
|
||||||
counter = 0
|
|
||||||
for notify in [*cfg.notifies]:
|
for notify in [*cfg.notifies]:
|
||||||
task = create_notify_task(notify, fail2remove=False)
|
await create_longtask(
|
||||||
ASYNK_TASKS.add(task)
|
handler=LONG_TASK_NAME,
|
||||||
task.add_done_callback(lambda self: ASYNK_TASKS.remove(self))
|
data={ "message": notify.notify_msg },
|
||||||
counter += 1
|
target=get_target_from_notify(notify),
|
||||||
logger.info(f"成功创建了 {counter} 条代办事项")
|
deadline=notify.notify_time,
|
||||||
|
)
|
||||||
|
cfg.notifies = []
|
||||||
save_notify_config(cfg)
|
save_notify_config(cfg)
|
||||||
DATA_FILE_LOCK.release()
|
DATA_FILE_LOCK.release()
|
||||||
|
|
||||||
loop = asynkio.get_running_loop()
|
|
||||||
|
|
||||||
# 解决 asynk task 没有被 cancel 的问题
|
@handle_long_task("TASK_SIMPLE_NOTIFY")
|
||||||
async def shutdown(sig: signal.Signals):
|
async def _(task: LongTask):
|
||||||
logger.info(f"收到 {sig.name} 指令,正在关闭所有的东西")
|
message = task.data["message"]
|
||||||
for task in ASYNK_TASKS:
|
await task.target.send_message(
|
||||||
task.cancel()
|
UniMessage().text(f"代办提醒:{message}")
|
||||||
await asynkio.gather(*ASYNK_TASKS, return_exceptions=True)
|
)
|
||||||
logger.info("所有的代办提醒 Task 都已经退出了")
|
async with DATA_FILE_LOCK:
|
||||||
|
data = load_notify_config()
|
||||||
|
if (chan := data.notify_channels.get(task.target.target_id)) is not None:
|
||||||
|
await send_notify_to_ntfy_instance(message, chan)
|
||||||
|
save_notify_config(data)
|
||||||
|
|
||||||
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
||||||
loop.add_signal_handler(
|
USER_CHECKOUT_TASK_CACHE: dict[str, dict[str, str]] = {}
|
||||||
sig, functools.partial(asynkio.create_task, shutdown(sig))
|
|
||||||
|
|
||||||
|
cmd_check_notify_list = on_alconna(Alconna(
|
||||||
|
"re:(?:我有哪些|查询)(?:提醒|代办)",
|
||||||
|
Args["page", int, 1]
|
||||||
|
))
|
||||||
|
|
||||||
|
@cmd_check_notify_list.handle()
|
||||||
|
async def _(page: int, target: DepLongTaskTarget):
|
||||||
|
if page <= 0:
|
||||||
|
await target.send_message(UniMessage().text("页数应该大于 0 吧"))
|
||||||
|
return
|
||||||
|
async with longtask_data() as data:
|
||||||
|
tasks = data.to_handle.get(LONG_TASK_NAME, {}).values()
|
||||||
|
tasks = [t for t in tasks if t.target.target_id == target.target_id]
|
||||||
|
tasks = sorted(tasks, key=lambda t: t.deadline)
|
||||||
|
pages = ceil(len(tasks) / PAGE_SIZE)
|
||||||
|
if page > pages:
|
||||||
|
await target.send_message(UniMessage().text(f"最多也就 {pages} 页啦!"))
|
||||||
|
tasks = tasks[(page - 1) * PAGE_SIZE: page * PAGE_SIZE]
|
||||||
|
|
||||||
|
message = "你可以输入「删除提醒 序号」来删除一个提醒\n====== 代办清单 ======\n\n"
|
||||||
|
|
||||||
|
to_cache = {}
|
||||||
|
if len(tasks) == 0:
|
||||||
|
message += "空空如也\n"
|
||||||
|
else:
|
||||||
|
for i, task in enumerate(tasks):
|
||||||
|
to_cache[str(i + 1)] = task.uuid
|
||||||
|
message += f"{i + 1}) {task.data['message']}({task.deadline.strftime(FMT_STRING)})\n"
|
||||||
|
|
||||||
|
message += f"\n==== 第 {page} 页,共 {pages} 页 ===="
|
||||||
|
USER_CHECKOUT_TASK_CACHE[target.target_id] = to_cache
|
||||||
|
|
||||||
|
await target.send_message(UniMessage().text(message))
|
||||||
|
|
||||||
|
|
||||||
|
cmd_remove_task = on_alconna(Alconna(
|
||||||
|
"re:删除(?:提醒|代办)",
|
||||||
|
Args["checker", str],
|
||||||
|
))
|
||||||
|
|
||||||
|
@cmd_remove_task.handle()
|
||||||
|
async def _(checker: str, target: DepLongTaskTarget):
|
||||||
|
if target.target_id not in USER_CHECKOUT_TASK_CACHE:
|
||||||
|
await target.send_message(UniMessage().text(
|
||||||
|
"先用「查询提醒」来查询你有哪些提醒吧"
|
||||||
|
))
|
||||||
|
return
|
||||||
|
if checker not in USER_CHECKOUT_TASK_CACHE[target.target_id]:
|
||||||
|
await target.send_message(UniMessage().text(
|
||||||
|
"没有这个任务哦,请检查一下吧"
|
||||||
|
))
|
||||||
|
uuid = USER_CHECKOUT_TASK_CACHE[target.target_id][checker]
|
||||||
|
async with longtask_data() as data:
|
||||||
|
if uuid not in data.to_handle[LONG_TASK_NAME]:
|
||||||
|
await target.send_message(UniMessage().text(
|
||||||
|
"似乎这个提醒已经发出去了,或者已经被删除"
|
||||||
|
))
|
||||||
|
return
|
||||||
|
_msg = data.to_handle[LONG_TASK_NAME][uuid].data["message"]
|
||||||
|
del data.to_handle[LONG_TASK_NAME][uuid]
|
||||||
|
await target.send_message(UniMessage().text(
|
||||||
|
f"成功取消了提醒:{_msg}"
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
cmd_notify_channel = on_alconna(Alconna(
|
||||||
|
"ntfy",
|
||||||
|
Subcommand("删除", dest="delete"),
|
||||||
|
Subcommand("创建", Args["notify_id?", str], dest="create"),
|
||||||
|
), rule=lambda: config.plugin_notify_enable_ntfy)
|
||||||
|
|
||||||
|
@cmd_notify_channel.assign("$main")
|
||||||
|
async def _(target: DepLongTaskTarget):
|
||||||
|
await target.send_message(UniMessage.text(
|
||||||
|
"配置 ntfy 通知:\n\n"
|
||||||
|
"- ntfy 创建: 启用 ntfy 通知,并为你随机生成一个通知渠道\n"
|
||||||
|
"- ntfy 删除:禁用 ntfy 通知\n"
|
||||||
|
))
|
||||||
|
|
||||||
|
@cmd_notify_channel.assign("create")
|
||||||
|
async def _(target: DepLongTaskTarget, notify_id: str = ""):
|
||||||
|
if notify_id == "":
|
||||||
|
notify_id = nanoid.generate(
|
||||||
|
alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-",
|
||||||
|
size=16,
|
||||||
)
|
)
|
||||||
|
|
||||||
await asynkio.gather(*ASYNK_TASKS)
|
channel_name = f"{config.plugin_notify_prefix}{notify_id}"
|
||||||
|
|
||||||
|
async with DATA_FILE_LOCK:
|
||||||
|
data = load_notify_config()
|
||||||
|
data.notify_channels[target.target_id] = channel_name
|
||||||
|
save_notify_config(data)
|
||||||
|
|
||||||
|
await target.send_message(UniMessage.text(
|
||||||
|
f"了解!将会在 {channel_name} 为你提醒!\n"
|
||||||
|
"\n"
|
||||||
|
"食用教程:在你的手机端 / 网页端 ntfy 点击「订阅主题」,选择「使用其他服务器」,"
|
||||||
|
f"服务器填写 {config.plugin_notify_base_url} ,主题名填写 {channel_name}\n"
|
||||||
|
f"最后点击订阅,就能看到我给你发的消息啦!"
|
||||||
|
))
|
||||||
|
|
||||||
|
await send_notify_to_ntfy_instance(
|
||||||
|
"如果你看到这条消息,说明你已经成功订阅主题!此方 BOT 将会在这里提醒你你的代办!",
|
||||||
|
channel_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@cmd_notify_channel.assign("delete")
|
||||||
|
async def _(target: DepLongTaskTarget):
|
||||||
|
async with DATA_FILE_LOCK:
|
||||||
|
data = load_notify_config()
|
||||||
|
del data.notify_channels[target.target_id]
|
||||||
|
save_notify_config(data)
|
||||||
|
await target.send_message(UniMessage.text("ok."))
|
||||||
|
|
||||||
|
|||||||
25
poetry.lock
generated
25
poetry.lock
generated
@ -1743,6 +1743,23 @@ type = "legacy"
|
|||||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
reference = "mirrors"
|
reference = "mirrors"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nanoid"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "A tiny, secure, URL-friendly, unique string ID generator for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb"},
|
||||||
|
{file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.source]
|
||||||
|
type = "legacy"
|
||||||
|
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
|
reference = "mirrors"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nepattern"
|
name = "nepattern"
|
||||||
version = "0.7.7"
|
version = "0.7.7"
|
||||||
@ -2402,14 +2419,14 @@ reference = "mirrors"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ptimeparse"
|
name = "ptimeparse"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
description = "一个用于解析中文的时间表达的库"
|
description = "一个用于解析中文的时间表达的库"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
groups = ["main"]
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "ptimeparse-0.2.0-py3-none-any.whl", hash = "sha256:57055f8fd99fb69e19deac3b8a5c7ac91af86c7ac09781632e9abf318df0d6d2"},
|
{file = "ptimeparse-0.2.1-py3-none-any.whl", hash = "sha256:cf1115784d5d983da2d5b7af327108bf04c218c795d63291e71f76d7c6ffd2d4"},
|
||||||
{file = "ptimeparse-0.2.0.tar.gz", hash = "sha256:867c265f2e157fe4d793d20fe9c449b8ede5c855f336d7e6b2eb78551e622766"},
|
{file = "ptimeparse-0.2.1.tar.gz", hash = "sha256:9b640e0a315d19b1e3821a290d236a051d8320348970ce3a835ed675bd2d832f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
@ -3807,4 +3824,4 @@ reference = "mirrors"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.12,<4.0"
|
python-versions = ">=3.12,<4.0"
|
||||||
content-hash = "02530953efe65da1a788845cd43f8856be62db5bfb59de691cad813f57bab25e"
|
content-hash = "96080ea588b3ac52b19909379585cd647646faf3dce291f8d2b5801a3111c838"
|
||||||
|
|||||||
@ -20,10 +20,11 @@ dependencies = [
|
|||||||
"imagetext-py (>=2.2.0,<3.0.0)",
|
"imagetext-py (>=2.2.0,<3.0.0)",
|
||||||
"opencv-python-headless (>=4.12.0.88,<5.0.0.0)",
|
"opencv-python-headless (>=4.12.0.88,<5.0.0.0)",
|
||||||
"returns (>=0.26.0,<0.27.0)",
|
"returns (>=0.26.0,<0.27.0)",
|
||||||
"ptimeparse (>=0.1.1,<1.0.0)",
|
|
||||||
"skia-python (>=138.0,<139.0)",
|
"skia-python (>=138.0,<139.0)",
|
||||||
"nonebot-plugin-analysis-bilibili (>=2.8.1,<3.0.0)",
|
"nonebot-plugin-analysis-bilibili (>=2.8.1,<3.0.0)",
|
||||||
"qrcode (>=8.2,<9.0)",
|
"qrcode (>=8.2,<9.0)",
|
||||||
|
"ptimeparse (>=0.2.1,<0.3.0)",
|
||||||
|
"nanoid (>=2.0.0,<3.0.0)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
@ -41,4 +42,3 @@ url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
|
|||||||
priority = "primary"
|
priority = "primary"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
ptimeparse = { source = "pt-gitea-pypi" }
|
|
||||||
|
|||||||
Reference in New Issue
Block a user