forked from mttu-developers/konabot
添加此方提醒的 CURD 和 ntfy 联动
This commit is contained in:
@ -1,25 +1,24 @@
|
||||
import aiohttp
|
||||
import asyncio as asynkio
|
||||
import datetime
|
||||
from math import ceil
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, cast
|
||||
from typing import Any, Literal
|
||||
|
||||
import nanoid
|
||||
import nonebot
|
||||
import ptimeparse
|
||||
from loguru import logger
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.adapters.console import Bot as ConsoleBot
|
||||
from nonebot.adapters.console.event import MessageEvent as ConsoleMessageEvent
|
||||
from nonebot.adapters.discord import Bot as DiscordBot
|
||||
from nonebot.adapters.discord.event import MessageEvent as DiscordMessageEvent
|
||||
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 nonebot import get_plugin_config, on_message
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.adapters.onebot.v11 import Bot as OBBot
|
||||
from nonebot.adapters.console import Bot as CBot
|
||||
from nonebot.adapters.discord import Bot as DCBot
|
||||
from nonebot_plugin_alconna import Alconna, Args, Subcommand, UniMessage, UniMsg, on_alconna
|
||||
from pydantic import BaseModel
|
||||
|
||||
from konabot.common.longtask import DepLongTaskTarget, LongTask, LongTaskTarget, create_longtask, handle_long_task, longtask_data
|
||||
|
||||
evt = on_message()
|
||||
|
||||
(Path(__file__).parent.parent.parent.parent / "data").mkdir(exist_ok=True)
|
||||
@ -27,6 +26,14 @@ DATA_FILE_PATH = Path(__file__).parent.parent.parent.parent / "data" / "notify.j
|
||||
DATA_FILE_LOCK = asynkio.Lock()
|
||||
|
||||
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):
|
||||
@ -41,14 +48,64 @@ class Notify(BaseModel):
|
||||
notify_time: datetime.datetime
|
||||
notify_msg: str
|
||||
|
||||
def get_str(self):
|
||||
return f"{self.target}-{self.target_env}-{self.platform}-{self.notify_time}"
|
||||
|
||||
|
||||
class NotifyConfigFile(BaseModel):
|
||||
version: int = 2
|
||||
notifies: 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:
|
||||
@ -65,89 +122,8 @@ def save_notify_config(config: NotifyConfigFile):
|
||||
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(
|
||||
f"代办提醒被信号中止,任务退出 NOTIFY={notify.notify_msg} TIME={notify.notify_time}"
|
||||
)
|
||||
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()
|
||||
async def _(msg: UniMsg, mEvt: Event):
|
||||
async def _(msg: UniMsg, mEvt: Event, target: DepLongTaskTarget):
|
||||
if mEvt.get_user_id() in nonebot.get_bots():
|
||||
return
|
||||
|
||||
@ -160,62 +136,26 @@ async def _(msg: UniMsg, mEvt: Event):
|
||||
return
|
||||
|
||||
notify_time, notify_text = segments
|
||||
# target_time = get_target_time(notify_time)
|
||||
try:
|
||||
# target_time = ptimeparse.parse(notify_time)
|
||||
target_time = ptimeparse.Parser().parse(notify_time)
|
||||
logger.info(f"从 {notify_time} 解析出了时间:{target_time}")
|
||||
except Exception:
|
||||
logger.info(f"无法从 {notify_time} 中解析出时间")
|
||||
return
|
||||
# if target_time is None:
|
||||
# logger.info(f"无法从 {notify_time} 中解析出时间")
|
||||
# return
|
||||
if not notify_text:
|
||||
return
|
||||
|
||||
await DATA_FILE_LOCK.acquire()
|
||||
cfg = load_notify_config()
|
||||
|
||||
if isinstance(mEvt, ConsoleMessageEvent):
|
||||
platform = "console"
|
||||
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,
|
||||
await create_longtask(
|
||||
LONG_TASK_NAME,
|
||||
{ "message": notify_text },
|
||||
target,
|
||||
target_time,
|
||||
)
|
||||
create_notify_task(notify)
|
||||
|
||||
cfg.notifies.append(notify)
|
||||
save_notify_config(cfg)
|
||||
DATA_FILE_LOCK.release()
|
||||
|
||||
await evt.send(
|
||||
await UniMessage()
|
||||
.at(mEvt.get_user_id())
|
||||
.text(f" 了解啦!将会在 {notify.notify_time} 提醒你哦~")
|
||||
.export()
|
||||
await target.send_message(
|
||||
UniMessage().text(f"了解啦!将会在 {target_time.strftime(FMT_STRING)} 提醒你哦~")
|
||||
)
|
||||
logger.info(f"创建了一条于 {notify.notify_time} 的代办提醒")
|
||||
logger.info(f"创建了一条于 {target_time} 的代办提醒")
|
||||
|
||||
|
||||
driver = nonebot.get_driver()
|
||||
@ -238,19 +178,152 @@ async def _():
|
||||
|
||||
await DATA_FILE_LOCK.acquire()
|
||||
|
||||
# tasks: set[asynkio.Task[Any]] = set()
|
||||
cfg = load_notify_config()
|
||||
if cfg.version == 1:
|
||||
logger.info("将配置文件的版本升级为 2")
|
||||
cfg.version = 2
|
||||
else:
|
||||
counter = 0
|
||||
for notify in [*cfg.notifies]:
|
||||
task = create_notify_task(notify, fail2remove=False)
|
||||
ASYNK_TASKS.add(task)
|
||||
task.add_done_callback(lambda self: ASYNK_TASKS.remove(self))
|
||||
counter += 1
|
||||
logger.info(f"成功创建了 {counter} 条代办事项")
|
||||
await create_longtask(
|
||||
handler=LONG_TASK_NAME,
|
||||
data={ "message": notify.notify_msg },
|
||||
target=get_target_from_notify(notify),
|
||||
deadline=notify.notify_time,
|
||||
)
|
||||
cfg.notifies = []
|
||||
save_notify_config(cfg)
|
||||
DATA_FILE_LOCK.release()
|
||||
|
||||
|
||||
@handle_long_task("TASK_SIMPLE_NOTIFY")
|
||||
async def _(task: LongTask):
|
||||
message = task.data["message"]
|
||||
await task.target.send_message(
|
||||
UniMessage().text(f"代办提醒:{message}")
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
USER_CHECKOUT_TASK_CACHE: dict[str, dict[str, str]] = {}
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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."))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user