Compare commits

...

4 Commits

Author SHA1 Message Date
f7cea196ec krgsay
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-28 15:31:12 +08:00
d4826e9e8b 配置两个 AI 功能都使用默认模型 2026-02-28 13:50:52 +08:00
33934ef7b5 让回复 celeste 允许不用带有前缀 2026-02-28 13:49:28 +08:00
f9f8ae4e67 调整 celeste 过度反应的 bug 2026-02-28 12:42:23 +08:00
5 changed files with 115 additions and 12 deletions

View File

@ -0,0 +1,38 @@
# Celeste
爬山小游戏,移植自 Ccleste是 Celeste Classic即 PICO-8版。
使用 `wasdxc` 和数字进行操作。
## 操作说明
`wsad` 是上下左右摇杆方向,或者是方向键。`c` 是跳跃键,`x` 是冲刺键。
使用空格分隔每一个操作,每个操作持续一帧。如果后面跟着数字,则持续那么多帧。
### 例子 1
```
xc 180
```
按下 xc 一帧,然后空置 180 帧。
### 例子 2
```
d10 cd d10 xdw d20
```
向右走 10 帧,向右跳一帧,再继续按下右 10 帧,按下向右上冲刺一帧,再按下右 20 帧。
## 指令使用说明
直接说 `celeste` 会开启一个新的游戏。但是,你需要在后面跟有操作,才能够渲染 gif 图出来。
一个常见的开始操作是直接发送 `celeste xc 130`,即按下 xc 两个按键触发 PICO 版的开始游戏,然后等待 130 秒动画播放完毕。
对于一个已经存在而且时间不是非常久远的 gif 图,只要是由 bot 自己发送出来的,就可以在它的基础上继续游戏。回复这条消息,可以继续游戏。
一种很常见的技巧是回复一个已经存在的 gif 图 `celeste 1`,此时会空操作一帧并且渲染画面。你可以用这种方法查看一个 gif 图的游戏目前的状态。

View File

@ -1,8 +1,9 @@
from pathlib import Path
import subprocess
import tempfile
from typing import Any
from loguru import logger
from nonebot import on_command
from nonebot import on_message
from pydantic import BaseModel
from nonebot.adapters import Event, Bot
@ -41,11 +42,12 @@ class CelesteStatus(BaseModel):
celeste_status = DataManager(CelesteStatus, DATA_PATH / "celeste-status.json")
cmd = on_command(cmd="celeste", aliases={"蔚蓝", "爬山", "鳌太线"})
# ↓ 这里的 Type Hinting 是为了能 fit 进去 set[str | tuple[str, ...]]
aliases: set[Any] = {"celeste", "蔚蓝", "爬山", "鳌太线"}
ALLOW_CHARS = "wasdxc0123456789 \t\n\r"
@cmd.handle()
async def _(msg: UniMsg, evt: Event, bot: Bot):
async def get_prev(evt: Event, bot: Bot) -> str | None:
prev = None
if isinstance(evt, OB11MessageEvent):
if evt.reply is not None:
@ -55,17 +57,37 @@ async def _(msg: UniMsg, evt: Event, bot: Bot):
if seg.type == 'reply':
msgid = seg.get('id')
prev = f"QQ:{bot.self_id}:" + str(msgid)
if prev is not None:
async with celeste_status.get_data() as data:
prev = data.records.get(prev)
return prev
actions = msg.extract_plain_text().strip().removeprefix("celeste")
for alias in {"蔚蓝", "爬山", "鳌太线"}:
async def match_celeste(evt: Event, bot: Bot, msg: UniMsg) -> bool:
prev = await get_prev(evt, bot)
text = msg.extract_plain_text().strip()
if any(text.startswith(a) for a in aliases):
return True
if prev is not None:
return True
return False
# cmd = on_command(cmd="celeste", aliases=aliases)
cmd = on_message(rule=match_celeste)
@cmd.handle()
async def _(msg: UniMsg, evt: Event, bot: Bot):
prev = await get_prev(evt, bot)
actions = msg.extract_plain_text().strip()
for alias in aliases:
actions = actions.removeprefix(alias)
actions = actions.strip()
if len(actions) == 0:
return
if prev is not None:
async with celeste_status.get_data() as data:
prev = data.records.get(prev)
if any((c not in ALLOW_CHARS) for c in actions):
return
await ensure_artifact(arti_ccleste_wrap_linux)
await ensure_artifact(arti_ccleste_wrap_windows)

View File

@ -7,7 +7,7 @@ class THQwen(TextHandler):
name = "qwen"
async def handle(self, env: TextHandlerEnvironment, istream: str | None, args: list[str]) -> TextHandleResult:
llm = get_llm("qwen3-max")
llm = get_llm()
messages = []
if istream is not None:

43
konabot/plugins/krgsay.py Normal file
View File

@ -0,0 +1,43 @@
import re
from typing import Any
from nonebot import on_message
from nonebot.adapters import Event
from nonebot_plugin_alconna import UniMessage, UniMsg
from playwright.async_api import Page
from konabot.common.nb import match_keyword
from konabot.common.web_render import WebRenderer, konaweb
async def render_image(message: str) -> UniMessage[Any]:
"""
渲染文本为图片
"""
async def page_function(page: Page):
await page.wait_for_function("typeof setContent === 'function'")
await page.evaluate(
"(message) => { return setContent(message); }",
message,
)
img_data = await WebRenderer.render(
url=konaweb("krgsay"),
target="#main",
other_function=page_function,
)
return UniMessage.image(raw=img_data)
cmd = on_message(
rule=match_keyword.match_keyword(
re.compile(r"^[kK][rR][gG][sS][aA][yY] .+"),
),
)
@cmd.handle()
async def _(event: Event, msg: UniMsg):
text = msg.extract_plain_text().strip()[6:].strip()
msg = await render_image(text)
await msg.send(event)

View File

@ -91,7 +91,7 @@ async def ask_ai(expression: str, now: datetime.datetime | None = None) -> tuple
logger.info(f"提醒功能:消息被阿里绿网拦截 message={expression}")
return None, ""
llm = get_llm("qwen3-max")
llm = get_llm()
message = await llm.chat([
{ "role": "system", "content": prompt },
{ "role": "user", "content": expression },