Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
"session.unwatch.ok": "Watch stopped.",
"session.unwatch.missing": "No matching watch owned by you.",
"session.unwatch.usage": "Usage: /session unwatch [listener|this] <target>. Omit the listener or write this for the current session.",
"session.watches.empty": "You have no active watches in this session.",
"session.watches.empty": "You have no active watches in {source}.",
"session.watches.body": "Your active watches:\n{watches}",
"session.watches.usage": "Usage: /session watches [listener|this]. Omit the listener or write this for the current session.",
"session.send.invalid": "Usage: /send <umo> <message or attachments>. Keep the command header in the message text.",
"session.send.accepted": "The target platform accepted the message.",
"session.send.partial": "Only part of the message was accepted. Resending may create duplicates.",
Expand Down Expand Up @@ -55,10 +56,12 @@
"bot.leave.ok": "Leaving the current group.",
"bot.leave.failed": "Failed to leave the group.",
"session.info.body": "UMO: {umo}\nUID: {user_id}\nSubject ID: {subject_id}\nUse UMO to set whitelist and configure routing. Use UID to set the admin list. Use Subject ID to bind roles on the authorization page.\n\nYour session information:\nBot ID: {platform_id}\nMessage Type: {message_type}\nSession ID: {session_id}{group_note}",
"session.info.target": "UMO: {umo}\nAuto name: {auto_name}\nAlias: {alias}\n\nSession information:\nBot ID: {platform_id}\nMessage Type: {message_type}\nSession ID: {session_id}",
"session.info.group": "\n\nGroup ID: {group_id}. Set this ID to whitelist to allow the entire group.",
"session.name.empty": "(empty)",
"session.name.usage": "Usage: /session name <name>\nUMO: {umo}\nAuto name: {auto_name}\nAlias: {alias}",
"session.name.usage": "Usage: /session name [--target UMO] <name>\nClear: /session name [--target UMO] --clear\nUMO: {umo}\nAuto name: {auto_name}\nAlias: {alias}",
"session.name.set": "UMO name set to: {alias}\nUMO: {umo}",
"session.name.cleared": "UMO alias cleared.\nUMO: {umo}",
"conversation.reset.ok": "Conversation reset.",
"conversation.reset.no_provider": "Cannot find any LLM provider. Configure one first.",
"conversation.reset.no_conversation": "You are not in a conversation. Use /conversation create to create one.",
Expand Down Expand Up @@ -98,6 +101,10 @@
"chat.status.disabled": "LLM chat is disabled for the current session.",
"chat.set.enabled": "LLM chat is now enabled for the current session.",
"chat.set.disabled": "LLM chat is now disabled for the current session.",
"tts.status.enabled": "TTS is enabled for the current session.",
"tts.status.disabled": "TTS is disabled for the current session.",
"tts.set.enabled": "TTS is now enabled for the current session.",
"tts.set.disabled": "TTS is now disabled for the current session.",
"flow.mode.unset": "unset (follow global)",
"flow.mode.on": "on",
"flow.mode.off": "off",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
"session.unwatch.ok": "已停止监听。",
"session.unwatch.missing": "没有找到属于你的该项监听。",
"session.unwatch.usage": "用法:/session unwatch [监听会话|this] <被监听会话>。监听会话可省略或写 this 表示当前会话。",
"session.watches.empty": "你在当前会话没有有效的监听。",
"session.watches.empty": "你在 {source} 没有有效的监听。",
"session.watches.body": "你的有效监听:\n{watches}",
"session.watches.usage": "用法:/session watches [监听会话|this]。监听会话可省略或写 this 表示当前会话。",
"session.send.invalid": "用法:/send <umo> <文字或附件>。请保留消息文字中的指令头。",
"session.send.accepted": "目标平台已接受消息。",
"session.send.partial": "目标平台仅接受了部分消息,重新发送可能造成重复。",
Expand Down Expand Up @@ -55,10 +56,12 @@
"bot.leave.ok": "正在退出当前群。",
"bot.leave.failed": "退群失败。",
"session.info.body": "UMO:{umo}\nUID:{user_id}\n授权主体:{subject_id}\nUMO 用于设置白名单和配置路由,UID 用于设置管理员列表,授权主体用于权限页绑定。\n\n当前会话信息:\nBot ID:{platform_id}\n消息类型:{message_type}\n会话 ID:{session_id}{group_note}",
"session.info.target": "UMO:{umo}\n自动名称:{auto_name}\n别名:{alias}\n\n会话信息:\nBot ID:{platform_id}\n消息类型:{message_type}\n会话 ID:{session_id}",
"session.info.group": "\n\n群 ID:{group_id}。将该 ID 加入白名单即可允许整个群。",
"session.name.empty": "(空)",
"session.name.usage": "用法:/session name <名称>\nUMO:{umo}\n自动名称:{auto_name}\n别名:{alias}",
"session.name.usage": "用法:/session name [--target UMO] <名称>\n清空:/session name [--target UMO] --clear\nUMO:{umo}\n自动名称:{auto_name}\n别名:{alias}",
"session.name.set": "UMO 名称已设为:{alias}\nUMO:{umo}",
"session.name.cleared": "已清除 UMO 别名。\nUMO:{umo}",
"conversation.reset.ok": "对话已重置。",
"conversation.reset.no_provider": "未找到 LLM Provider。请先配置。",
"conversation.reset.no_conversation": "当前不在对话中。使用 /conversation create 创建。",
Expand Down Expand Up @@ -98,6 +101,10 @@
"chat.status.disabled": "当前会话已停用 LLM 聊天。",
"chat.set.enabled": "当前会话的 LLM 聊天已启用。",
"chat.set.disabled": "当前会话的 LLM 聊天已停用。",
"tts.status.enabled": "当前会话已启用 TTS。",
"tts.status.disabled": "当前会话已停用 TTS。",
"tts.set.enabled": "当前会话的 TTS 已启用。",
"tts.set.disabled": "当前会话的 TTS 已停用。",
"flow.mode.unset": "未设置(跟随全局)",
"flow.mode.on": "开启",
"flow.mode.off": "关闭",
Expand Down
2 changes: 2 additions & 0 deletions astrbot/builtin_stars/builtin_commands/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .plugin import PluginCommands
from .provider import ProviderCommands
from .session import SessionCommands
from .tts import TtsCommands
from .variable import VariableCommands
from .work import WorkCommands

