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 @@ -6,19 +6,26 @@
"commands": {
"session.bridge.denied": "This operation requires instance operator permission for both sessions in the same configuration.",
"session.watch.failed": "Cannot start the watch. Check the UMO and active watch limit.",
"session.watch.usage": "Usage: /session watch [listener|this] <target> [seconds]. Omit the listener or write this for the current session. Duration is 60–864000 seconds, default 43200.",
"session.watch.ttl_invalid": "Watch duration must be between 60 and 864000 seconds.",
"session.watch.usage": "Usage: /session watch [listener|this] <target> [seconds]. Omit the listener or write this for the current session. Duration is 1–864000 seconds, default 43200.",
"session.watch.ttl_invalid": "Watch duration must be between 1 and 864000 seconds.",
"session.watch.ok": "Watching {umo} into {source} for {seconds} seconds. New incoming messages will be shared with everyone in the listening session.",
"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 {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.connect.usage": "Usage: /session connect <UMO>. Omit the UMO to show the current unbounded link.",
"session.connect.usage": "Usage: /session connect <UMO>. Omit the UMO to show the current unbounded link. Extra tokens such as a duration are not accepted.",
"session.connect.failed": "Cannot connect. Check the UMO and active link limit.",
"session.connect.ok": "Connected to {umo}. Incoming messages will be forwarded without expiry. /send without a UMO uses this target.",
"session.connect.status": "Connected to {umo}. Incoming messages are forwarded without expiry. /send without a UMO uses this target.",
"session.links.empty": "No visible session-bridge rules.",
"session.links.body": "Session-bridge rules:\n{links}",
"session.links.ttl_unbounded": "unbounded",
"session.links.ttl_seconds": "{seconds}s",
"session.unlink.usage": "Usage: /session unlink <rule_id>. rule_id is 12 lowercase hex characters.",
"session.unlink.ok": "Removed that session-bridge rule.",
"session.unlink.missing": "No matching rule to remove.",
"session.disconnect.ok": "Session disconnected.",
"session.disconnect.missing": "No unbounded link in this session.",
"session.send.invalid": "Usage: /send <umo> <message or attachments>, or /send after /session connect. Keep the command header in the message text.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@
"commands": {
"session.bridge.denied": "此操作需要同一配置下两个会话的实例管理员权限。",
"session.watch.failed": "无法开始监听,请检查 UMO 和当前监听数量。",
"session.watch.usage": "用法:/session watch [监听会话|this] <被监听会话> [秒数]。监听会话可省略或写 this 表示当前会话。时长 60–864000 秒,默认 43200。",
"session.watch.ttl_invalid": "监听时长须在 60 到 864000 秒之间。",
"session.watch.usage": "用法:/session watch [监听会话|this] <被监听会话> [秒数]。监听会话可省略或写 this 表示当前会话。时长 1–864000 秒,默认 43200。",
"session.watch.ttl_invalid": "监听时长须在 1 到 864000 秒之间。",
"session.watch.ok": "已在 {source} 监听 {umo},有效期 {seconds} 秒。后续收到的消息将对监听会话所有成员可见。",
"session.unwatch.ok": "已停止监听。",
"session.unwatch.missing": "没有找到属于你的该项监听。",
"session.unwatch.usage": "用法:/session unwatch [监听会话|this] <被监听会话>。监听会话可省略或写 this 表示当前会话。",
"session.watches.empty": "你在 {source} 没有有效的监听。",
"session.watches.body": "你的有效监听:\n{watches}",
"session.watches.usage": "用法:/session watches [监听会话|this]。监听会话可省略或写 this 表示当前会话。",
"session.connect.usage": "用法:/session connect <UMO>。省略 UMO 可查看当前无期限连接。",
"session.connect.usage": "用法:/session connect <UMO>。省略 UMO 可查看当前无期限连接。不接受秒数或其他额外参数。",
"session.connect.failed": "无法连接,请检查 UMO 和当前连接数量。",
"session.connect.ok": "已连接到 {umo}。将无限期转发其新消息。不带 UMO 的 /send 会发往该会话。",
"session.connect.status": "当前已连接到 {umo}。将无限期转发其新消息。不带 UMO 的 /send 会发往该会话。",
"session.links.empty": "没有可见的会话桥接规则。",
"session.links.body": "会话桥接规则:\n{links}",
"session.links.ttl_unbounded": "无期限",
"session.links.ttl_seconds": "{seconds}秒",
"session.unlink.usage": "用法:/session unlink <rule_id>。rule_id 为 12 位小写十六进制。",
"session.unlink.ok": "已撤销该会话桥接规则。",
"session.unlink.missing": "没有找到可撤销的规则。",
"session.disconnect.ok": "已断开会话连接。",
"session.disconnect.missing": "当前会话没有无期限连接。",
"session.send.invalid": "用法:/send <umo> <文字或附件>,或先 /session connect 后再 /send。请保留消息文字中的指令头。",
Expand Down
58 changes: 58 additions & 0 deletions astrbot/builtin_stars/builtin_commands/commands/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ def parse_unwatch_spec(spec: str, current_umo: str) -> tuple[str, str]:
return _resolve_listener(parts[0], current_umo), parts[1]


def parse_unlink_spec(spec: str) -> str:
"""Parse `/session unlink <rule_id>`."""
parts = spec.split()
if len(parts) != 1:
raise ValueError("Invalid unlink arguments")
rule_id = parts[0]
if len(rule_id) != 12 or any(char not in "0123456789abcdef" for char in rule_id):
raise ValueError("Invalid unlink arguments")
return rule_id


def parse_watches_spec(spec: str, current_umo: str) -> str:
"""Parse `/session watches [listener|this]`."""
parts = spec.split()
Expand Down Expand Up @@ -332,6 +343,53 @@ async def connect(self, event: AstrMessageEvent, target: str) -> None:
umo=item.target_umo,
)

async def links(self, event: AstrMessageEvent) -> None:
"""List watch/connect edges visible to the current actor."""
try:
items = await self.context.bridges._manager.list_links(event)
except PermissionError:
await reply_i18n(self.context, event, "session.bridge.denied")
return
if not items:
await reply_i18n(self.context, event, "session.links.empty")
return
lines = []
for watch, kind in items:
if watch.expires_at is None:
ttl = await self.context.i18n.t(event, "session.links.ttl_unbounded")
else:
ttl = await self.context.i18n.t(
event,
"session.links.ttl_seconds",
seconds=watch.remaining_seconds,
)
lines.append(
f"{watch.rule_id} {kind} {watch.source_umo} -> {watch.target_umo} ({ttl})"
)
await reply_i18n(
self.context,
event,
"session.links.body",
links="\n".join(lines),
)

async def unlink(self, event: AstrMessageEvent, spec: str) -> None:
"""Remove a watch or connect by public id."""
try:
rule_id = parse_unlink_spec(spec)
removed = await self.context.bridges._manager.unlink(event, rule_id)
except ValueError:
await reply_i18n(self.context, event, "session.unlink.usage")
return
except PermissionError:
await reply_i18n(self.context, event, "session.bridge.denied")
return
await reply_i18n(
self.context,
event,
"session.unlink.ok" if removed else "session.unlink.missing",
)

async def disconnect(self, event: AstrMessageEvent) -> None:
"""Drop the unbounded link owned by the current actor."""
try:
Expand Down
14 changes: 14 additions & 0 deletions astrbot/builtin_stars/builtin_commands/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ async def session_disconnect(self, event: AstrMessageEvent) -> None:
"""Drop the unbounded session link for the current session."""
await self.session_c.disconnect(event)

@filter.permission("session.read")
@session.command("links")
async def session_links(self, event: AstrMessageEvent) -> None:
"""List visible session-bridge watch and connect rules."""
await self.session_c.links(event)

@filter.permission("session.read")
@session.command("unlink")
async def session_unlink(
self, event: AstrMessageEvent, spec: GreedyStr = GreedyStr("")
) -> None:
"""Remove a session-bridge rule by id."""
await self.session_c.unlink(event, spec)

@filter.permission("session.block")
@session.command("block")
async def session_block(
Expand Down
1 change: 1 addition & 0 deletions astrbot/core/core_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ async def _initialize(self) -> None:
self.execution_context.session_bridge_manager.observe
)
await self.platform_manager.initialize()
await self.execution_context.session_bridge_manager.restore()

# 初始化关闭控制面板的事件
self.dashboard_shutdown_event = asyncio.Event()
Expand Down
2 changes: 2 additions & 0 deletions astrbot/core/db/po/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
)
from astrbot.core.db.po.preferences import Preference
from astrbot.core.db.po.projects import ChatUIProject, SessionProjectRelation
from astrbot.core.db.po.session_bridge import SessionBridgeRule
from astrbot.core.db.po.sessions import PlatformSession, UmoAlias
from astrbot.core.db.po.statistics import PlatformStat, ProviderStat
from astrbot.core.db.po.webchat import WebChatThread
Expand Down Expand Up @@ -77,6 +78,7 @@
"PlatformStat",
"Preference",
"ProviderStat",
"SessionBridgeRule",
"SessionProjectRelation",
"UmoAlias",
"WebChatThread",
Expand Down
2 changes: 2 additions & 0 deletions astrbot/core/db/po/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from astrbot.core.db.po.preferences import Preference
from astrbot.core.db.po.projects import ChatUIProject, SessionProjectRelation
from astrbot.core.db.po.session_bridge import SessionBridgeRule
from astrbot.core.db.po.sessions import PlatformSession, UmoAlias
from astrbot.core.db.po.statistics import PlatformStat, ProviderStat
from astrbot.core.db.po.webchat import WebChatThread
Expand Down Expand Up @@ -73,6 +74,7 @@
PlatformStat,
Preference,
ProviderStat,
SessionBridgeRule,
SessionProjectRelation,
UmoAlias,
WebChatThread,
Expand Down
34 changes: 34 additions & 0 deletions astrbot/core/db/po/session_bridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from sqlmodel import JSON, Column, Field, SQLModel, UniqueConstraint

