mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 20:14:36 +00:00
Compare commits
3 Commits
fix/plugin
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5029d89630 | ||
|
|
b081ef89d5 | ||
|
|
75d449f21a |
@@ -166,6 +166,11 @@ class QQOfficialClient:
|
|||||||
else:
|
else:
|
||||||
message_data['image_attachments'] = None
|
message_data['image_attachments'] = None
|
||||||
|
|
||||||
|
# Extract message_reference if present
|
||||||
|
message_reference = msg.get('d', {}).get('message_reference', {})
|
||||||
|
if message_reference:
|
||||||
|
message_data['message_reference'] = message_reference
|
||||||
|
|
||||||
return message_data
|
return message_data
|
||||||
|
|
||||||
async def is_image(self, attachment: dict) -> bool:
|
async def is_image(self, attachment: dict) -> bool:
|
||||||
@@ -272,6 +277,57 @@ class QQOfficialClient:
|
|||||||
return True
|
return True
|
||||||
return time.time() > self.access_token_expiry_time
|
return time.time() > self.access_token_expiry_time
|
||||||
|
|
||||||
|
async def get_message_by_id(self, message_id: str, channel_id: str = None, group_openid: str = None, user_openid: str = None) -> Dict[str, Any]:
|
||||||
|
"""根据消息ID获取消息内容
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message_id: 消息ID
|
||||||
|
channel_id: 频道ID(频道消息需要)
|
||||||
|
group_openid: 群组openid(群消息需要)
|
||||||
|
user_openid: 用户openid(私聊消息需要)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
消息内容字典
|
||||||
|
"""
|
||||||
|
if not await self.check_access_token():
|
||||||
|
await self.get_access_token()
|
||||||
|
|
||||||
|
# Validate that exactly one context parameter is provided
|
||||||
|
provided_contexts = sum([bool(channel_id), bool(group_openid), bool(user_openid)])
|
||||||
|
if provided_contexts == 0:
|
||||||
|
await self.logger.warning(f'Cannot fetch message {message_id}: no context provided')
|
||||||
|
return {}
|
||||||
|
if provided_contexts > 1:
|
||||||
|
await self.logger.warning(f'Cannot fetch message {message_id}: multiple contexts provided')
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Determine which API endpoint to use based on provided parameters
|
||||||
|
if channel_id:
|
||||||
|
# Channel message
|
||||||
|
url = f'{self.base_url}/channels/{channel_id}/messages/{message_id}'
|
||||||
|
elif group_openid:
|
||||||
|
# Group message
|
||||||
|
url = f'{self.base_url}/v2/groups/{group_openid}/messages/{message_id}'
|
||||||
|
elif user_openid:
|
||||||
|
# Private message
|
||||||
|
url = f'{self.base_url}/v2/users/{user_openid}/messages/{message_id}'
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
headers = {
|
||||||
|
'Authorization': f'QQBot {self.access_token}',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = await client.get(url, headers=headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
await self.logger.warning(f'Failed to fetch message {message_id}: {response.status_code}')
|
||||||
|
return {}
|
||||||
|
except Exception as e:
|
||||||
|
await self.logger.warning(f'Error fetching message {message_id}: {e}')
|
||||||
|
return {}
|
||||||
|
|
||||||
async def repeat_seed(self, bot_secret: str, target_size: int = 32) -> bytes:
|
async def repeat_seed(self, bot_secret: str, target_size: int = 32) -> bytes:
|
||||||
seed = bot_secret
|
seed = bot_secret
|
||||||
while len(seed) < target_size:
|
while len(seed) < target_size:
|
||||||
|
|||||||
@@ -110,3 +110,10 @@ class QQOfficialEvent(dict):
|
|||||||
文件类型
|
文件类型
|
||||||
"""
|
"""
|
||||||
return self.get('content_type', '')
|
return self.get('content_type', '')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message_reference(self) -> dict:
|
||||||
|
"""
|
||||||
|
引用消息
|
||||||
|
"""
|
||||||
|
return self.get('message_reference', {})
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ class PreProcessor(stage.PipelineStage):
|
|||||||
|
|
||||||
plain_text = ''
|
plain_text = ''
|
||||||
quote_msg = query.pipeline_config['trigger'].get('misc', '').get('combine-quote-message')
|
quote_msg = query.pipeline_config['trigger'].get('misc', '').get('combine-quote-message')
|
||||||
|
quoted_text = '' # Store quoted message text
|
||||||
|
|
||||||
for me in query.message_chain:
|
for me in query.message_chain:
|
||||||
if isinstance(me, platform_message.Plain):
|
if isinstance(me, platform_message.Plain):
|
||||||
@@ -117,6 +118,7 @@ class PreProcessor(stage.PipelineStage):
|
|||||||
elif isinstance(me, platform_message.Quote) and quote_msg:
|
elif isinstance(me, platform_message.Quote) and quote_msg:
|
||||||
for msg in me.origin:
|
for msg in me.origin:
|
||||||
if isinstance(msg, platform_message.Plain):
|
if isinstance(msg, platform_message.Plain):
|
||||||
|
quoted_text += msg.text
|
||||||
content_list.append(provider_message.ContentElement.from_text(msg.text))
|
content_list.append(provider_message.ContentElement.from_text(msg.text))
|
||||||
elif isinstance(msg, platform_message.Image):
|
elif isinstance(msg, platform_message.Image):
|
||||||
if selected_runner != 'local-agent' or (
|
if selected_runner != 'local-agent' or (
|
||||||
@@ -126,6 +128,7 @@ class PreProcessor(stage.PipelineStage):
|
|||||||
content_list.append(provider_message.ContentElement.from_image_base64(msg.base64))
|
content_list.append(provider_message.ContentElement.from_image_base64(msg.base64))
|
||||||
|
|
||||||
query.variables['user_message_text'] = plain_text
|
query.variables['user_message_text'] = plain_text
|
||||||
|
query.variables['quoted_message_text'] = quoted_text # Add quoted text as variable
|
||||||
|
|
||||||
query.user_message = provider_message.Message(role='user', content=content_list)
|
query.user_message = provider_message.Message(role='user', content=content_list)
|
||||||
# =========== 触发事件 PromptPreProcessing
|
# =========== 触发事件 PromptPreProcessing
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ from ..logger import EventLogger
|
|||||||
|
|
||||||
|
|
||||||
class QQOfficialMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
class QQOfficialMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||||
|
def __init__(self, bot: QQOfficialClient = None):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def yiri2target(message_chain: platform_message.MessageChain):
|
async def yiri2target(message_chain: platform_message.MessageChain):
|
||||||
content_list = []
|
content_list = []
|
||||||
@@ -31,10 +34,63 @@ class QQOfficialMessageConverter(abstract_platform_adapter.AbstractMessageConver
|
|||||||
|
|
||||||
return content_list
|
return content_list
|
||||||
|
|
||||||
@staticmethod
|
async def target2yiri(self, message: str, message_id: str, pic_url: str, content_type, message_reference: dict = None, event_type: str = None, channel_id: str = None, group_openid: str = None, user_openid: str = None):
|
||||||
async def target2yiri(message: str, message_id: str, pic_url: str, content_type):
|
|
||||||
yiri_msg_list = []
|
yiri_msg_list = []
|
||||||
yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now()))
|
yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now()))
|
||||||
|
|
||||||
|
# Handle quoted message if message_reference exists
|
||||||
|
if message_reference and message_reference.get('message_id') and self.bot:
|
||||||
|
referenced_msg_id = message_reference.get('message_id')
|
||||||
|
try:
|
||||||
|
# Fetch the referenced message
|
||||||
|
referenced_msg = await self.bot.get_message_by_id(
|
||||||
|
referenced_msg_id,
|
||||||
|
channel_id=channel_id,
|
||||||
|
group_openid=group_openid,
|
||||||
|
user_openid=user_openid
|
||||||
|
)
|
||||||
|
|
||||||
|
if referenced_msg:
|
||||||
|
# Create message chain for the quoted content
|
||||||
|
quoted_content = referenced_msg.get('content', '')
|
||||||
|
quoted_chain = platform_message.MessageChain()
|
||||||
|
|
||||||
|
if quoted_content:
|
||||||
|
quoted_chain.append(platform_message.Plain(text=quoted_content))
|
||||||
|
|
||||||
|
# Add images if present in quoted message
|
||||||
|
quoted_attachments = referenced_msg.get('attachments', [])
|
||||||
|
for attachment in quoted_attachments:
|
||||||
|
if attachment.get('content_type', '').startswith('image/'):
|
||||||
|
img_url = attachment.get('url', '')
|
||||||
|
if img_url:
|
||||||
|
try:
|
||||||
|
img_base64 = await image.get_qq_official_image_base64(
|
||||||
|
pic_url=img_url if img_url.startswith('https://') else 'https://' + img_url,
|
||||||
|
content_type=attachment.get('content_type', '')
|
||||||
|
)
|
||||||
|
quoted_chain.append(platform_message.Image(base64=img_base64))
|
||||||
|
except Exception:
|
||||||
|
# If image fetch fails, just skip it
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Get sender info from referenced message
|
||||||
|
quoted_sender_id = referenced_msg.get('author', {}).get('id', '') or \
|
||||||
|
referenced_msg.get('author', {}).get('user_openid', '') or \
|
||||||
|
referenced_msg.get('author', {}).get('member_openid', '')
|
||||||
|
|
||||||
|
# Add Quote component
|
||||||
|
yiri_msg_list.append(
|
||||||
|
platform_message.Quote(
|
||||||
|
id=referenced_msg_id,
|
||||||
|
sender_id=quoted_sender_id,
|
||||||
|
origin=quoted_chain,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# If fetching quoted message fails, log and continue
|
||||||
|
await self.bot.logger.warning(f'Failed to fetch quoted message {referenced_msg_id}: {e}')
|
||||||
|
|
||||||
if pic_url is not None:
|
if pic_url is not None:
|
||||||
base64_url = await image.get_qq_official_image_base64(pic_url=pic_url, content_type=content_type)
|
base64_url = await image.get_qq_official_image_base64(pic_url=pic_url, content_type=content_type)
|
||||||
yiri_msg_list.append(platform_message.Image(base64=base64_url))
|
yiri_msg_list.append(platform_message.Image(base64=base64_url))
|
||||||
@@ -45,20 +101,35 @@ class QQOfficialMessageConverter(abstract_platform_adapter.AbstractMessageConver
|
|||||||
|
|
||||||
|
|
||||||
class QQOfficialEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
class QQOfficialEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||||
|
def __init__(self, message_converter: QQOfficialMessageConverter):
|
||||||
|
self.message_converter = message_converter
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def yiri2target(event: platform_events.MessageEvent) -> QQOfficialEvent:
|
async def yiri2target(event: platform_events.MessageEvent) -> QQOfficialEvent:
|
||||||
return event.source_platform_object
|
return event.source_platform_object
|
||||||
|
|
||||||
@staticmethod
|
async def target2yiri(self, event: QQOfficialEvent):
|
||||||
async def target2yiri(event: QQOfficialEvent):
|
|
||||||
"""
|
"""
|
||||||
QQ官方消息转换为LB对象
|
QQ官方消息转换为LB对象
|
||||||
"""
|
"""
|
||||||
yiri_chain = await QQOfficialMessageConverter.target2yiri(
|
# Get message reference if present
|
||||||
|
message_reference = event.message_reference
|
||||||
|
|
||||||
|
# Determine context based on event type
|
||||||
|
channel_id = event.channel_id if event.t in ['AT_MESSAGE_CREATE', 'DIRECT_MESSAGE_CREATE'] else None
|
||||||
|
group_openid = event.group_openid if event.t == 'GROUP_AT_MESSAGE_CREATE' else None
|
||||||
|
user_openid = event.user_openid if event.t == 'C2C_MESSAGE_CREATE' else None
|
||||||
|
|
||||||
|
yiri_chain = await self.message_converter.target2yiri(
|
||||||
message=event.content,
|
message=event.content,
|
||||||
message_id=event.d_id,
|
message_id=event.d_id,
|
||||||
pic_url=event.attachments,
|
pic_url=event.attachments,
|
||||||
content_type=event.content_type,
|
content_type=event.content_type,
|
||||||
|
message_reference=message_reference,
|
||||||
|
event_type=event.t,
|
||||||
|
channel_id=channel_id,
|
||||||
|
group_openid=group_openid,
|
||||||
|
user_openid=user_openid,
|
||||||
)
|
)
|
||||||
|
|
||||||
if event.t == 'C2C_MESSAGE_CREATE':
|
if event.t == 'C2C_MESSAGE_CREATE':
|
||||||
@@ -135,14 +206,18 @@ class QQOfficialAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter
|
|||||||
config: dict
|
config: dict
|
||||||
bot_account_id: str
|
bot_account_id: str
|
||||||
bot_uuid: str = None
|
bot_uuid: str = None
|
||||||
message_converter: QQOfficialMessageConverter = QQOfficialMessageConverter()
|
message_converter: QQOfficialMessageConverter
|
||||||
event_converter: QQOfficialEventConverter = QQOfficialEventConverter()
|
event_converter: QQOfficialEventConverter
|
||||||
|
|
||||||
def __init__(self, config: dict, logger: EventLogger):
|
def __init__(self, config: dict, logger: EventLogger):
|
||||||
bot = QQOfficialClient(
|
bot = QQOfficialClient(
|
||||||
app_id=config['appid'], secret=config['secret'], token=config['token'], logger=logger, unified_mode=True
|
app_id=config['appid'], secret=config['secret'], token=config['token'], logger=logger, unified_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Initialize converters with bot reference
|
||||||
|
message_converter = QQOfficialMessageConverter(bot=bot)
|
||||||
|
event_converter = QQOfficialEventConverter(message_converter=message_converter)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
config=config,
|
config=config,
|
||||||
logger=logger,
|
logger=logger,
|
||||||
@@ -150,6 +225,9 @@ class QQOfficialAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter
|
|||||||
bot_account_id=config['appid'],
|
bot_account_id=config['appid'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.message_converter = message_converter
|
||||||
|
self.event_converter = event_converter
|
||||||
|
|
||||||
async def reply_message(
|
async def reply_message(
|
||||||
self,
|
self,
|
||||||
message_source: platform_events.MessageEvent,
|
message_source: platform_events.MessageEvent,
|
||||||
|
|||||||
Reference in New Issue
Block a user