Expand All @@ -24,6 +25,7 @@
"PluginCommands",
"ProviderCommands",
"SessionCommands",
"TtsCommands",
"VariableCommands",
"WorkCommands",
]
28 changes: 7 additions & 21 deletions astrbot/builtin_stars/builtin_commands/commands/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from astrbot.api.event import AstrMessageEvent

from .reply import reply_i18n, send_i18n
from .target import resolve_target_umo

_SESSION_SERVICE_CONFIG = "session_service_config"

Expand Down Expand Up @@ -30,27 +31,12 @@ async def _resolve_target(
*,
action: str,
) -> str | None:
target = target.strip()
if not target:
return event.unified_msg_origin
if len(target.split()) != 1:
await reply_i18n(self.context, event, "bot.target.usage")
return None
if target.lower() == "this":
return event.unified_msg_origin
try:
decision = await self.context.authz.authorize_target_session(
event,
action=action,
umo=target,
)
except PermissionError, ValueError:
await reply_i18n(self.context, event, "bot.target.denied")
return None
if not decision.allowed:
await reply_i18n(self.context, event, "bot.target.denied")
return None
return target
return await resolve_target_umo(
self.context,
event,
target,
action=action,
)

async def status(self, event: AstrMessageEvent, target: str = "") -> None:
"""Show bot switches for the current or an explicitly selected session."""
Expand Down
108 changes: 97 additions & 11 deletions astrbot/builtin_stars/builtin_commands/commands/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from astrbot.api.platform import MAX_WATCH_TTL_SECONDS, MIN_WATCH_TTL_SECONDS

from .reply import reply_i18n
from .target import resolve_target_umo


def _resolve_listener(token: str, current_umo: str) -> str:
Expand Down Expand Up @@ -37,6 +38,16 @@ def parse_unwatch_spec(spec: str, current_umo: str) -> tuple[str, str]:
return _resolve_listener(parts[0], current_umo), parts[1]


def parse_watches_spec(spec: str, current_umo: str) -> str:
"""Parse `/session watches [listener|this]`."""
parts = spec.split()
if not parts:
return current_umo
if len(parts) > 1:
raise ValueError("Invalid watch arguments")
return _resolve_listener(parts[0], current_umo)


def _authorization_subject_id(event: AstrMessageEvent) -> str:
"""Return the authorization subject id for Dashboard binding import."""
attached = getattr(event, "subject", None)
Expand All @@ -59,9 +70,47 @@ class SessionCommands:
def __init__(self, context: star.PluginContext) -> None:
self.context = context

async def info(self, event: AstrMessageEvent) -> None:
"""Show identifiers and metadata for the current session."""
umo = event.unified_msg_origin
async def _auto_name(self, event: AstrMessageEvent, umo: str) -> str:
if umo == event.unified_msg_origin:
return self.context.sessions.auto_name(event)
saved = await self.context.sessions.alias(umo)
return self.context.sessions.normalize_name(
saved.auto_name if saved else "",
)

async def info(self, event: AstrMessageEvent, target: str = "") -> None:
"""Show identifiers and metadata for the current or selected session."""
umo = await resolve_target_umo(
self.context,
event,
target,
action="session.read_target",
)
if umo is None:
return
if umo != event.unified_msg_origin:
parsed = self.context.sessions.parse(umo)
saved = await self.context.sessions.alias(umo)
empty = await self.context.i18n.t(event, "session.name.empty")
await reply_i18n(
self.context,
event,
"session.info.target",
umo=umo,
auto_name=self.context.sessions.normalize_name(
saved.auto_name if saved else "",
)
or empty,
alias=self.context.sessions.normalize_name(
saved.user_alias if saved else "",
)
or empty,
platform_id=parsed["platform"],
message_type=parsed["message_type"],
session_id=parsed["session_id"],
)
return