from astrbot.core.db.po.mixins import TimestampMixin


class SessionBridgeRule(TimestampMixin, SQLModel, table=True):
"""Persisted directed edge for a session-bridge watch, connect, or later pair."""

__tablename__ = "session_bridge_rules" # type: ignore

rule_id: str = Field(primary_key=True, max_length=12, nullable=False)
subject_id: str = Field(nullable=False, max_length=512, index=True)
source_umo: str = Field(nullable=False, max_length=512)
target_umo: str = Field(nullable=False, max_length=512)
source_config_id: str = Field(nullable=False, max_length=128, index=True)
target_config_id: str = Field(nullable=False, max_length=128, index=True)
kind: str = Field(nullable=False, max_length=32, index=True)
expires_at: int | None = Field(default=None)
header: bool = Field(default=True, nullable=False)
pair_id: str | None = Field(default=None, max_length=12)
match: dict = Field(default_factory=dict, sa_type=JSON, nullable=False)
except_: dict = Field(
default_factory=dict,
sa_column=Column("except", JSON, nullable=False),
)

__table_args__ = (
UniqueConstraint(
"subject_id",
"source_umo",
"target_umo",
name="uix_session_bridge_direction",
),
)
78 changes: 78 additions & 0 deletions astrbot/core/db/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
PlatformStat,
Preference,
ProviderStat,
SessionBridgeRule,
SessionProjectRelation,
UmoAlias,
WebChatThread,
Expand Down Expand Up @@ -911,6 +912,82 @@ async def get_umo_aliases(
) -> list[UmoAlias]: ...


