mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-11-05 07:12:02 +08:00
feat(plugins): consolidate official plugins with independent releases (LANG-180) (#2797)
Co-authored-by: multica-agent <[email protected]>
This commit is contained in:
co-authored by
multica-agent
parent
d099033d79
commit
3a77306c3c
@@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from langbot_plugin.api.definition.plugin import BasePlugin
|
||||
|
||||
logger = logging.getLogger("KeywordAlert")
|
||||
|
||||
# Scope used when the active invocation carries no installation binding.
|
||||
LEGACY_SCOPE = ""
|
||||
|
||||
|
||||
class KeywordAlert(BasePlugin):
|
||||
"""Monitor group messages for keywords and alert admins via private message."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Cooldown tracking, keyed by installation scope then
|
||||
# (group_id, keyword): one object graph serves every installation of
|
||||
# this artifact digest, so a cooldown must never suppress a sibling
|
||||
# installation's alert. Entries are released in
|
||||
# on_installation_revoked().
|
||||
self._cooldowns: dict[str, dict[tuple[str, str], float]] = {}
|
||||
|
||||
async def initialize(self):
|
||||
# Process-scoped only: no tenant config or state is read here.
|
||||
logger.info("KeywordAlert plugin initialized")
|
||||
|
||||
async def on_installation_revoked(self, binding) -> None:
|
||||
"""Release one installation's cooldown table (no Host call is made here)."""
|
||||
self._cooldowns.pop(self._scope(binding), None)
|
||||
|
||||
def _scope(self, binding=None) -> str:
|
||||
"""Return the full installation scope of this invocation.
|
||||
|
||||
The whole binding is used so two Workspaces that reuse an installation
|
||||
UUID never share a cooldown. Dedicated placement has no binding and keeps
|
||||
its process-local scope.
|
||||
"""
|
||||
if binding is None:
|
||||
binding = self.get_installation_binding()
|
||||
if binding is None:
|
||||
return LEGACY_SCOPE
|
||||
return f"{binding.instance_uuid}:{binding.workspace_uuid}:{binding.installation_uuid}"
|
||||
|
||||
def parse_keywords(self) -> list[str]:
|
||||
raw = self.get_config().get("keywords", "")
|
||||
return [k.strip() for k in raw.split(",") if k.strip()]
|
||||
|
||||
def parse_group_ids(self) -> Optional[set[str]]:
|
||||
raw = self.get_config().get("group_ids", "")
|
||||
if not raw or not raw.strip():
|
||||
return None # Monitor all groups
|
||||
return {g.strip() for g in raw.split(",") if g.strip()}
|
||||
|
||||
def check_cooldown(self, group_id: str, keyword: str) -> bool:
|
||||
"""Returns True if alert is allowed (not in cooldown)."""
|
||||
config = self.get_config()
|
||||
cooldown = int(config.get("cooldown_seconds", 60))
|
||||
key = (group_id, keyword)
|
||||
now = time.time()
|
||||
table = self._cooldowns.setdefault(self._scope(), {})
|
||||
last = table.get(key, 0)
|
||||
if now - last < cooldown:
|
||||
return False
|
||||
table[key] = now
|
||||
return True
|
||||
|
||||
async def send_alert(self, group_id: str, sender_id: str, keyword: str, text: str):
|
||||
"""Send private alert to admin."""
|
||||
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
||||
|
||||
config = self.get_config()
|
||||
admin_id = config.get("admin_id", "")
|
||||
if not admin_id:
|
||||
logger.warning("No admin_id configured, skipping alert")
|
||||
return
|
||||
|
||||
bot_uuid = config.get("bot")
|
||||
if not bot_uuid:
|
||||
bots = await self.get_bots()
|
||||
if not bots:
|
||||
logger.warning("No bots available to send alert")
|
||||
return
|
||||
bot_uuid = bots[0]
|
||||
|
||||
alert_text = (
|
||||
f"🔔 关键词告警\n"
|
||||
f"━━━━━━━━━━━━━━\n"
|
||||
f"关键词: {keyword}\n"
|
||||
f"群组: {group_id}\n"
|
||||
f"发送者: {sender_id}\n"
|
||||
f"━━━━━━━━━━━━━━\n"
|
||||
f"{text[:500]}"
|
||||
)
|
||||
|
||||
chain = platform_message.MessageChain([
|
||||
platform_message.Plain(text=alert_text)
|
||||
])
|
||||
|
||||
try:
|
||||
await self.send_message(
|
||||
bot_uuid=bot_uuid,
|
||||
target_type="person",
|
||||
target_id=admin_id,
|
||||
message_chain=chain,
|
||||
)
|
||||
logger.info(f"Alert sent: keyword='{keyword}' group={group_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send alert: {e}")
|
||||
Reference in New Issue
Block a user