From d2ec53ec91dbfeefe3790cdf3e46222b7594c88c Mon Sep 17 00:00:00 2001 From: Hyu Date: Sat, 10 Oct 2026 21:29:49 +0800 Subject: [PATCH] feat(dingtalk): support group subscriptions and card interaction events --- .../pkg/platform/adapters/dingtalk/adapter.py | 12 ++- .../adapters/dingtalk/event_converter.py | 13 +++- .../platform/adapters/dingtalk/manifest.yaml | 4 + .../adapters/dingtalk/subscriptions.py | 77 +++++++++++++++++++ .../platform/test_dingtalk_eba_adapter.py | 45 ++++++++++- .../agents/components/debug-event-data.ts | 4 + .../bot-form/EventBindingsEditor.tsx | 9 ++- web/src/i18n/locales/en-US.ts | 7 +- web/src/i18n/locales/es-ES.ts | 7 +- web/src/i18n/locales/ja-JP.ts | 7 +- web/src/i18n/locales/ru-RU.ts | 7 +- web/src/i18n/locales/th-TH.ts | 7 +- web/src/i18n/locales/vi-VN.ts | 7 +- web/src/i18n/locales/zh-Hans.ts | 6 +- web/src/i18n/locales/zh-Hant.ts | 6 +- 15 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 src/langbot/pkg/platform/adapters/dingtalk/subscriptions.py diff --git a/src/langbot/pkg/platform/adapters/dingtalk/adapter.py b/src/langbot/pkg/platform/adapters/dingtalk/adapter.py index 2a7720130..bb175f59e 100644 --- a/src/langbot/pkg/platform/adapters/dingtalk/adapter.py +++ b/src/langbot/pkg/platform/adapters/dingtalk/adapter.py @@ -7,6 +7,8 @@ import typing import dingtalk_stream import pydantic +from .subscriptions import DingTalkSubscriptionHandler + from langbot.libs.dingtalk_api.api import DingTalkClient, is_stream_card_configured from langbot.libs.dingtalk_api.dingtalkevent import DingTalkEvent import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter @@ -40,6 +42,7 @@ class DingTalkCardCallbackHandler(dingtalk_stream.CallbackHandler): { 'conversation_type': 'CardCallback', 'Type': 'card_callback', + 'timestamp': float(message.headers.time or 0) / 1000, 'CardCallback': { 'extension': callback.extension, 'corp_id': callback.corp_id, @@ -107,6 +110,10 @@ class DingTalkAdapter(DingTalkAPIMixin, abstract_platform_adapter.AbstractPlatfo return [ 'message.received', 'feedback.received', + 'group.member_joined', + 'group.member_left', + 'group.info_updated', + 'dingtalk.card_action', 'platform.specific', ] @@ -280,6 +287,7 @@ class DingTalkAdapter(DingTalkAPIMixin, abstract_platform_adapter.AbstractPlatfo async def on_message(event: DingTalkEvent): await self._handle_native_event(event) + self.bot.client.register_all_event_handler(DingTalkSubscriptionHandler(self)) self.bot.on_message('FriendMessage')(on_message) self.bot.on_message('GroupMessage')(on_message) self.bot.client.register_callback_handler( @@ -298,7 +306,9 @@ class DingTalkAdapter(DingTalkAPIMixin, abstract_platform_adapter.AbstractPlatfo 'DingTalk event received: ' f'conversation={event.conversation}, message_id={getattr(event.incoming_message, "message_id", None)}' ) - if platform_events.FriendMessage in self.listeners or platform_events.GroupMessage in self.listeners: + if event.conversation in {'FriendMessage', 'GroupMessage'} and ( + platform_events.FriendMessage in self.listeners or platform_events.GroupMessage in self.listeners + ): legacy_event = await self.event_converter.target2legacy(event, self.bot_account_id) if legacy_event: callback = self.listeners.get(type(legacy_event)) diff --git a/src/langbot/pkg/platform/adapters/dingtalk/event_converter.py b/src/langbot/pkg/platform/adapters/dingtalk/event_converter.py index 41a97ba6f..dd7798b2d 100644 --- a/src/langbot/pkg/platform/adapters/dingtalk/event_converter.py +++ b/src/langbot/pkg/platform/adapters/dingtalk/event_converter.py @@ -23,7 +23,18 @@ class DingTalkEventConverter(abstract_platform_adapter.AbstractEventConverter): feedback = DingTalkEventConverter.card_callback_to_feedback(event) if feedback is not None: return feedback - return DingTalkEventConverter.platform_specific(event, 'card.callback') + callback = event.get('CardCallback') or {} + content = callback.get('content') or {} + return platform_events.DingTalkCardActionEvent( + adapter_name=ADAPTER_NAME, + user=platform_entities.User(id=str(callback.get('user_id') or '')), + space_id=str(callback.get('space_id') or ''), + card_instance_id=str(callback.get('card_instance_id') or ''), + action=str(content.get('action') or content.get('actionKey') or ''), + data=callback, + timestamp=float(event.get('timestamp') or 0), + source_platform_object=event, + ) return DingTalkEventConverter.platform_specific(event, f'message.{event.conversation or "unknown"}') @staticmethod diff --git a/src/langbot/pkg/platform/adapters/dingtalk/manifest.yaml b/src/langbot/pkg/platform/adapters/dingtalk/manifest.yaml index 24adcb857..11a0b0e1f 100644 --- a/src/langbot/pkg/platform/adapters/dingtalk/manifest.yaml +++ b/src/langbot/pkg/platform/adapters/dingtalk/manifest.yaml @@ -151,6 +151,10 @@ spec: supported_events: - message.received - feedback.received + - group.member_joined + - group.member_left + - group.info_updated + - dingtalk.card_action - platform.specific supported_apis: diff --git a/src/langbot/pkg/platform/adapters/dingtalk/subscriptions.py b/src/langbot/pkg/platform/adapters/dingtalk/subscriptions.py new file mode 100644 index 000000000..fb6aad958 --- /dev/null +++ b/src/langbot/pkg/platform/adapters/dingtalk/subscriptions.py @@ -0,0 +1,77 @@ +"""Translate DingTalk Stream EVENT envelopes, keeping unknown payloads intact.""" + +import asyncio +from collections import OrderedDict + +import dingtalk_stream +from langbot_plugin.api.entities.builtin.platform import entities, events + +from .types import ADAPTER_NAME + + +def subscription_events(message): + data = message.data + headers = message.headers + action = headers.event_type or data.get('EventType') or data.get('eventType') or '' + raw = {'headers': vars(headers).copy(), 'data': data} + stamp = headers.event_born_time or data.get('TimeStamp') or data.get('EventTime') or data.get('timestamp') or headers.time or 0 + stamp = float(stamp) + common = dict( + adapter_name=ADAPTER_NAME, + timestamp=stamp / 1000 if stamp > 10_000_000_000 else stamp, + source_platform_object=raw, + ) + group_id = data.get('OpenConversationId') or data.get('openConversationId') or data.get('chatId') or data.get('ChatId') + group = entities.UserGroup(id=str(group_id or ''), name=str(data.get('Title') or data.get('title') or '')) + owner_id = data.get('Owner') or data.get('owner') + if owner_id: + group.owner_id = str(owner_id) + operator_id = data.get('Operator') or data.get('operator') + operator = entities.User(id=str(operator_id)) if operator_id else None + members = data.get('UserId') or data.get('userId') or [] + if isinstance(members, str): + members = [members] + if group_id and members and action in {'chat_add_member', 'chat_remove_member', 'chat_quit'}: + for member_id in dict.fromkeys(members): + member = entities.User(id=str(member_id)) + if action == 'chat_add_member': + yield events.MemberJoinedEvent(group=group, member=member, inviter=operator, **common) + else: + yield events.MemberLeftEvent( + group=group, member=member, operator=operator, + is_kicked=action == 'chat_remove_member', **common, + ) + elif group_id and action in {'chat_update_title', 'chat_update_owner'}: + yield events.GroupInfoUpdatedEvent( + group=group, operator=operator, + changed_fields=['name' if action == 'chat_update_title' else 'owner_id'], **common, + ) + else: + yield events.PlatformSpecificEvent(action=action or 'unknown', data=raw, **common) + + +class DingTalkSubscriptionHandler(dingtalk_stream.EventHandler): + def __init__(self, adapter): + super().__init__() + self.adapter = adapter + self.completed = OrderedDict() + self.lock = asyncio.Lock() + + async def process(self, message): + # Serialize retries; mark each fan-out event only after successful dispatch. + async with self.lock: + try: + identity = message.headers.event_id or message.headers.message_id + for index, event in enumerate(subscription_events(message)): + key = (message.headers.event_corp_id, identity, index) + if identity and key in self.completed: + continue + await self.adapter._dispatch_eba_event(event) + if identity: + self.completed[key] = None + while len(self.completed) > 4096: + self.completed.popitem(last=False) + return dingtalk_stream.AckMessage.STATUS_OK, 'OK' + except Exception: + self.logger.exception('DingTalk subscription dispatch failed') + return 500, 'Event dispatch failed' diff --git a/tests/unit_tests/platform/test_dingtalk_eba_adapter.py b/tests/unit_tests/platform/test_dingtalk_eba_adapter.py index 81ac54c51..f1338915f 100644 --- a/tests/unit_tests/platform/test_dingtalk_eba_adapter.py +++ b/tests/unit_tests/platform/test_dingtalk_eba_adapter.py @@ -3,7 +3,7 @@ from __future__ import annotations import json import pathlib from types import SimpleNamespace -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch import pytest import yaml @@ -38,7 +38,7 @@ class DummyLogger(AbstractEventLogger): class DummyDingTalkClient(DingTalkClient): def __init__(self, *args, **kwargs): self._message_handlers = {} - self.client = SimpleNamespace(register_callback_handler=AsyncMock()) + self.client = SimpleNamespace(register_callback_handler=Mock(), register_all_event_handler=Mock()) self.markdown_card = kwargs.get('markdown_card', True) self.access_token = '' self.send_message = AsyncMock() @@ -287,8 +287,8 @@ async def test_dingtalk_event_converter_maps_card_feedback(): assert feedback.session_id == 'space-1' assert feedback.message_id == 'bot-msg-1' - assert isinstance(callback, platform_events.PlatformSpecificEvent) - assert callback.action == 'card.callback' + assert isinstance(callback, platform_events.DingTalkCardActionEvent) + assert callback.action == 'open_details' @pytest.mark.asyncio @@ -522,3 +522,40 @@ async def test_dingtalk_adapter_dispatches_native_input_submission(): assert calls[0].data['callback_token'] == 'callback-token' assert calls[0].data['values'] == {'score': 42.5} assert result['message_id'] not in adapter.interaction_callback_contexts + + +def test_dingtalk_subscription_member_and_group_mapping(): + from dingtalk_stream.frames import EventMessage + from langbot.pkg.platform.adapters.dingtalk.subscriptions import subscription_events + + message = EventMessage() + message.headers.event_type = 'chat_add_member' + message.data = {'OpenConversationId': 'group', 'UserId': ['a', 'b']} + converted = list(subscription_events(message)) + assert [event.member.id for event in converted] == ['a', 'b'] + assert all(event.type == 'group.member_joined' for event in converted) + message.headers.event_type = 'chat_update_owner' + message.data = {'OpenConversationId': 'group', 'Owner': 'new-owner'} + event = list(subscription_events(message))[0] + assert event.changed_fields == ['owner_id'] + assert event.group.owner_id == 'new-owner' + message.headers.event_type = 'chat_add_member' + message.data = {'chatId': 'group', 'unionId': ['union-id']} + assert list(subscription_events(message))[0].type == 'platform.specific' + + +@pytest.mark.asyncio +async def test_dingtalk_subscription_retry_does_not_repeat_successful_members(): + from dingtalk_stream.frames import EventMessage + from langbot.pkg.platform.adapters.dingtalk.subscriptions import DingTalkSubscriptionHandler + + message = EventMessage() + message.headers.event_id = 'event-1' + message.headers.event_type = 'chat_add_member' + message.data = {'OpenConversationId': 'group', 'UserId': ['a', 'b']} + adapter = SimpleNamespace(_dispatch_eba_event=AsyncMock(side_effect=[None, RuntimeError('retry'), None])) + handler = DingTalkSubscriptionHandler(adapter) + assert (await handler.process(message))[0] == 500 + assert (await handler.process(message))[0] == 200 + assert (await handler.process(message))[0] == 200 + assert [call.args[0].member.id for call in adapter._dispatch_eba_event.await_args_list] == ['a', 'b', 'b'] diff --git a/web/src/app/home/agents/components/debug-event-data.ts b/web/src/app/home/agents/components/debug-event-data.ts index 369499015..a9cedc17a 100644 --- a/web/src/app/home/agents/components/debug-event-data.ts +++ b/web/src/app/home/agents/components/debug-event-data.ts @@ -90,6 +90,10 @@ const DEBUG_EVENT_DEFINITIONS: Record = { messageField: 'text', }, 'message.deleted': deletion, + 'dingtalk.card_action': { + fields: [user], + defaults: { space_id: 'dtv1.card//IM_GROUP.debug-group', card_instance_id: 'debug-card', action: 'confirm', data: {} }, + }, 'wecombot.enter_chat': { fields: [user], defaults: { chat_id: 'debug-user', chat_type: 'private', message_id: 'debug-entry', data: {} }, diff --git a/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx b/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx index 223d8824a..29db8f508 100644 --- a/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx +++ b/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx @@ -331,7 +331,14 @@ function eventLabel(event: string, t: TFunction) { function eventDescription(event: string, t: TFunction) { if (event === '*') return t('bots.eventDescriptions.all'); - if (event.endsWith('.*')) return t('bots.eventDescriptions.namespace'); + if (event.endsWith('.*')) { + const group = event.slice(0, -2); + const key = `bots.eventDescriptions.namespace_${group}`; + const description = t(key); + return description === key + ? t('bots.eventDescriptions.namespace', { group }) + : description; + } const key = `bots.eventDescriptions.${event.replace(/\./g, '_')}`; const description = t(key); return description === key ? t('bots.eventDescriptions.custom') : description; diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index b5c071468..1c8414f13 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -1005,6 +1005,7 @@ const enUS = { eventNames: { wecombot_enter_chat: "User entered bot chat", wecombot_template_card_event: "Template card interaction", + dingtalk_card_action: "DingTalk card interaction", wecomcs_enter_session: "Customer entered conversation", wecomcs_msg_send_fail: "Customer service message failed", wecomcs_servicer_status_change: "Representative availability changed", @@ -1019,6 +1020,7 @@ const enUS = { friend_added: 'Friend added', group_member_joined: 'Member joined group', group_member_left: 'Member left group', + group_info_updated: "Group information updated", group_member_banned: 'Member banned', bot_invited_to_group: 'Bot invited to group', bot_removed_from_group: 'Bot removed from group', @@ -1029,6 +1031,7 @@ const enUS = { eventDescriptions: { wecombot_enter_chat: "A user opened the bot chat. Use this event for welcome workflows.", wecombot_template_card_event: "A user clicked or submitted a template card. Managed form callbacks are handled separately.", + dingtalk_card_action: "Card clicks and submissions; feedback and managed forms are handled separately.", wecomcs_enter_session: "A customer opened a service link; includes entry source and welcome code.", wecomcs_msg_send_fail: "WeChat reported an outgoing message failure, with message ID and failure type.", wecomcs_servicer_status_change: "A representative started, stopped or suspended service.", @@ -1040,8 +1043,7 @@ const enUS = { 'Matches bot invitations, removals, mutes, and other status events.', namespace_feedback: 'Matches feedback events from a platform or user.', namespace_friend: 'Matches friend requests and friendship changes.', - namespace_group: - 'Matches member joins, leaves, removals, and other group events.', + namespace_group: "Matches group events such as membership changes and group information updates.", namespace_message: 'Matches received, edited, deleted, and reaction message events.', namespace_platform: @@ -1057,6 +1059,7 @@ const enUS = { friend_added: 'A friend relationship was created.', group_member_joined: 'A member joins a group where the bot is present.', group_member_left: 'A member leaves a group where the bot is present.', + group_info_updated: "Group details such as its name or owner have changed.", group_member_banned: 'A group member is banned or removed.', bot_invited_to_group: 'The bot is invited to a group.', bot_removed_from_group: 'The bot is removed from a group.', diff --git a/web/src/i18n/locales/es-ES.ts b/web/src/i18n/locales/es-ES.ts index cc05428a1..0b9eafa91 100644 --- a/web/src/i18n/locales/es-ES.ts +++ b/web/src/i18n/locales/es-ES.ts @@ -1024,6 +1024,7 @@ const esES = { eventNames: { wecombot_enter_chat: "Usuario entró al chat del bot", wecombot_template_card_event: "Interacción con tarjeta", + dingtalk_card_action: "Interacción con tarjeta de DingTalk", wecomcs_enter_session: "Cliente entró en la conversación", wecomcs_msg_send_fail: "Fallo de envío de atención al cliente", wecomcs_servicer_status_change: "Disponibilidad del agente modificada", @@ -1038,6 +1039,7 @@ const esES = { friend_added: 'Amigo añadido', group_member_joined: 'Miembro unido al grupo', group_member_left: 'Miembro salió del grupo', + group_info_updated: "Información del grupo actualizada", group_member_banned: 'Miembro bloqueado', bot_invited_to_group: 'Bot invitado al grupo', bot_removed_from_group: 'Bot eliminado del grupo', @@ -1048,6 +1050,7 @@ const esES = { eventDescriptions: { wecombot_enter_chat: "Un usuario abrió el chat del bot. Úsalo para flujos de bienvenida.", wecombot_template_card_event: "Un usuario pulsó o envió una tarjeta. Las respuestas de formularios gestionados se procesan por separado.", + dingtalk_card_action: "Clics y envíos de tarjetas; las valoraciones y los formularios gestionados se procesan por separado.", wecomcs_enter_session: "Un cliente abrió el enlace de atención; incluye origen y código de bienvenida.", wecomcs_msg_send_fail: "WeChat notificó un fallo de envío con el ID y el tipo de error.", wecomcs_servicer_status_change: "Un agente inició, detuvo o suspendió la atención.", @@ -1060,8 +1063,7 @@ const esES = { namespace_feedback: 'Coincide con comentarios recibidos de la plataforma o de usuarios.', namespace_friend: 'Coincide con solicitudes y cambios de amistad.', - namespace_group: - 'Coincide con entradas, salidas, expulsiones y otros eventos de grupo.', + namespace_group: "Coincide con cambios de miembros y actualizaciones de información del grupo.", namespace_message: 'Coincide con mensajes recibidos, editados, eliminados y reacciones.', namespace_platform: @@ -1078,6 +1080,7 @@ const esES = { friend_added: 'Se ha creado una relación de amistad.', group_member_joined: 'Un miembro se une a un grupo donde está el bot.', group_member_left: 'Un miembro abandona un grupo donde está el bot.', + group_info_updated: "Han cambiado datos del grupo, como su nombre o propietario.", group_member_banned: 'Un miembro del grupo es bloqueado o expulsado.', bot_invited_to_group: 'El bot es invitado a un grupo.', bot_removed_from_group: 'El bot es expulsado de un grupo.', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 6a51d3817..39f301ce7 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -1010,6 +1010,7 @@ const jaJP = { eventNames: { wecombot_enter_chat: "ボットとのチャットに参加", wecombot_template_card_event: "テンプレートカード操作", + dingtalk_card_action: "DingTalk カード操作", wecomcs_enter_session: "顧客が会話に参加", wecomcs_msg_send_fail: "カスタマーサービスの送信失敗", wecomcs_servicer_status_change: "担当者の受付状態変更", @@ -1024,6 +1025,7 @@ const jaJP = { friend_added: '友達追加', group_member_joined: 'メンバー参加', group_member_left: 'メンバー退出', + group_info_updated: "グループ情報更新", group_member_banned: 'メンバーBAN', bot_invited_to_group: 'ボットがグループに招待された', bot_removed_from_group: 'ボットがグループから削除された', @@ -1034,6 +1036,7 @@ const jaJP = { eventDescriptions: { wecombot_enter_chat: "ユーザーがボットのチャットを開きました。歓迎や案内のフローに使用できます。", wecombot_template_card_event: "ユーザーがカードをクリックまたは送信しました。管理対象フォームの応答は別途処理します。", + dingtalk_card_action: "カードのクリックや送信。フィードバックと管理対象フォームは別途処理します。", wecomcs_enter_session: "顧客がリンクから会話を開きました。流入元と歓迎メッセージ用コードを含みます。", wecomcs_msg_send_fail: "WeChat が送信失敗を通知しました。メッセージ ID と失敗種別を含みます。", wecomcs_servicer_status_change: "担当者が受付を開始、停止、または一時停止しました。", @@ -1046,8 +1049,7 @@ const jaJP = { namespace_feedback: 'プラットフォームまたはユーザーからのフィードバックイベントに一致します。', namespace_friend: '友達リクエストや友達関係の変更イベントに一致します。', - namespace_group: - 'メンバーの参加、退出、削除などのグループイベントに一致します。', + namespace_group: "メンバーの変更やグループ情報の更新などに一致します。", namespace_message: 'メッセージの受信、編集、削除、リアクションイベントに一致します。', namespace_platform: @@ -1067,6 +1069,7 @@ const jaJP = { friend_added: '友達関係が作成されました。', group_member_joined: 'ボットがいるグループにメンバーが参加しました。', group_member_left: 'ボットがいるグループからメンバーが退出しました。', + group_info_updated: "グループ名や所有者などの情報が変更されました。", group_member_banned: 'グループメンバーがBANまたは削除されました。', bot_invited_to_group: 'ボットがグループに招待されました。', bot_removed_from_group: 'ボットがグループから削除されました。', diff --git a/web/src/i18n/locales/ru-RU.ts b/web/src/i18n/locales/ru-RU.ts index 482830ede..2e8e73fcd 100644 --- a/web/src/i18n/locales/ru-RU.ts +++ b/web/src/i18n/locales/ru-RU.ts @@ -1020,6 +1020,7 @@ const ruRU = { eventNames: { wecombot_enter_chat: "Пользователь открыл чат бота", wecombot_template_card_event: "Взаимодействие с карточкой", + dingtalk_card_action: "Взаимодействие с карточкой DingTalk", wecomcs_enter_session: "Клиент вошёл в диалог", wecomcs_msg_send_fail: "Ошибка отправки сообщения поддержки", wecomcs_servicer_status_change: "Статус оператора изменён", @@ -1034,6 +1035,7 @@ const ruRU = { friend_added: 'Друг добавлен', group_member_joined: 'Участник вступил в группу', group_member_left: 'Участник покинул группу', + group_info_updated: "Информация о группе обновлена", group_member_banned: 'Участник заблокирован', bot_invited_to_group: 'Бот приглашён в группу', bot_removed_from_group: 'Бот удалён из группы', @@ -1044,6 +1046,7 @@ const ruRU = { eventDescriptions: { wecombot_enter_chat: "Пользователь открыл чат бота. Событие подходит для сценариев приветствия.", wecombot_template_card_event: "Пользователь нажал или отправил карточку. Ответы управляемых форм обрабатываются отдельно.", + dingtalk_card_action: "Нажатия и отправка карточек; отзывы и управляемые формы обрабатываются отдельно.", wecomcs_enter_session: "Клиент открыл ссылку поддержки; событие содержит источник и код приветствия.", wecomcs_msg_send_fail: "WeChat сообщил об ошибке отправки с ID сообщения и типом ошибки.", wecomcs_servicer_status_change: "Оператор начал, остановил или приостановил приём обращений.", @@ -1057,8 +1060,7 @@ const ruRU = { 'Соответствует обратной связи от платформы или пользователя.', namespace_friend: 'Соответствует заявкам в друзья и изменениям дружеских связей.', - namespace_group: - 'Соответствует вступлениям, выходам, удалениям участников и другим событиям группы.', + namespace_group: "Соответствует изменениям состава и информации группы.", namespace_message: 'Соответствует получению, редактированию, удалению сообщений и реакциям.', namespace_platform: @@ -1077,6 +1079,7 @@ const ruRU = { friend_added: 'Установлена дружеская связь.', group_member_joined: 'Участник вступает в группу, где есть бот.', group_member_left: 'Участник покидает группу, где есть бот.', + group_info_updated: "Изменились сведения о группе, например название или владелец.", group_member_banned: 'Участник группы заблокирован или удалён.', bot_invited_to_group: 'Бот приглашён в группу.', bot_removed_from_group: 'Бот удалён из группы.', diff --git a/web/src/i18n/locales/th-TH.ts b/web/src/i18n/locales/th-TH.ts index 508b9624e..c730ebe99 100644 --- a/web/src/i18n/locales/th-TH.ts +++ b/web/src/i18n/locales/th-TH.ts @@ -988,6 +988,7 @@ const thTH = { eventNames: { wecombot_enter_chat: "ผู้ใช้เข้าสู่แชตบอต", wecombot_template_card_event: "การโต้ตอบกับการ์ด", + dingtalk_card_action: "การโต้ตอบกับการ์ด DingTalk", wecomcs_enter_session: "ลูกค้าเข้าสู่การสนทนา", wecomcs_msg_send_fail: "ส่งข้อความบริการลูกค้าไม่สำเร็จ", wecomcs_servicer_status_change: "สถานะเจ้าหน้าที่เปลี่ยนแปลง", @@ -1002,6 +1003,7 @@ const thTH = { friend_added: 'เพิ่มเพื่อน', group_member_joined: 'สมาชิกเข้าร่วมกลุ่ม', group_member_left: 'สมาชิกออกจากกลุ่ม', + group_info_updated: "อัปเดตข้อมูลกลุ่ม", group_member_banned: 'สมาชิกถูกแบน', bot_invited_to_group: 'บอตได้รับเชิญเข้ากลุ่ม', bot_removed_from_group: 'บอตถูกนำออกจากกลุ่ม', @@ -1012,6 +1014,7 @@ const thTH = { eventDescriptions: { wecombot_enter_chat: "ผู้ใช้เปิดแชตบอต ใช้สำหรับขั้นตอนต้อนรับและแนะนำ", wecombot_template_card_event: "ผู้ใช้คลิกหรือส่งการ์ด การตอบกลับแบบฟอร์มที่ระบบจัดการจะประมวลผลแยกกัน", + dingtalk_card_action: "การคลิกและส่งการ์ด ความคิดเห็นและแบบฟอร์มที่ระบบจัดการจะประมวลผลแยกกัน", wecomcs_enter_session: "ลูกค้าเปิดลิงก์บริการ พร้อมข้อมูลแหล่งที่มาและรหัสต้อนรับ", wecomcs_msg_send_fail: "WeChat แจ้งการส่งล้มเหลว พร้อม ID ข้อความและประเภทข้อผิดพลาด", wecomcs_servicer_status_change: "เจ้าหน้าที่เริ่ม หยุด หรือพักการให้บริการ", @@ -1023,8 +1026,7 @@ const thTH = { 'ตรงกับการเชิญ การนำบอทออก การปิดเสียง และเหตุการณ์สถานะอื่น', namespace_feedback: 'ตรงกับเหตุการณ์ข้อเสนอแนะจากแพลตฟอร์มหรือผู้ใช้', namespace_friend: 'ตรงกับคำขอเป็นเพื่อนและการเปลี่ยนแปลงความสัมพันธ์', - namespace_group: - 'ตรงกับการเข้ากลุ่ม ออกจากกลุ่ม ถูกนำออก และเหตุการณ์กลุ่มอื่น', + namespace_group: "ตรงกับการเปลี่ยนแปลงสมาชิกและการอัปเดตข้อมูลกลุ่ม", namespace_message: 'ตรงกับการรับ แก้ไข ลบข้อความ และแสดงปฏิกิริยา', namespace_platform: 'ตรงกับเหตุการณ์เฉพาะแพลตฟอร์มที่อะแดปเตอร์ให้มา', custom: 'เหตุการณ์กำหนดเองหรือยังไม่มีคำอธิบาย', @@ -1037,6 +1039,7 @@ const thTH = { friend_added: 'สร้างความสัมพันธ์เพื่อนแล้ว', group_member_joined: 'สมาชิกเข้าร่วมกลุ่มที่มีบอท', group_member_left: 'สมาชิกออกจากกลุ่มที่มีบอท', + group_info_updated: "ข้อมูลกลุ่ม เช่น ชื่อหรือเจ้าของกลุ่ม มีการเปลี่ยนแปลง", group_member_banned: 'สมาชิกกลุ่มถูกแบนหรือนำออก', bot_invited_to_group: 'บอทได้รับเชิญเข้ากลุ่ม', bot_removed_from_group: 'บอทถูกนำออกจากกลุ่ม', diff --git a/web/src/i18n/locales/vi-VN.ts b/web/src/i18n/locales/vi-VN.ts index b1554257a..d31b6ebf4 100644 --- a/web/src/i18n/locales/vi-VN.ts +++ b/web/src/i18n/locales/vi-VN.ts @@ -1005,6 +1005,7 @@ const viVN = { eventNames: { wecombot_enter_chat: "Người dùng vào cuộc trò chuyện với bot", wecombot_template_card_event: "Tương tác thẻ mẫu", + dingtalk_card_action: "Tương tác thẻ DingTalk", wecomcs_enter_session: "Khách hàng vào hội thoại", wecomcs_msg_send_fail: "Gửi tin nhắn hỗ trợ thất bại", wecomcs_servicer_status_change: "Trạng thái nhân viên thay đổi", @@ -1019,6 +1020,7 @@ const viVN = { friend_added: 'Đã thêm bạn', group_member_joined: 'Thành viên tham gia nhóm', group_member_left: 'Thành viên rời nhóm', + group_info_updated: "Thông tin nhóm được cập nhật", group_member_banned: 'Thành viên bị cấm tham gia nhóm', bot_invited_to_group: 'Bot được mời vào nhóm', bot_removed_from_group: 'Bot bị xóa khỏi nhóm', @@ -1029,6 +1031,7 @@ const viVN = { eventDescriptions: { wecombot_enter_chat: "Người dùng mở cuộc trò chuyện với bot. Dùng cho quy trình chào mừng.", wecombot_template_card_event: "Người dùng nhấp hoặc gửi thẻ. Phản hồi biểu mẫu do hệ thống quản lý được xử lý riêng.", + dingtalk_card_action: "Nhấp và gửi thẻ; phản hồi và biểu mẫu được quản lý được xử lý riêng.", wecomcs_enter_session: "Khách hàng mở liên kết hỗ trợ; gồm nguồn truy cập và mã chào mừng.", wecomcs_msg_send_fail: "WeChat thông báo gửi thất bại, gồm ID tin nhắn và loại lỗi.", wecomcs_servicer_status_change: "Nhân viên bắt đầu, dừng hoặc tạm dừng tiếp nhận.", @@ -1040,8 +1043,7 @@ const viVN = { 'Khớp sự kiện mời, xóa, tắt tiếng bot và các thay đổi trạng thái khác.', namespace_feedback: 'Khớp sự kiện phản hồi từ nền tảng hoặc người dùng.', namespace_friend: 'Khớp yêu cầu kết bạn và thay đổi quan hệ bạn bè.', - namespace_group: - 'Khớp sự kiện tham gia, rời, bị xóa khỏi nhóm và sự kiện nhóm khác.', + namespace_group: "Khớp các thay đổi thành viên và cập nhật thông tin nhóm.", namespace_message: 'Khớp sự kiện nhận, sửa, xóa và thả cảm xúc tin nhắn.', namespace_platform: 'Khớp sự kiện riêng của nền tảng do bộ chuyển đổi cung cấp.', @@ -1055,6 +1057,7 @@ const viVN = { friend_added: 'Đã tạo quan hệ bạn bè.', group_member_joined: 'Thành viên tham gia nhóm có bot.', group_member_left: 'Thành viên rời nhóm có bot.', + group_info_updated: "Thông tin nhóm như tên hoặc chủ sở hữu đã thay đổi.", group_member_banned: 'Thành viên nhóm bị cấm hoặc xóa.', bot_invited_to_group: 'Bot được mời vào nhóm.', bot_removed_from_group: 'Bot bị xóa khỏi nhóm.', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 9a3a56181..78d309f4d 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -948,6 +948,7 @@ const zhHans = { eventNames: { wecombot_enter_chat: "用户进入机器人会话", wecombot_template_card_event: "模板卡片交互", + dingtalk_card_action: "钉钉卡片交互", wecomcs_enter_session: "客户进入会话", wecomcs_msg_send_fail: "客服消息发送失败", wecomcs_servicer_status_change: "接待人员状态变更", @@ -962,6 +963,7 @@ const zhHans = { friend_added: '好友添加成功', group_member_joined: '成员加入群组', group_member_left: '成员离开群组', + group_info_updated: "群信息更新", group_member_banned: '成员被封禁', bot_invited_to_group: '机器人被邀入群', bot_removed_from_group: '机器人被移出群', @@ -972,6 +974,7 @@ const zhHans = { eventDescriptions: { wecombot_enter_chat: "用户进入机器人会话,可用于欢迎和引导流程。", wecombot_template_card_event: "用户点击或提交模板卡片;已有交互表单的回调单独处理。", + dingtalk_card_action: "用户点击或提交钉钉卡片;反馈和受管理的表单回调单独处理。", wecomcs_enter_session: "客户通过客服链接进入会话,可获取来源和欢迎语凭据。", wecomcs_msg_send_fail: "微信通知客服消息发送失败,包含消息 ID 和失败类型。", wecomcs_servicer_status_change: "接待人员开始、停止接待或暂时挂起。", @@ -982,7 +985,7 @@ const zhHans = { namespace_bot: '匹配机器人入群、退群、禁言和解除禁言等状态事件。', namespace_feedback: '匹配平台或用户反馈事件。', namespace_friend: '匹配好友请求、好友添加成功等好友关系事件。', - namespace_group: '匹配成员加入、离开或被移出群组等群组成员事件。', + namespace_group: "匹配群成员变动、群信息更新等群组事件。", namespace_message: '匹配消息接收、编辑、删除和表态事件。', namespace_platform: '匹配适配器提供的平台专属事件。', custom: '自定义或暂未提供说明的事件。', @@ -995,6 +998,7 @@ const zhHans = { friend_added: '好友关系建立成功。', group_member_joined: '有成员加入机器人所在群组。', group_member_left: '有成员离开机器人所在群组。', + group_info_updated: "群名称、群主等群资料发生变更。", group_member_banned: '群成员被封禁或移出。', bot_invited_to_group: '机器人被邀请加入群组。', bot_removed_from_group: '机器人被移出群组。', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index d19b6f8b0..f1292a130 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -947,6 +947,7 @@ const zhHant = { eventNames: { wecombot_enter_chat: "使用者進入機器人會話", wecombot_template_card_event: "範本卡片互動", + dingtalk_card_action: "釘釘卡片互動", wecomcs_enter_session: "客戶進入會話", wecomcs_msg_send_fail: "客服訊息傳送失敗", wecomcs_servicer_status_change: "接待人員狀態變更", @@ -961,6 +962,7 @@ const zhHant = { friend_added: '新增好友', group_member_joined: '成員加入群組', group_member_left: '成員離開群組', + group_info_updated: "群組資訊更新", group_member_banned: '成員被封禁', bot_invited_to_group: '機器人被邀請加入群組', bot_removed_from_group: '機器人被移出群組', @@ -971,6 +973,7 @@ const zhHant = { eventDescriptions: { wecombot_enter_chat: "使用者進入機器人會話,可用於歡迎和引導流程。", wecombot_template_card_event: "使用者點擊或提交範本卡片;已有互動表單的回呼單獨處理。", + dingtalk_card_action: "使用者點擊或提交釘釘卡片;回饋和受管理的表單回呼單獨處理。", wecomcs_enter_session: "客戶透過客服連結進入會話,可取得來源和歡迎語憑證。", wecomcs_msg_send_fail: "微信通知客服訊息傳送失敗,包含訊息 ID 和失敗類型。", wecomcs_servicer_status_change: "接待人員開始、停止接待或暫時掛起。", @@ -981,7 +984,7 @@ const zhHant = { namespace_bot: '匹配機器人入羣、退羣、禁言和解除禁言等狀態事件。', namespace_feedback: '匹配平臺或使用者反饋事件。', namespace_friend: '匹配好友請求、好友添加成功等好友關係事件。', - namespace_group: '匹配成員加入、離開或被移出羣組等羣組成員事件。', + namespace_group: "匹配群組成員變動、群組資訊更新等事件。", namespace_message: '匹配訊息接收、編輯、刪除和表態事件。', namespace_platform: '匹配適配器提供的平臺專屬事件。', custom: '自定義或暫未提供說明的事件。', @@ -994,6 +997,7 @@ const zhHant = { friend_added: '好友關係建立成功。', group_member_joined: '有成員加入機器人所在羣組。', group_member_left: '有成員離開機器人所在羣組。', + group_info_updated: "群組名稱、群主等群組資料發生變更。", group_member_banned: '羣成員被封禁或移出。', bot_invited_to_group: '機器人被邀請加入羣組。', bot_removed_from_group: '機器人被移出羣組。',