@runtime_checkable
class SessionBridgeStore(Protocol):
"""Operations for persisted session-bridge directed edges."""

async def insert_session_bridge_rule(
self,
*,
subject_id: str,
source_umo: str,
target_umo: str,
source_config_id: str,
target_config_id: str,
kind: str,
expires_at: int | None = None,
header: bool = True,
pair_id: str | None = None,
match: dict | None = None,
except_: dict | None = None,
rule_id: str | None = None,
) -> SessionBridgeRule:
return T.cast("SessionBridgeRule", None)

async def get_session_bridge_rule(self, rule_id: str) -> SessionBridgeRule | None:
return None

async def get_session_bridge_rule_by_direction(
self,
subject_id: str,
source_umo: str,
target_umo: str,
) -> SessionBridgeRule | None:
return None

async def list_session_bridge_rules(self) -> list[SessionBridgeRule]:
return []

async def list_session_bridge_rules_by_subject(
self, subject_id: str
) -> list[SessionBridgeRule]:
return []

async def list_session_bridge_connects_for_listener(
self, subject_id: str, source_umo: str
) -> list[SessionBridgeRule]:
return []

async def list_session_bridge_rules_touching_config(
self, config_id: str
) -> list[SessionBridgeRule]:
return []

async def update_session_bridge_rule(
self,
rule_id: str,
*,
expires_at: int | None = None,
kind: str | None = None,
target_umo: str | None = None,
source_config_id: str | None = None,
target_config_id: str | None = None,
header: bool | None = None,
pair_id: str | None = None,
match: dict | None = None,
except_: dict | None = None,
) -> SessionBridgeRule | None:
return None