group_id = event.get_group_id()
unique_session = bool(
self.context.config.get()["platform_settings"]["unique_session"]
Expand All @@ -86,12 +135,43 @@ async def info(self, event: AstrMessageEvent) -> None:
group_note=group_note,
)

async def name(self, event: AstrMessageEvent, alias: str) -> None:
"""Show or set the display name for the current session."""
umo = event.unified_msg_origin
auto_name = self.context.sessions.auto_name(event)
async def name(
self,
event: AstrMessageEvent,
alias: str,
target: str = "",
*,
clear: bool = False,
) -> None:
"""Show, set, or clear the display name for a session."""
alias = self.context.sessions.normalize_name(alias)
if clear and alias:
await reply_i18n(self.context, event, "session.name.usage")
return
umo = await resolve_target_umo(
self.context,
event,
target,
action="session.manage_target",
)
if umo is None:
return
auto_name = await self._auto_name(event, umo)
empty = await self.context.i18n.t(event, "session.name.empty")
if clear:
await self.context.sessions.set_alias(
umo=umo,
creator_sender_id=str(event.get_sender_id() or ""),
auto_name=auto_name,
user_alias=None,
)
await reply_i18n(
self.context,
event,
"session.name.cleared",
umo=umo,
)
return
if not alias:
saved_alias = await self.context.sessions.alias(umo)
user_alias = self.context.sessions.normalize_name(
Expand Down Expand Up @@ -181,19 +261,25 @@ async def unwatch(self, event: AstrMessageEvent, spec: str) -> None:
"session.unwatch.ok" if removed else "session.unwatch.missing",
)

async def watches(self, event: AstrMessageEvent) -> None:
"""List this actor's active watches in the current session."""
async def watches(self, event: AstrMessageEvent, spec: str = "") -> None:
"""List this actor's active watches for a listener session."""
try:
items = await self.context.bridges.list(event)
source_umo = parse_watches_spec(spec, event.unified_msg_origin)
items = await self.context.bridges.list(event, source_umo=source_umo)
except ValueError:
await reply_i18n(self.context, event, "session.watches.usage")
return
except PermissionError:
await reply_i18n(self.context, event, "session.bridge.denied")
return
await reply_i18n(
self.context,
event,
"session.watches.body" if items else "session.watches.empty",
source=source_umo,
watches="\n".join(
f"{item.target_umo} ({item.remaining_seconds}s)" for item in items
f"{item.source_umo} -> {item.target_umo} ({item.remaining_seconds}s)"
for item in items
),
)

Expand Down
35 changes: 35 additions & 0 deletions astrbot/builtin_stars/builtin_commands/commands/target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from astrbot.api import star
from astrbot.api.event import AstrMessageEvent

from .reply import reply_i18n


async def resolve_target_umo(
context: star.PluginContext,
event: AstrMessageEvent,
target: str,
*,
action: str,
) -> str | None:
"""Return the current or explicitly selected session UMO."""
target = target.strip()
if not target:
return event.unified_msg_origin
if len(target.split()) != 1:
await reply_i18n(context, event, "bot.target.usage")
return None
if target.lower() == "this":
return event.unified_msg_origin
try:
decision = await context.authz.authorize_target_session(
event,
action=action,
umo=target,
)
except PermissionError, ValueError:
await reply_i18n(context, event, "bot.target.denied")
return None
if not decision.allowed:
await reply_i18n(context, event, "bot.target.denied")
return None
return target
49 changes: 49 additions & 0 deletions astrbot/builtin_stars/builtin_commands/commands/tts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from astrbot.api import star
from astrbot.api.event import AstrMessageEvent

from .reply import reply_i18n


class TtsCommands:
def __init__(self, context: star.PluginContext) -> None:
self.context = context

async def status(self, event: AstrMessageEvent) -> None:
"""Show the TTS state for the current session."""
umo = event.unified_msg_origin
settings = await self.context.preferences.session_get(
umo,
"session_service_config",
{},
)
enabled = settings.get("tts_enabled", True)
await reply_i18n(
self.context,
event,
"tts.status.enabled" if enabled else "tts.status.disabled",
)

async def set_enabled(
self,
event: AstrMessageEvent,
enabled: bool,
) -> None:
"""Set the TTS state for the current session."""
umo = event.unified_msg_origin
settings = await self.context.preferences.session_get(
umo,
"session_service_config",
{},
)
settings = dict(settings or {})
settings["tts_enabled"] = enabled
await self.context.preferences.session_put(
umo,
"session_service_config",
settings,
)
await reply_i18n(
self.context,
event,
"tts.set.enabled" if enabled else "tts.set.disabled",
)
Loading
Loading