mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-17 18:21:46 -05:00
Update blocks
This commit is contained in:
@@ -5,6 +5,7 @@ Provides utilities for making authenticated requests to the Telegram Bot API.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from typing import Any, Optional
|
||||
|
||||
from backend.data.model import APIKeyCredentials
|
||||
@@ -66,6 +67,49 @@ async def call_telegram_api(
|
||||
return result.get("result", {})
|
||||
|
||||
|
||||
async def call_telegram_api_with_file(
|
||||
credentials: APIKeyCredentials,
|
||||
method: str,
|
||||
file_field: str,
|
||||
file_data: bytes,
|
||||
filename: str,
|
||||
content_type: str,
|
||||
data: Optional[dict[str, Any]] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Make a multipart/form-data request to the Telegram Bot API with a file upload.
|
||||
|
||||
Args:
|
||||
credentials: Bot token credentials
|
||||
method: API method name (e.g., "sendPhoto", "sendVoice")
|
||||
file_field: Form field name for the file (e.g., "photo", "voice")
|
||||
file_data: Raw file bytes
|
||||
filename: Filename for the upload
|
||||
content_type: MIME type of the file
|
||||
data: Additional form parameters
|
||||
|
||||
Returns:
|
||||
API response result
|
||||
|
||||
Raises:
|
||||
TelegramAPIException: If the API returns an error
|
||||
"""
|
||||
token = credentials.api_key.get_secret_value()
|
||||
url = get_bot_api_url(token, method)
|
||||
|
||||
files = [(file_field, (filename, BytesIO(file_data), content_type))]
|
||||
|
||||
response = await Requests().post(url, files=files, data=data or {})
|
||||
result = response.json()
|
||||
|
||||
if not result.get("ok"):
|
||||
error_code = result.get("error_code", 0)
|
||||
description = result.get("description", "Unknown error")
|
||||
raise TelegramAPIException(description, error_code)
|
||||
|
||||
return result.get("result", {})
|
||||
|
||||
|
||||
async def get_file_info(
|
||||
credentials: APIKeyCredentials, file_id: str
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@@ -43,5 +43,5 @@ TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.title,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Telegram action blocks for sending messages, photos, and voice messages.
|
||||
Telegram action blocks for sending messages, media, and managing chat content.
|
||||
"""
|
||||
|
||||
import base64
|
||||
@@ -16,10 +16,10 @@ from backend.data.block import (
|
||||
)
|
||||
from backend.data.execution import ExecutionContext
|
||||
from backend.data.model import APIKeyCredentials, SchemaField
|
||||
from backend.util.file import store_media_file
|
||||
from backend.util.file import get_exec_file_path, store_media_file
|
||||
from backend.util.type import MediaFileType
|
||||
|
||||
from ._api import call_telegram_api, download_telegram_file
|
||||
from ._api import call_telegram_api, call_telegram_api_with_file, download_telegram_file
|
||||
from ._auth import (
|
||||
TEST_CREDENTIALS,
|
||||
TEST_CREDENTIALS_INPUT,
|
||||
@@ -33,7 +33,7 @@ logger = logging.getLogger(__name__)
|
||||
class ParseMode(str, Enum):
|
||||
"""Telegram message parse modes."""
|
||||
|
||||
NONE = ""
|
||||
NONE = "none"
|
||||
MARKDOWN = "Markdown"
|
||||
MARKDOWNV2 = "MarkdownV2"
|
||||
HTML = "HTML"
|
||||
@@ -73,7 +73,7 @@ class SendTelegramMessageBlock(Block):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="b2c3d4e5-f6a7-8901-bcde-f23456789012",
|
||||
id="787678ad-1f47-4efc-89df-643f9908621a",
|
||||
description="Send a text message to a Telegram chat.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=SendTelegramMessageBlock.Input,
|
||||
@@ -108,7 +108,7 @@ class SendTelegramMessageBlock(Block):
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
}
|
||||
if parse_mode:
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = reply_to_message_id
|
||||
@@ -169,7 +169,7 @@ class SendTelegramPhotoBlock(Block):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="c3d4e5f6-a7b8-9012-cdef-345678901234",
|
||||
id="ceb9fd95-fd95-49ff-b57b-278957255716",
|
||||
description="Send a photo to a Telegram chat.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=SendTelegramPhotoBlock.Input,
|
||||
@@ -185,32 +185,63 @@ class SendTelegramPhotoBlock(Block):
|
||||
("status", "Photo sent"),
|
||||
],
|
||||
test_mock={
|
||||
"_send_photo": lambda *args, **kwargs: {"message_id": 123}
|
||||
"_send_photo_url": lambda *args, **kwargs: {"message_id": 123}
|
||||
},
|
||||
)
|
||||
|
||||
async def _send_photo(
|
||||
async def _send_photo_url(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
photo_data: str,
|
||||
photo_url: str,
|
||||
caption: str,
|
||||
parse_mode: str,
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
data: dict = {
|
||||
"chat_id": chat_id,
|
||||
"photo": photo_data,
|
||||
"photo": photo_url,
|
||||
}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if parse_mode:
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = reply_to_message_id
|
||||
|
||||
return await call_telegram_api(credentials, "sendPhoto", data)
|
||||
|
||||
async def _send_photo_file(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
file_path: str,
|
||||
caption: str,
|
||||
parse_mode: str,
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(file_path)
|
||||
file_bytes = path.read_bytes()
|
||||
data: dict = {"chat_id": str(chat_id)}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = str(reply_to_message_id)
|
||||
|
||||
return await call_telegram_api_with_file(
|
||||
credentials,
|
||||
"sendPhoto",
|
||||
file_field="photo",
|
||||
file_data=file_bytes,
|
||||
filename=path.name,
|
||||
content_type="image/jpeg",
|
||||
data=data,
|
||||
)
|
||||
|
||||
async def run(
|
||||
self,
|
||||
input_data: Input,
|
||||
@@ -224,23 +255,36 @@ class SendTelegramPhotoBlock(Block):
|
||||
|
||||
# If it's a URL, pass it directly to Telegram (it will fetch it)
|
||||
if photo_input.startswith(("http://", "https://")):
|
||||
photo_data = photo_input
|
||||
result = await self._send_photo_url(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
photo_url=photo_input,
|
||||
caption=input_data.caption,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
else:
|
||||
# For data URIs or workspace:// references, use store_media_file
|
||||
photo_data = await store_media_file(
|
||||
file=photo_input,
|
||||
# For data URIs or workspace:// references, resolve to local
|
||||
# file and upload via multipart/form-data
|
||||
relative_path = await store_media_file(
|
||||
file=input_data.photo,
|
||||
execution_context=execution_context,
|
||||
return_format="for_external_api",
|
||||
return_format="for_local_processing",
|
||||
)
|
||||
if not execution_context.graph_exec_id:
|
||||
raise ValueError("graph_exec_id is required")
|
||||
abs_path = get_exec_file_path(
|
||||
execution_context.graph_exec_id, relative_path
|
||||
)
|
||||
result = await self._send_photo_file(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
file_path=abs_path,
|
||||
caption=input_data.caption,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
|
||||
result = await self._send_photo(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
photo_data=photo_data,
|
||||
caption=input_data.caption,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
yield "message_id", result.get("message_id", 0)
|
||||
yield "status", "Photo sent"
|
||||
except Exception as e:
|
||||
@@ -281,7 +325,7 @@ class SendTelegramVoiceBlock(Block):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="d4e5f6a7-b8c9-0123-def0-456789012345",
|
||||
id="7a0ef660-1a5b-4951-8642-c13a0c8d6d93",
|
||||
description="Send a voice message to a Telegram chat. "
|
||||
"Voice must be OGG format with OPUS codec.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
@@ -298,22 +342,22 @@ class SendTelegramVoiceBlock(Block):
|
||||
("status", "Voice sent"),
|
||||
],
|
||||
test_mock={
|
||||
"_send_voice": lambda *args, **kwargs: {"message_id": 123}
|
||||
"_send_voice_url": lambda *args, **kwargs: {"message_id": 123}
|
||||
},
|
||||
)
|
||||
|
||||
async def _send_voice(
|
||||
async def _send_voice_url(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
voice_data: str,
|
||||
voice_url: str,
|
||||
caption: str,
|
||||
duration: Optional[int],
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
data: dict = {
|
||||
"chat_id": chat_id,
|
||||
"voice": voice_data,
|
||||
"voice": voice_url,
|
||||
}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
@@ -324,6 +368,37 @@ class SendTelegramVoiceBlock(Block):
|
||||
|
||||
return await call_telegram_api(credentials, "sendVoice", data)
|
||||
|
||||
async def _send_voice_file(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
file_path: str,
|
||||
caption: str,
|
||||
duration: Optional[int],
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(file_path)
|
||||
file_bytes = path.read_bytes()
|
||||
data: dict = {"chat_id": str(chat_id)}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if duration:
|
||||
data["duration"] = str(duration)
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = str(reply_to_message_id)
|
||||
|
||||
return await call_telegram_api_with_file(
|
||||
credentials,
|
||||
"sendVoice",
|
||||
file_field="voice",
|
||||
file_data=file_bytes,
|
||||
filename=path.name,
|
||||
content_type="audio/ogg",
|
||||
data=data,
|
||||
)
|
||||
|
||||
async def run(
|
||||
self,
|
||||
input_data: Input,
|
||||
@@ -337,22 +412,36 @@ class SendTelegramVoiceBlock(Block):
|
||||
|
||||
# If it's a URL, pass it directly to Telegram
|
||||
if voice_input.startswith(("http://", "https://")):
|
||||
voice_data = voice_input
|
||||
result = await self._send_voice_url(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
voice_url=voice_input,
|
||||
caption=input_data.caption,
|
||||
duration=input_data.duration,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
else:
|
||||
voice_data = await store_media_file(
|
||||
file=voice_input,
|
||||
# For data URIs or workspace:// references, resolve to local
|
||||
# file and upload via multipart/form-data
|
||||
relative_path = await store_media_file(
|
||||
file=input_data.voice,
|
||||
execution_context=execution_context,
|
||||
return_format="for_external_api",
|
||||
return_format="for_local_processing",
|
||||
)
|
||||
if not execution_context.graph_exec_id:
|
||||
raise ValueError("graph_exec_id is required")
|
||||
abs_path = get_exec_file_path(
|
||||
execution_context.graph_exec_id, relative_path
|
||||
)
|
||||
result = await self._send_voice_file(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
file_path=abs_path,
|
||||
caption=input_data.caption,
|
||||
duration=input_data.duration,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
|
||||
result = await self._send_voice(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
voice_data=voice_data,
|
||||
caption=input_data.caption,
|
||||
duration=input_data.duration,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
yield "message_id", result.get("message_id", 0)
|
||||
yield "status", "Voice sent"
|
||||
except Exception as e:
|
||||
@@ -383,7 +472,7 @@ class ReplyToTelegramMessageBlock(Block):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="e5f6a7b8-c9d0-1234-ef01-567890123456",
|
||||
id="c2b1c976-844f-4b6c-ab21-4973d2ceab15",
|
||||
description="Reply to a specific message in a Telegram chat.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=ReplyToTelegramMessageBlock.Input,
|
||||
@@ -417,7 +506,7 @@ class ReplyToTelegramMessageBlock(Block):
|
||||
"text": text,
|
||||
"reply_to_message_id": reply_to_message_id,
|
||||
}
|
||||
if parse_mode:
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
|
||||
return await call_telegram_api(credentials, "sendMessage", data)
|
||||
@@ -457,7 +546,7 @@ class GetTelegramFileBlock(Block):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="f6a7b8c9-d0e1-2345-f012-678901234567",
|
||||
id="b600aaf2-6272-40c6-b973-c3984747c5bd",
|
||||
description="Download a file from Telegram using its file_id. "
|
||||
"Use this to process photos, voice messages, or documents received.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
@@ -499,9 +588,9 @@ class GetTelegramFileBlock(Block):
|
||||
file_id=input_data.file_id,
|
||||
)
|
||||
|
||||
# Convert to data URI
|
||||
# Convert to data URI and wrap as MediaFileType
|
||||
mime_type = "application/octet-stream"
|
||||
data_uri = (
|
||||
data_uri = MediaFileType(
|
||||
f"data:{mime_type};base64,"
|
||||
f"{base64.b64encode(file_content).decode()}"
|
||||
)
|
||||
@@ -517,3 +606,658 @@ class GetTelegramFileBlock(Block):
|
||||
yield "status", "File downloaded"
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to download file: {e}")
|
||||
|
||||
|
||||
class DeleteTelegramMessageBlock(Block):
|
||||
"""Delete a message from a Telegram chat."""
|
||||
|
||||
class Input(BlockSchemaInput):
|
||||
credentials: TelegramCredentialsInput = TelegramCredentialsField()
|
||||
chat_id: int = SchemaField(
|
||||
description="The chat ID containing the message"
|
||||
)
|
||||
message_id: int = SchemaField(
|
||||
description="The ID of the message to delete"
|
||||
)
|
||||
|
||||
class Output(BlockSchemaOutput):
|
||||
status: str = SchemaField(description="Status of the operation")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="bb4fd91a-883e-4d29-9879-b06c4bb79d30",
|
||||
description="Delete a message from a Telegram chat. "
|
||||
"Bots can delete their own messages and incoming messages "
|
||||
"in private chats at any time.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=DeleteTelegramMessageBlock.Input,
|
||||
output_schema=DeleteTelegramMessageBlock.Output,
|
||||
test_input={
|
||||
"chat_id": 12345678,
|
||||
"message_id": 42,
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
("status", "Message deleted"),
|
||||
],
|
||||
test_mock={
|
||||
"_delete_message": lambda *args, **kwargs: True
|
||||
},
|
||||
)
|
||||
|
||||
async def _delete_message(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
message_id: int,
|
||||
) -> bool:
|
||||
await call_telegram_api(
|
||||
credentials,
|
||||
"deleteMessage",
|
||||
{"chat_id": chat_id, "message_id": message_id},
|
||||
)
|
||||
return True
|
||||
|
||||
async def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
await self._delete_message(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
message_id=input_data.message_id,
|
||||
)
|
||||
yield "status", "Message deleted"
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to delete message: {e}")
|
||||
|
||||
|
||||
class EditTelegramMessageBlock(Block):
|
||||
"""Edit the text of an existing message."""
|
||||
|
||||
class Input(BlockSchemaInput):
|
||||
credentials: TelegramCredentialsInput = TelegramCredentialsField()
|
||||
chat_id: int = SchemaField(
|
||||
description="The chat ID containing the message"
|
||||
)
|
||||
message_id: int = SchemaField(
|
||||
description="The ID of the message to edit"
|
||||
)
|
||||
text: str = SchemaField(
|
||||
description="New text for the message (max 4096 characters)"
|
||||
)
|
||||
parse_mode: ParseMode = SchemaField(
|
||||
description="Message formatting mode",
|
||||
default=ParseMode.NONE,
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchemaOutput):
|
||||
message_id: int = SchemaField(description="The ID of the edited message")
|
||||
status: str = SchemaField(description="Status of the operation")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="c55816a2-37af-4901-ba19-36edcf2acfc1",
|
||||
description="Edit the text of an existing message sent by the bot.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=EditTelegramMessageBlock.Input,
|
||||
output_schema=EditTelegramMessageBlock.Output,
|
||||
test_input={
|
||||
"chat_id": 12345678,
|
||||
"message_id": 42,
|
||||
"text": "Updated text!",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
("message_id", 42),
|
||||
("status", "Message edited"),
|
||||
],
|
||||
test_mock={
|
||||
"_edit_message": lambda *args, **kwargs: {"message_id": 42}
|
||||
},
|
||||
)
|
||||
|
||||
async def _edit_message(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
message_id: int,
|
||||
text: str,
|
||||
parse_mode: str,
|
||||
) -> dict:
|
||||
data: dict = {
|
||||
"chat_id": chat_id,
|
||||
"message_id": message_id,
|
||||
"text": text,
|
||||
}
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
|
||||
return await call_telegram_api(credentials, "editMessageText", data)
|
||||
|
||||
async def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
result = await self._edit_message(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
message_id=input_data.message_id,
|
||||
text=input_data.text,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
)
|
||||
yield "message_id", result.get("message_id", 0)
|
||||
yield "status", "Message edited"
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to edit message: {e}")
|
||||
|
||||
|
||||
class SendTelegramAudioBlock(Block):
|
||||
"""Send an audio file to a Telegram chat, displayed in the music player."""
|
||||
|
||||
class Input(BlockSchemaInput):
|
||||
credentials: TelegramCredentialsInput = TelegramCredentialsField()
|
||||
chat_id: int = SchemaField(
|
||||
description="The chat ID to send the audio to"
|
||||
)
|
||||
audio: MediaFileType = SchemaField(
|
||||
description="Audio file to send (MP3 or M4A format). "
|
||||
"Can be URL, data URI, or workspace:// reference."
|
||||
)
|
||||
caption: str = SchemaField(
|
||||
description="Caption for the audio file",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
title: str = SchemaField(
|
||||
description="Track title",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
performer: str = SchemaField(
|
||||
description="Track performer/artist",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
duration: Optional[int] = SchemaField(
|
||||
description="Duration in seconds",
|
||||
default=None,
|
||||
advanced=True,
|
||||
)
|
||||
reply_to_message_id: Optional[int] = SchemaField(
|
||||
description="Message ID to reply to",
|
||||
default=None,
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchemaOutput):
|
||||
message_id: int = SchemaField(description="The ID of the sent message")
|
||||
status: str = SchemaField(description="Status of the operation")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="9044829a-7915-4ab4-a50f-89922ba3679f",
|
||||
description="Send an audio file to a Telegram chat. "
|
||||
"The file is displayed in the music player. "
|
||||
"For voice messages, use the Send Voice block instead.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=SendTelegramAudioBlock.Input,
|
||||
output_schema=SendTelegramAudioBlock.Output,
|
||||
test_input={
|
||||
"chat_id": 12345678,
|
||||
"audio": "https://example.com/track.mp3",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
("message_id", 123),
|
||||
("status", "Audio sent"),
|
||||
],
|
||||
test_mock={
|
||||
"_send_audio_url": lambda *args, **kwargs: {"message_id": 123}
|
||||
},
|
||||
)
|
||||
|
||||
async def _send_audio_url(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
audio_url: str,
|
||||
caption: str,
|
||||
title: str,
|
||||
performer: str,
|
||||
duration: Optional[int],
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
data: dict = {
|
||||
"chat_id": chat_id,
|
||||
"audio": audio_url,
|
||||
}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if title:
|
||||
data["title"] = title
|
||||
if performer:
|
||||
data["performer"] = performer
|
||||
if duration:
|
||||
data["duration"] = duration
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = reply_to_message_id
|
||||
|
||||
return await call_telegram_api(credentials, "sendAudio", data)
|
||||
|
||||
async def _send_audio_file(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
file_path: str,
|
||||
caption: str,
|
||||
title: str,
|
||||
performer: str,
|
||||
duration: Optional[int],
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(file_path)
|
||||
file_bytes = path.read_bytes()
|
||||
data: dict = {"chat_id": str(chat_id)}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if title:
|
||||
data["title"] = title
|
||||
if performer:
|
||||
data["performer"] = performer
|
||||
if duration:
|
||||
data["duration"] = str(duration)
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = str(reply_to_message_id)
|
||||
|
||||
return await call_telegram_api_with_file(
|
||||
credentials,
|
||||
"sendAudio",
|
||||
file_field="audio",
|
||||
file_data=file_bytes,
|
||||
filename=path.name,
|
||||
content_type="audio/mpeg",
|
||||
data=data,
|
||||
)
|
||||
|
||||
async def run(
|
||||
self,
|
||||
input_data: Input,
|
||||
*,
|
||||
credentials: APIKeyCredentials,
|
||||
execution_context: ExecutionContext,
|
||||
**kwargs,
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
audio_input = input_data.audio
|
||||
|
||||
if audio_input.startswith(("http://", "https://")):
|
||||
result = await self._send_audio_url(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
audio_url=audio_input,
|
||||
caption=input_data.caption,
|
||||
title=input_data.title,
|
||||
performer=input_data.performer,
|
||||
duration=input_data.duration,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
else:
|
||||
relative_path = await store_media_file(
|
||||
file=input_data.audio,
|
||||
execution_context=execution_context,
|
||||
return_format="for_local_processing",
|
||||
)
|
||||
if not execution_context.graph_exec_id:
|
||||
raise ValueError("graph_exec_id is required")
|
||||
abs_path = get_exec_file_path(
|
||||
execution_context.graph_exec_id, relative_path
|
||||
)
|
||||
result = await self._send_audio_file(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
file_path=abs_path,
|
||||
caption=input_data.caption,
|
||||
title=input_data.title,
|
||||
performer=input_data.performer,
|
||||
duration=input_data.duration,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
|
||||
yield "message_id", result.get("message_id", 0)
|
||||
yield "status", "Audio sent"
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to send audio: {e}")
|
||||
|
||||
|
||||
class SendTelegramDocumentBlock(Block):
|
||||
"""Send a document to a Telegram chat."""
|
||||
|
||||
class Input(BlockSchemaInput):
|
||||
credentials: TelegramCredentialsInput = TelegramCredentialsField()
|
||||
chat_id: int = SchemaField(
|
||||
description="The chat ID to send the document to"
|
||||
)
|
||||
document: MediaFileType = SchemaField(
|
||||
description="Document to send (any file type). "
|
||||
"Can be URL, data URI, or workspace:// reference."
|
||||
)
|
||||
filename: str = SchemaField(
|
||||
description="Filename shown to the recipient. "
|
||||
"If empty, the original filename is used (may be a random ID "
|
||||
"for uploaded files).",
|
||||
default="",
|
||||
)
|
||||
caption: str = SchemaField(
|
||||
description="Caption for the document",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
parse_mode: ParseMode = SchemaField(
|
||||
description="Caption formatting mode",
|
||||
default=ParseMode.NONE,
|
||||
advanced=True,
|
||||
)
|
||||
reply_to_message_id: Optional[int] = SchemaField(
|
||||
description="Message ID to reply to",
|
||||
default=None,
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchemaOutput):
|
||||
message_id: int = SchemaField(description="The ID of the sent message")
|
||||
status: str = SchemaField(description="Status of the operation")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="554f4bad-4c99-48ba-9d1c-75840b71c5ad",
|
||||
description="Send a document (any file type) to a Telegram chat.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=SendTelegramDocumentBlock.Input,
|
||||
output_schema=SendTelegramDocumentBlock.Output,
|
||||
test_input={
|
||||
"chat_id": 12345678,
|
||||
"document": "https://example.com/file.pdf",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
("message_id", 123),
|
||||
("status", "Document sent"),
|
||||
],
|
||||
test_mock={
|
||||
"_send_document_url": lambda *args, **kwargs: {"message_id": 123}
|
||||
},
|
||||
)
|
||||
|
||||
async def _send_document_url(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
document_url: str,
|
||||
caption: str,
|
||||
parse_mode: str,
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
data: dict = {
|
||||
"chat_id": chat_id,
|
||||
"document": document_url,
|
||||
}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = reply_to_message_id
|
||||
|
||||
return await call_telegram_api(credentials, "sendDocument", data)
|
||||
|
||||
async def _send_document_file(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
file_path: str,
|
||||
display_filename: str,
|
||||
caption: str,
|
||||
parse_mode: str,
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(file_path)
|
||||
file_bytes = path.read_bytes()
|
||||
data: dict = {"chat_id": str(chat_id)}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = str(reply_to_message_id)
|
||||
|
||||
return await call_telegram_api_with_file(
|
||||
credentials,
|
||||
"sendDocument",
|
||||
file_field="document",
|
||||
file_data=file_bytes,
|
||||
filename=display_filename or path.name,
|
||||
content_type="application/octet-stream",
|
||||
data=data,
|
||||
)
|
||||
|
||||
async def run(
|
||||
self,
|
||||
input_data: Input,
|
||||
*,
|
||||
credentials: APIKeyCredentials,
|
||||
execution_context: ExecutionContext,
|
||||
**kwargs,
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
doc_input = input_data.document
|
||||
|
||||
if doc_input.startswith(("http://", "https://")):
|
||||
result = await self._send_document_url(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
document_url=doc_input,
|
||||
caption=input_data.caption,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
else:
|
||||
relative_path = await store_media_file(
|
||||
file=input_data.document,
|
||||
execution_context=execution_context,
|
||||
return_format="for_local_processing",
|
||||
)
|
||||
if not execution_context.graph_exec_id:
|
||||
raise ValueError("graph_exec_id is required")
|
||||
abs_path = get_exec_file_path(
|
||||
execution_context.graph_exec_id, relative_path
|
||||
)
|
||||
result = await self._send_document_file(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
file_path=abs_path,
|
||||
display_filename=input_data.filename,
|
||||
caption=input_data.caption,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
|
||||
yield "message_id", result.get("message_id", 0)
|
||||
yield "status", "Document sent"
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to send document: {e}")
|
||||
|
||||
|
||||
class SendTelegramVideoBlock(Block):
|
||||
"""Send a video to a Telegram chat."""
|
||||
|
||||
class Input(BlockSchemaInput):
|
||||
credentials: TelegramCredentialsInput = TelegramCredentialsField()
|
||||
chat_id: int = SchemaField(
|
||||
description="The chat ID to send the video to"
|
||||
)
|
||||
video: MediaFileType = SchemaField(
|
||||
description="Video to send (MP4 format). "
|
||||
"Can be URL, data URI, or workspace:// reference."
|
||||
)
|
||||
caption: str = SchemaField(
|
||||
description="Caption for the video",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
parse_mode: ParseMode = SchemaField(
|
||||
description="Caption formatting mode",
|
||||
default=ParseMode.NONE,
|
||||
advanced=True,
|
||||
)
|
||||
duration: Optional[int] = SchemaField(
|
||||
description="Duration in seconds",
|
||||
default=None,
|
||||
advanced=True,
|
||||
)
|
||||
reply_to_message_id: Optional[int] = SchemaField(
|
||||
description="Message ID to reply to",
|
||||
default=None,
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchemaOutput):
|
||||
message_id: int = SchemaField(description="The ID of the sent message")
|
||||
status: str = SchemaField(description="Status of the operation")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="cda075af-3f31-47b0-baa9-0050b3bd78bd",
|
||||
description="Send a video to a Telegram chat.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=SendTelegramVideoBlock.Input,
|
||||
output_schema=SendTelegramVideoBlock.Output,
|
||||
test_input={
|
||||
"chat_id": 12345678,
|
||||
"video": "https://example.com/video.mp4",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
("message_id", 123),
|
||||
("status", "Video sent"),
|
||||
],
|
||||
test_mock={
|
||||
"_send_video_url": lambda *args, **kwargs: {"message_id": 123}
|
||||
},
|
||||
)
|
||||
|
||||
async def _send_video_url(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
video_url: str,
|
||||
caption: str,
|
||||
parse_mode: str,
|
||||
duration: Optional[int],
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
data: dict = {
|
||||
"chat_id": chat_id,
|
||||
"video": video_url,
|
||||
}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
if duration:
|
||||
data["duration"] = duration
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = reply_to_message_id
|
||||
|
||||
return await call_telegram_api(credentials, "sendVideo", data)
|
||||
|
||||
async def _send_video_file(
|
||||
self,
|
||||
credentials: APIKeyCredentials,
|
||||
chat_id: int,
|
||||
file_path: str,
|
||||
caption: str,
|
||||
parse_mode: str,
|
||||
duration: Optional[int],
|
||||
reply_to_message_id: Optional[int],
|
||||
) -> dict:
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(file_path)
|
||||
file_bytes = path.read_bytes()
|
||||
data: dict = {"chat_id": str(chat_id)}
|
||||
if caption:
|
||||
data["caption"] = caption
|
||||
if parse_mode and parse_mode != "none":
|
||||
data["parse_mode"] = parse_mode
|
||||
if duration:
|
||||
data["duration"] = str(duration)
|
||||
if reply_to_message_id:
|
||||
data["reply_to_message_id"] = str(reply_to_message_id)
|
||||
|
||||
return await call_telegram_api_with_file(
|
||||
credentials,
|
||||
"sendVideo",
|
||||
file_field="video",
|
||||
file_data=file_bytes,
|
||||
filename=path.name,
|
||||
content_type="video/mp4",
|
||||
data=data,
|
||||
)
|
||||
|
||||
async def run(
|
||||
self,
|
||||
input_data: Input,
|
||||
*,
|
||||
credentials: APIKeyCredentials,
|
||||
execution_context: ExecutionContext,
|
||||
**kwargs,
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
video_input = input_data.video
|
||||
|
||||
if video_input.startswith(("http://", "https://")):
|
||||
result = await self._send_video_url(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
video_url=video_input,
|
||||
caption=input_data.caption,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
duration=input_data.duration,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
else:
|
||||
relative_path = await store_media_file(
|
||||
file=input_data.video,
|
||||
execution_context=execution_context,
|
||||
return_format="for_local_processing",
|
||||
)
|
||||
if not execution_context.graph_exec_id:
|
||||
raise ValueError("graph_exec_id is required")
|
||||
abs_path = get_exec_file_path(
|
||||
execution_context.graph_exec_id, relative_path
|
||||
)
|
||||
result = await self._send_video_file(
|
||||
credentials=credentials,
|
||||
chat_id=input_data.chat_id,
|
||||
file_path=abs_path,
|
||||
caption=input_data.caption,
|
||||
parse_mode=input_data.parse_mode.value,
|
||||
duration=input_data.duration,
|
||||
reply_to_message_id=input_data.reply_to_message_id,
|
||||
)
|
||||
|
||||
yield "message_id", result.get("message_id", 0)
|
||||
yield "status", "Video sent"
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to send video: {e}")
|
||||
|
||||
@@ -117,11 +117,18 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
description="File ID of the audio file (for audio messages). "
|
||||
"Use GetTelegramFileBlock to download."
|
||||
)
|
||||
file_id: str = SchemaField(
|
||||
description="File ID for document/video messages. "
|
||||
"Use GetTelegramFileBlock to download."
|
||||
)
|
||||
file_name: str = SchemaField(
|
||||
description="Original filename (for document/audio messages)"
|
||||
)
|
||||
caption: str = SchemaField(description="Caption for media messages")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
id="4435e4e0-df6e-4301-8f35-ad70b12fc9ec",
|
||||
description="Triggers when a message is received by your Telegram bot. "
|
||||
"Supports text, photos, voice messages, and audio files.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
@@ -152,6 +159,8 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
("photo_file_id", ""),
|
||||
("voice_file_id", ""),
|
||||
("audio_file_id", ""),
|
||||
("file_id", ""),
|
||||
("file_name", ""),
|
||||
("caption", ""),
|
||||
],
|
||||
)
|
||||
@@ -178,16 +187,20 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
yield "photo_file_id", ""
|
||||
yield "voice_file_id", ""
|
||||
yield "audio_file_id", ""
|
||||
yield "file_id", ""
|
||||
yield "file_name", ""
|
||||
yield "caption", ""
|
||||
elif "photo" in message:
|
||||
# Get the largest photo (last in array)
|
||||
photos = message.get("photo", [])
|
||||
file_id = photos[-1]["file_id"] if photos else ""
|
||||
photo_fid = photos[-1]["file_id"] if photos else ""
|
||||
yield "event", "photo"
|
||||
yield "text", ""
|
||||
yield "photo_file_id", file_id
|
||||
yield "photo_file_id", photo_fid
|
||||
yield "voice_file_id", ""
|
||||
yield "audio_file_id", ""
|
||||
yield "file_id", ""
|
||||
yield "file_name", ""
|
||||
yield "caption", message.get("caption", "")
|
||||
elif "voice" in message:
|
||||
voice = message.get("voice", {})
|
||||
@@ -196,6 +209,8 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
yield "photo_file_id", ""
|
||||
yield "voice_file_id", voice.get("file_id", "")
|
||||
yield "audio_file_id", ""
|
||||
yield "file_id", ""
|
||||
yield "file_name", ""
|
||||
yield "caption", message.get("caption", "")
|
||||
elif "audio" in message:
|
||||
audio = message.get("audio", {})
|
||||
@@ -204,6 +219,8 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
yield "photo_file_id", ""
|
||||
yield "voice_file_id", ""
|
||||
yield "audio_file_id", audio.get("file_id", "")
|
||||
yield "file_id", ""
|
||||
yield "file_name", audio.get("file_name", "")
|
||||
yield "caption", message.get("caption", "")
|
||||
elif "document" in message:
|
||||
document = message.get("document", {})
|
||||
@@ -211,7 +228,9 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
yield "text", ""
|
||||
yield "photo_file_id", ""
|
||||
yield "voice_file_id", ""
|
||||
yield "audio_file_id", document.get("file_id", "")
|
||||
yield "audio_file_id", ""
|
||||
yield "file_id", document.get("file_id", "")
|
||||
yield "file_name", document.get("file_name", "")
|
||||
yield "caption", message.get("caption", "")
|
||||
elif "video" in message:
|
||||
video = message.get("video", {})
|
||||
@@ -219,7 +238,9 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
yield "text", ""
|
||||
yield "photo_file_id", ""
|
||||
yield "voice_file_id", ""
|
||||
yield "audio_file_id", video.get("file_id", "")
|
||||
yield "audio_file_id", ""
|
||||
yield "file_id", video.get("file_id", "")
|
||||
yield "file_name", video.get("file_name", "")
|
||||
yield "caption", message.get("caption", "")
|
||||
else:
|
||||
yield "event", "other"
|
||||
@@ -227,4 +248,113 @@ class TelegramMessageTriggerBlock(TelegramTriggerBase, Block):
|
||||
yield "photo_file_id", ""
|
||||
yield "voice_file_id", ""
|
||||
yield "audio_file_id", ""
|
||||
yield "file_id", ""
|
||||
yield "file_name", ""
|
||||
yield "caption", ""
|
||||
|
||||
|
||||
# Example payload for reaction trigger testing
|
||||
EXAMPLE_REACTION_PAYLOAD = {
|
||||
"update_id": 123456790,
|
||||
"message_reaction": {
|
||||
"chat": {
|
||||
"id": 12345678,
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"username": "johndoe",
|
||||
"type": "private",
|
||||
},
|
||||
"message_id": 42,
|
||||
"user": {
|
||||
"id": 12345678,
|
||||
"is_bot": False,
|
||||
"first_name": "John",
|
||||
"username": "johndoe",
|
||||
},
|
||||
"date": 1234567890,
|
||||
"new_reaction": [{"type": "emoji", "emoji": "👍"}],
|
||||
"old_reaction": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TelegramMessageReactionTriggerBlock(TelegramTriggerBase, Block):
|
||||
"""
|
||||
Triggers when a reaction to a message is changed.
|
||||
|
||||
Works automatically in private chats. In group chats, the bot must be
|
||||
an administrator to receive reaction updates.
|
||||
"""
|
||||
|
||||
class Input(TelegramTriggerBase.Input):
|
||||
pass
|
||||
|
||||
class Output(BlockSchemaOutput):
|
||||
payload: dict = SchemaField(
|
||||
description="The complete webhook payload from Telegram"
|
||||
)
|
||||
chat_id: int = SchemaField(
|
||||
description="The chat ID where the reaction occurred"
|
||||
)
|
||||
message_id: int = SchemaField(
|
||||
description="The message ID that was reacted to"
|
||||
)
|
||||
user_id: int = SchemaField(
|
||||
description="The user ID who changed the reaction"
|
||||
)
|
||||
username: str = SchemaField(
|
||||
description="Username of the user (may be empty)"
|
||||
)
|
||||
new_reactions: list = SchemaField(
|
||||
description="List of new reactions on the message"
|
||||
)
|
||||
old_reactions: list = SchemaField(
|
||||
description="List of previous reactions on the message"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="82525328-9368-4966-8f0c-cd78e80181fd",
|
||||
description="Triggers when a reaction to a message is changed. "
|
||||
"Works in private chats automatically. "
|
||||
"In groups, the bot must be an administrator.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
input_schema=TelegramMessageReactionTriggerBlock.Input,
|
||||
output_schema=TelegramMessageReactionTriggerBlock.Output,
|
||||
webhook_config=BlockWebhookConfig(
|
||||
provider=ProviderName.TELEGRAM,
|
||||
webhook_type=TelegramWebhookType.BOT,
|
||||
resource_format="bot",
|
||||
event_filter_input="",
|
||||
event_format="message_reaction",
|
||||
),
|
||||
test_input={
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
"payload": EXAMPLE_REACTION_PAYLOAD,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
("payload", EXAMPLE_REACTION_PAYLOAD),
|
||||
("chat_id", 12345678),
|
||||
("message_id", 42),
|
||||
("user_id", 12345678),
|
||||
("username", "johndoe"),
|
||||
("new_reactions", [{"type": "emoji", "emoji": "👍"}]),
|
||||
("old_reactions", []),
|
||||
],
|
||||
)
|
||||
|
||||
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
payload = input_data.payload
|
||||
reaction = payload.get("message_reaction", {})
|
||||
|
||||
chat = reaction.get("chat", {})
|
||||
user = reaction.get("user", {})
|
||||
|
||||
yield "payload", payload
|
||||
yield "chat_id", chat.get("id", 0)
|
||||
yield "message_id", reaction.get("message_id", 0)
|
||||
yield "user_id", user.get("id", 0)
|
||||
yield "username", user.get("username", "")
|
||||
yield "new_reactions", reaction.get("new_reaction", [])
|
||||
yield "old_reactions", reaction.get("old_reaction", [])
|
||||
|
||||
@@ -83,6 +83,8 @@ class TelegramWebhooksManager(BaseWebhooksManager[TelegramWebhookType]):
|
||||
event_type = "message.other"
|
||||
elif "edited_message" in payload:
|
||||
event_type = "edited_message"
|
||||
elif "message_reaction" in payload:
|
||||
event_type = "message_reaction"
|
||||
elif "callback_query" in payload:
|
||||
event_type = "callback_query"
|
||||
else:
|
||||
@@ -123,7 +125,7 @@ class TelegramWebhooksManager(BaseWebhooksManager[TelegramWebhookType]):
|
||||
webhook_data = {
|
||||
"url": ingress_url,
|
||||
"secret_token": secret,
|
||||
"allowed_updates": ["message", "edited_message"],
|
||||
"allowed_updates": ["message", "edited_message", "message_reaction"],
|
||||
}
|
||||
|
||||
response = await Requests().post(url, json=webhook_data)
|
||||
|
||||
@@ -54,7 +54,8 @@ export default function BuilderPage() {
|
||||
);
|
||||
}
|
||||
|
||||
return isNewFlowEditorEnabled ? (
|
||||
// return isNewFlowEditorEnabled ? (
|
||||
return true ? (
|
||||
<ReactFlowProvider>
|
||||
<Flow />
|
||||
</ReactFlowProvider>
|
||||
|
||||
Reference in New Issue
Block a user