async def delete_session_bridge_rule(self, rule_id: str) -> None:
return None

async def delete_session_bridge_connects_for_listener(
self, subject_id: str, source_umo: str
) -> None:
return None


@runtime_checkable
class ChatProjectStore(Protocol):
"""Operations for ChatUI projects and their session relations."""
Expand Down Expand Up @@ -1056,6 +1133,7 @@ class DashboardStore(
"PlatformSessionStore",
"PluginRuntimeStore",
"PreferenceStore",
"SessionBridgeStore",
"SessionManagementStore",
"StatisticsStore",
"StatisticsSessionStore",
Expand Down
2 changes: 2 additions & 0 deletions astrbot/core/db/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from astrbot.core.db.stores.personas import PersonaStoreMixin
from astrbot.core.db.stores.preferences import PreferenceStoreMixin
from astrbot.core.db.stores.projects import ChatProjectStoreMixin
from astrbot.core.db.stores.session_bridge import SessionBridgeStoreMixin
from astrbot.core.db.stores.sessions import PlatformSessionStoreMixin
from astrbot.core.db.stores.statistics import StatisticsStoreMixin
from astrbot.core.db.stores.webchat import WebChatThreadStoreMixin
Expand All @@ -33,6 +34,7 @@ class SQLiteDatabase(
CommandStoreMixin,
CronStoreMixin,
PlatformSessionStoreMixin,
SessionBridgeStoreMixin,
UmoAliasStoreMixin,
ChatProjectStoreMixin,
BaseDatabase,
Expand Down
2 changes: 2 additions & 0 deletions astrbot/core/db/stores/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from astrbot.core.db.stores.personas import PersonaStoreMixin
from astrbot.core.db.stores.preferences import PreferenceStoreMixin
from astrbot.core.db.stores.projects import ChatProjectStoreMixin
from astrbot.core.db.stores.session_bridge import SessionBridgeStoreMixin
from astrbot.core.db.stores.sessions import PlatformSessionStoreMixin
from astrbot.core.db.stores.statistics import StatisticsStoreMixin
from astrbot.core.db.stores.webchat import WebChatThreadStoreMixin
Expand All @@ -29,6 +30,7 @@
"PersonaStoreMixin",
"PlatformSessionStoreMixin",
"PreferenceStoreMixin",
"SessionBridgeStoreMixin",
"StatisticsStoreMixin",
"UmoAliasStoreMixin",
"WebChatThreadStoreMixin",
Expand Down
Loading
Loading