lfx-telegram 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lfx_telegram/__init__.py +17 -0
- lfx_telegram/_base.py +72 -0
- lfx_telegram/_client.py +127 -0
- lfx_telegram/_triggers.py +79 -0
- lfx_telegram/_validation.py +51 -0
- lfx_telegram/components/__init__.py +1 -0
- lfx_telegram/components/telegram/__init__.py +15 -0
- lfx_telegram/components/telegram/capabilities.v1.json +163 -0
- lfx_telegram/components/telegram/telegram_notify.py +29 -0
- lfx_telegram/components/telegram/telegram_on_command.py +27 -0
- lfx_telegram/components/telegram/telegram_on_message.py +12 -0
- lfx_telegram/components/telegram/telegram_reply.py +39 -0
- lfx_telegram/components/telegram/telegram_send_message.py +27 -0
- lfx_telegram/extension.json +25 -0
- lfx_telegram-0.1.0.dist-info/METADATA +15 -0
- lfx_telegram-0.1.0.dist-info/RECORD +18 -0
- lfx_telegram-0.1.0.dist-info/WHEEL +4 -0
- lfx_telegram-0.1.0.dist-info/entry_points.txt +5 -0
lfx_telegram/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Connection-backed Telegram triggers and bot actions."""
|
|
2
|
+
|
|
3
|
+
from lfx_telegram.components.telegram import (
|
|
4
|
+
TelegramNotifyComponent,
|
|
5
|
+
TelegramOnCommandTriggerComponent,
|
|
6
|
+
TelegramOnMessageTriggerComponent,
|
|
7
|
+
TelegramReplyComponent,
|
|
8
|
+
TelegramSendMessageComponent,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"TelegramNotifyComponent",
|
|
13
|
+
"TelegramOnCommandTriggerComponent",
|
|
14
|
+
"TelegramOnMessageTriggerComponent",
|
|
15
|
+
"TelegramReplyComponent",
|
|
16
|
+
"TelegramSendMessageComponent",
|
|
17
|
+
]
|
lfx_telegram/_base.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Connection and action execution shared by Telegram components."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from lfx.custom import Component
|
|
8
|
+
from lfx.integrations.telemetry import integration_action
|
|
9
|
+
from lfx.io import ConnectionRefInput
|
|
10
|
+
from lfx.schema.data import Data
|
|
11
|
+
|
|
12
|
+
from lfx_telegram._client import TelegramClient, redact
|
|
13
|
+
|
|
14
|
+
AUTH_PROFILE = "telegram-bot-token"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def connection_input(*, capabilities: list[str], required: bool = True) -> ConnectionRefInput:
|
|
18
|
+
"""Request only the bot connection owned by this flow's user."""
|
|
19
|
+
return ConnectionRefInput(
|
|
20
|
+
name="connection",
|
|
21
|
+
display_name="Telegram Connection",
|
|
22
|
+
provider="telegram",
|
|
23
|
+
auth_profile_id=AUTH_PROFILE,
|
|
24
|
+
identity_kind="instance",
|
|
25
|
+
ownership_mode="user",
|
|
26
|
+
required_scopes=[],
|
|
27
|
+
capabilities=capabilities,
|
|
28
|
+
required=required,
|
|
29
|
+
info="A BotFather bot token stored in your connection. Triggers also require background-run consent.",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TelegramActionComponent(Component):
|
|
34
|
+
"""Resolve and govern each action through the common connection service."""
|
|
35
|
+
|
|
36
|
+
icon = "Telegram"
|
|
37
|
+
capability_id: ClassVar[str] = ""
|
|
38
|
+
|
|
39
|
+
async def send(self, payload: dict, *, expected_bot_id: str | None = None) -> Data:
|
|
40
|
+
lease = self.resolve_connection("connection")
|
|
41
|
+
credential = await lease.get_credential()
|
|
42
|
+
async with (
|
|
43
|
+
integration_action(
|
|
44
|
+
self, provider="telegram", capability=self.capability_id, owner_kind=credential.owner_kind
|
|
45
|
+
),
|
|
46
|
+
TelegramClient(lease) as client,
|
|
47
|
+
):
|
|
48
|
+
bot = await client.identity()
|
|
49
|
+
if expected_bot_id is not None and expected_bot_id != bot["id"]:
|
|
50
|
+
from lfx.integrations.errors import ConnectionNotAuthorizedError
|
|
51
|
+
|
|
52
|
+
raise ConnectionNotAuthorizedError(
|
|
53
|
+
provider="telegram", hint="Reply with the bot that received this event."
|
|
54
|
+
)
|
|
55
|
+
result = await client.call("sendMessage", **payload)
|
|
56
|
+
if not isinstance(result, dict) or type(result.get("message_id")) is not int:
|
|
57
|
+
from lfx.integrations.errors import ProviderUnavailableError
|
|
58
|
+
|
|
59
|
+
raise ProviderUnavailableError(provider="telegram")
|
|
60
|
+
self.status = "Telegram message sent."
|
|
61
|
+
return Data(
|
|
62
|
+
data=redact(
|
|
63
|
+
{
|
|
64
|
+
"provider": "telegram",
|
|
65
|
+
"bot_id": bot["id"],
|
|
66
|
+
"chat_id": payload["chat_id"],
|
|
67
|
+
"message_id": result["message_id"],
|
|
68
|
+
"thread_id": result.get("message_thread_id"),
|
|
69
|
+
"text": result.get("text", ""),
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
)
|
lfx_telegram/_client.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Fixed-origin Telegram Bot API transport with credential-free failures."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from lfx.integrations.errors import (
|
|
10
|
+
AuthExpiredError,
|
|
11
|
+
ConnectionNotAuthorizedError,
|
|
12
|
+
InvalidRequestError,
|
|
13
|
+
ProviderUnavailableError,
|
|
14
|
+
RateLimitedError,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from lfx.integrations.models import CredentialLease
|
|
19
|
+
|
|
20
|
+
API_ORIGIN = "https://api.telegram.org"
|
|
21
|
+
TOKEN_PATTERN = re.compile(r"[1-9][0-9]{4,15}:[A-Za-z0-9_-]{30,128}")
|
|
22
|
+
MAX_RETRY_AFTER_SECONDS = 86400
|
|
23
|
+
METHODS = frozenset({"getMe", "getUpdates", "getWebhookInfo", "setWebhook", "deleteWebhook", "sendMessage"})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def redact(value: Any) -> Any:
|
|
27
|
+
"""Remove Telegram credentials even when a user posts one as message text."""
|
|
28
|
+
if isinstance(value, str):
|
|
29
|
+
return TOKEN_PATTERN.sub("[redacted bot token]", value)
|
|
30
|
+
if isinstance(value, list):
|
|
31
|
+
return [redact(item) for item in value]
|
|
32
|
+
if isinstance(value, dict):
|
|
33
|
+
return {key: redact(item) for key, item in value.items()}
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TelegramClient:
|
|
38
|
+
"""Keep token-bearing URLs below httpx's request-logging client layer."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, lease: CredentialLease, *, transport: httpx.AsyncBaseTransport | None = None) -> None:
|
|
41
|
+
self._lease = lease
|
|
42
|
+
self._transport = transport if transport is not None else httpx.AsyncHTTPTransport(retries=0)
|
|
43
|
+
|
|
44
|
+
async def __aenter__(self):
|
|
45
|
+
await self._transport.__aenter__()
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
async def __aexit__(self, *args: object) -> None:
|
|
49
|
+
await self._transport.__aexit__(*args)
|
|
50
|
+
|
|
51
|
+
async def _request(self, method: str, payload: dict[str, Any]) -> Any:
|
|
52
|
+
credential = await self._lease.get_credential()
|
|
53
|
+
token = credential.access_token.get_secret_value()
|
|
54
|
+
if credential.provider not in {"", "telegram"} or credential.identity not in {None, "bot"}:
|
|
55
|
+
raise ConnectionNotAuthorizedError(provider="telegram")
|
|
56
|
+
if not TOKEN_PATTERN.fullmatch(token):
|
|
57
|
+
msg = "Use a Telegram bot token issued by BotFather."
|
|
58
|
+
raise InvalidRequestError(msg, provider="telegram")
|
|
59
|
+
bot_id = token.split(":", 1)[0]
|
|
60
|
+
if credential.account is not None and credential.account.id != bot_id:
|
|
61
|
+
raise ConnectionNotAuthorizedError(provider="telegram")
|
|
62
|
+
request = httpx.Request(
|
|
63
|
+
"POST",
|
|
64
|
+
f"{API_ORIGIN}/bot{token}/{method}",
|
|
65
|
+
json=payload,
|
|
66
|
+
extensions={"timeout": {"connect": 10.0, "read": 35.0, "write": 10.0, "pool": 10.0}},
|
|
67
|
+
)
|
|
68
|
+
try:
|
|
69
|
+
response = await self._transport.handle_async_request(request)
|
|
70
|
+
try:
|
|
71
|
+
await response.aread()
|
|
72
|
+
body = response.json()
|
|
73
|
+
status = response.status_code
|
|
74
|
+
finally:
|
|
75
|
+
await response.aclose()
|
|
76
|
+
except (httpx.HTTPError, ValueError, TypeError):
|
|
77
|
+
raise ProviderUnavailableError(provider="telegram") from None
|
|
78
|
+
if not isinstance(body, dict):
|
|
79
|
+
raise ProviderUnavailableError(provider="telegram")
|
|
80
|
+
code = body.get("error_code", status)
|
|
81
|
+
code = code if type(code) is int else status
|
|
82
|
+
if status >= httpx.codes.INTERNAL_SERVER_ERROR:
|
|
83
|
+
raise ProviderUnavailableError(provider="telegram", http_status=status)
|
|
84
|
+
if code == httpx.codes.UNAUTHORIZED:
|
|
85
|
+
raise AuthExpiredError(provider="telegram")
|
|
86
|
+
if code == httpx.codes.TOO_MANY_REQUESTS:
|
|
87
|
+
parameters = body.get("parameters")
|
|
88
|
+
retry = parameters.get("retry_after") if isinstance(parameters, dict) else None
|
|
89
|
+
retry = float(retry) if type(retry) is int and 0 <= retry <= MAX_RETRY_AFTER_SECONDS else None
|
|
90
|
+
raise RateLimitedError(provider="telegram", retry_after=retry)
|
|
91
|
+
if code == httpx.codes.FORBIDDEN:
|
|
92
|
+
raise ConnectionNotAuthorizedError(provider="telegram", reason="provider")
|
|
93
|
+
if code in {400, 404, 409}:
|
|
94
|
+
message = "Telegram rejected the request. Check the target and bot receiver configuration."
|
|
95
|
+
raise InvalidRequestError(message, provider="telegram", http_status=code)
|
|
96
|
+
if body.get("ok") is not True or status != httpx.codes.OK:
|
|
97
|
+
raise ProviderUnavailableError(provider="telegram")
|
|
98
|
+
return body.get("result")
|
|
99
|
+
|
|
100
|
+
async def call(self, method: str, **payload: Any) -> Any:
|
|
101
|
+
"""Call only declared methods, with one credential re-resolution on rejection."""
|
|
102
|
+
if method not in METHODS:
|
|
103
|
+
msg = "This Telegram method is unsupported."
|
|
104
|
+
raise InvalidRequestError(msg, provider="telegram")
|
|
105
|
+
try:
|
|
106
|
+
return await self._request(method, payload)
|
|
107
|
+
except AuthExpiredError as error:
|
|
108
|
+
await self._lease.get_token_after_auth_error(error)
|
|
109
|
+
return await self._request(method, payload)
|
|
110
|
+
|
|
111
|
+
async def identity(self) -> dict[str, Any]:
|
|
112
|
+
"""Establish the bot identity from Telegram, never from caller metadata."""
|
|
113
|
+
result = await self.call("getMe")
|
|
114
|
+
credential = await self._lease.get_credential()
|
|
115
|
+
token_id = credential.access_token.get_secret_value().split(":", 1)[0]
|
|
116
|
+
if (
|
|
117
|
+
not isinstance(result, dict)
|
|
118
|
+
or result.get("is_bot") is not True
|
|
119
|
+
or type(result.get("id")) is not int
|
|
120
|
+
or str(result["id"]) != token_id
|
|
121
|
+
or not isinstance(result.get("username"), str)
|
|
122
|
+
or not re.fullmatch(r"[A-Za-z0-9_]{5,32}", result["username"])
|
|
123
|
+
):
|
|
124
|
+
raise ConnectionNotAuthorizedError(
|
|
125
|
+
provider="telegram", hint="Use a token whose Telegram bot identity can be verified."
|
|
126
|
+
)
|
|
127
|
+
return {"id": str(result["id"]), "username": result["username"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Telegram trigger declarations have no transport or credential material."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from lfx.base.triggers.base import BaseTriggerComponent
|
|
8
|
+
from lfx.io import DropdownInput, Output, StrInput
|
|
9
|
+
from lfx.schema.message import Message
|
|
10
|
+
|
|
11
|
+
from lfx_telegram._base import connection_input
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def trigger_inputs(event: str) -> list:
|
|
15
|
+
return [
|
|
16
|
+
connection_input(
|
|
17
|
+
capabilities=[f"telegram.trigger.{event}.webhook", f"telegram.trigger.{event}.poll"], required=False
|
|
18
|
+
),
|
|
19
|
+
StrInput(
|
|
20
|
+
name="allowed_chats",
|
|
21
|
+
display_name="Allowed Chat IDs",
|
|
22
|
+
is_list=True,
|
|
23
|
+
value=[],
|
|
24
|
+
required=True,
|
|
25
|
+
info="An explicit list of numeric chat IDs this flow may receive. Empty lists cannot be armed.",
|
|
26
|
+
),
|
|
27
|
+
StrInput(
|
|
28
|
+
name="allowed_senders",
|
|
29
|
+
display_name="Allowed Sender IDs",
|
|
30
|
+
is_list=True,
|
|
31
|
+
value=[],
|
|
32
|
+
advanced=True,
|
|
33
|
+
info="Optional numeric user IDs. Anonymous messages fail this filter.",
|
|
34
|
+
),
|
|
35
|
+
DropdownInput(
|
|
36
|
+
name="session_scope",
|
|
37
|
+
display_name="Conversation Session",
|
|
38
|
+
options=["thread", "sender", "chat"],
|
|
39
|
+
value="thread",
|
|
40
|
+
advanced=True,
|
|
41
|
+
),
|
|
42
|
+
DropdownInput(
|
|
43
|
+
name="delivery_mode",
|
|
44
|
+
display_name="Delivery",
|
|
45
|
+
options=["auto", "push", "poll"],
|
|
46
|
+
value="auto",
|
|
47
|
+
advanced=True,
|
|
48
|
+
info="Polling refuses a bot that already has a webhook. Transport changes retain pending updates.",
|
|
49
|
+
),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TelegramTriggerComponent(BaseTriggerComponent):
|
|
54
|
+
"""Hand the server-verified event to the downstream flow."""
|
|
55
|
+
|
|
56
|
+
provider = "telegram"
|
|
57
|
+
icon = "Telegram"
|
|
58
|
+
needs_connection = True
|
|
59
|
+
config_fields: ClassVar[tuple[str, ...]] = (
|
|
60
|
+
"connection",
|
|
61
|
+
"allowed_chats",
|
|
62
|
+
"allowed_senders",
|
|
63
|
+
"session_scope",
|
|
64
|
+
"delivery_mode",
|
|
65
|
+
)
|
|
66
|
+
outputs = [
|
|
67
|
+
Output(display_name="Event", name="trigger_event", method="build_event"),
|
|
68
|
+
Output(display_name="Message", name="message", method="build_message"),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
def trigger_config(self) -> dict:
|
|
72
|
+
return {name: getattr(self, name, None) for name in self.config_fields}
|
|
73
|
+
|
|
74
|
+
def build_message(self) -> Message:
|
|
75
|
+
event, _problem = self._firing_event()
|
|
76
|
+
payload = event.get("payload")
|
|
77
|
+
payload = payload if isinstance(payload, dict) else {}
|
|
78
|
+
payload = payload.get("data", payload.get("delivery", payload))
|
|
79
|
+
return Message(text=payload.get("text") or "", session_id=payload.get("session_key") or "")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Validate the bounded Telegram target and message inputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from lfx.integrations.errors import InvalidRequestError
|
|
9
|
+
|
|
10
|
+
MAX_MESSAGE_UNITS = 4096
|
|
11
|
+
MAX_TELEGRAM_ID = (1 << 52) - 1
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def numeric_id(value: Any, *, positive: bool = False) -> int:
|
|
15
|
+
"""Accept exact numeric identifiers without float rounding or coercion."""
|
|
16
|
+
if type(value) is int:
|
|
17
|
+
result = value
|
|
18
|
+
elif isinstance(value, str) and re.fullmatch(r"-?[1-9][0-9]{0,15}", value):
|
|
19
|
+
result = int(value)
|
|
20
|
+
else:
|
|
21
|
+
msg = "Use a numeric Telegram ID."
|
|
22
|
+
raise InvalidRequestError(msg, provider="telegram")
|
|
23
|
+
if result == 0 or abs(result) > MAX_TELEGRAM_ID or (positive and result < 0):
|
|
24
|
+
msg = "The Telegram ID is outside the supported range."
|
|
25
|
+
raise InvalidRequestError(msg, provider="telegram")
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def message_payload(
|
|
30
|
+
*, chat_id: Any, text: Any, thread_id: Any = None, reply_id: Any = None, silent: bool = False
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
"""Build one plain-text send without an arbitrary API destination."""
|
|
33
|
+
if (
|
|
34
|
+
not isinstance(text, str)
|
|
35
|
+
or not text.strip()
|
|
36
|
+
or len(text.encode("utf-16-le", errors="surrogatepass")) // 2 > MAX_MESSAGE_UNITS
|
|
37
|
+
):
|
|
38
|
+
msg = "Telegram messages need 1 to 4096 UTF-16 characters."
|
|
39
|
+
raise InvalidRequestError(msg, provider="telegram")
|
|
40
|
+
if type(silent) is not bool:
|
|
41
|
+
msg = "The notification setting must be a boolean."
|
|
42
|
+
raise InvalidRequestError(msg, provider="telegram")
|
|
43
|
+
payload: dict[str, Any] = {"chat_id": numeric_id(chat_id), "text": text, "disable_notification": silent}
|
|
44
|
+
if thread_id is not None and thread_id != "":
|
|
45
|
+
payload["message_thread_id"] = numeric_id(thread_id, positive=True)
|
|
46
|
+
if reply_id is not None and reply_id != "":
|
|
47
|
+
payload["reply_parameters"] = {
|
|
48
|
+
"message_id": numeric_id(reply_id, positive=True),
|
|
49
|
+
"allow_sending_without_reply": False,
|
|
50
|
+
}
|
|
51
|
+
return payload
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Telegram component namespace."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Telegram bundle components."""
|
|
2
|
+
|
|
3
|
+
from .telegram_notify import TelegramNotifyComponent
|
|
4
|
+
from .telegram_on_command import TelegramOnCommandTriggerComponent
|
|
5
|
+
from .telegram_on_message import TelegramOnMessageTriggerComponent
|
|
6
|
+
from .telegram_reply import TelegramReplyComponent
|
|
7
|
+
from .telegram_send_message import TelegramSendMessageComponent
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"TelegramNotifyComponent",
|
|
11
|
+
"TelegramOnCommandTriggerComponent",
|
|
12
|
+
"TelegramOnMessageTriggerComponent",
|
|
13
|
+
"TelegramReplyComponent",
|
|
14
|
+
"TelegramSendMessageComponent",
|
|
15
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"provider_id": "telegram",
|
|
4
|
+
"display_name": "Telegram",
|
|
5
|
+
"icon": "Telegram",
|
|
6
|
+
"docs_url": "https://docs.langflow.org/next/bundles-telegram",
|
|
7
|
+
"auth_profiles": [
|
|
8
|
+
{
|
|
9
|
+
"id": "telegram-bot-token",
|
|
10
|
+
"kind": "api_key",
|
|
11
|
+
"identity": "bot",
|
|
12
|
+
"supports_pkce": false,
|
|
13
|
+
"supports_refresh": false,
|
|
14
|
+
"default_scopes": [],
|
|
15
|
+
"client_type_by_context": {
|
|
16
|
+
"hosted": "external",
|
|
17
|
+
"self_managed": "external",
|
|
18
|
+
"desktop": "external",
|
|
19
|
+
"headless": "external"
|
|
20
|
+
},
|
|
21
|
+
"owner_by_context": {
|
|
22
|
+
"hosted": "customer",
|
|
23
|
+
"self_managed": "customer",
|
|
24
|
+
"desktop": "customer",
|
|
25
|
+
"headless": "customer"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"capabilities": [
|
|
30
|
+
{
|
|
31
|
+
"id": "telegram.trigger.message.webhook",
|
|
32
|
+
"display_name": "Telegram: On Message",
|
|
33
|
+
"auth_profile_id": "telegram-bot-token",
|
|
34
|
+
"identity": "bot",
|
|
35
|
+
"required_scopes": [],
|
|
36
|
+
"policy_keys": [
|
|
37
|
+
"integrations.telegram.trigger.message"
|
|
38
|
+
],
|
|
39
|
+
"substrate": "rest",
|
|
40
|
+
"maturity": "preview",
|
|
41
|
+
"deployment_contexts": [
|
|
42
|
+
"hosted",
|
|
43
|
+
"self_managed"
|
|
44
|
+
],
|
|
45
|
+
"risk": "read",
|
|
46
|
+
"component_ref": "TelegramOnMessageTriggerComponent"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "telegram.trigger.message.poll",
|
|
50
|
+
"display_name": "Telegram: On Message",
|
|
51
|
+
"auth_profile_id": "telegram-bot-token",
|
|
52
|
+
"identity": "bot",
|
|
53
|
+
"required_scopes": [],
|
|
54
|
+
"policy_keys": [
|
|
55
|
+
"integrations.telegram.trigger.message"
|
|
56
|
+
],
|
|
57
|
+
"substrate": "rest",
|
|
58
|
+
"maturity": "preview",
|
|
59
|
+
"deployment_contexts": [
|
|
60
|
+
"self_managed",
|
|
61
|
+
"desktop"
|
|
62
|
+
],
|
|
63
|
+
"risk": "read",
|
|
64
|
+
"component_ref": "TelegramOnMessageTriggerComponent"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "telegram.trigger.command.webhook",
|
|
68
|
+
"display_name": "Telegram: On Command",
|
|
69
|
+
"auth_profile_id": "telegram-bot-token",
|
|
70
|
+
"identity": "bot",
|
|
71
|
+
"required_scopes": [],
|
|
72
|
+
"policy_keys": [
|
|
73
|
+
"integrations.telegram.trigger.command"
|
|
74
|
+
],
|
|
75
|
+
"substrate": "rest",
|
|
76
|
+
"maturity": "preview",
|
|
77
|
+
"deployment_contexts": [
|
|
78
|
+
"hosted",
|
|
79
|
+
"self_managed"
|
|
80
|
+
],
|
|
81
|
+
"risk": "read",
|
|
82
|
+
"component_ref": "TelegramOnCommandTriggerComponent"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "telegram.trigger.command.poll",
|
|
86
|
+
"display_name": "Telegram: On Command",
|
|
87
|
+
"auth_profile_id": "telegram-bot-token",
|
|
88
|
+
"identity": "bot",
|
|
89
|
+
"required_scopes": [],
|
|
90
|
+
"policy_keys": [
|
|
91
|
+
"integrations.telegram.trigger.command"
|
|
92
|
+
],
|
|
93
|
+
"substrate": "rest",
|
|
94
|
+
"maturity": "preview",
|
|
95
|
+
"deployment_contexts": [
|
|
96
|
+
"self_managed",
|
|
97
|
+
"desktop"
|
|
98
|
+
],
|
|
99
|
+
"risk": "read",
|
|
100
|
+
"component_ref": "TelegramOnCommandTriggerComponent"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "telegram.bot.send",
|
|
104
|
+
"display_name": "Telegram: Send Message",
|
|
105
|
+
"auth_profile_id": "telegram-bot-token",
|
|
106
|
+
"identity": "bot",
|
|
107
|
+
"required_scopes": [],
|
|
108
|
+
"policy_keys": [
|
|
109
|
+
"integrations.telegram.bot.send"
|
|
110
|
+
],
|
|
111
|
+
"substrate": "rest",
|
|
112
|
+
"maturity": "preview",
|
|
113
|
+
"deployment_contexts": [
|
|
114
|
+
"hosted",
|
|
115
|
+
"self_managed",
|
|
116
|
+
"desktop",
|
|
117
|
+
"headless"
|
|
118
|
+
],
|
|
119
|
+
"risk": "write",
|
|
120
|
+
"component_ref": "TelegramSendMessageComponent"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "telegram.bot.reply",
|
|
124
|
+
"display_name": "Telegram: Reply to Message",
|
|
125
|
+
"auth_profile_id": "telegram-bot-token",
|
|
126
|
+
"identity": "bot",
|
|
127
|
+
"required_scopes": [],
|
|
128
|
+
"policy_keys": [
|
|
129
|
+
"integrations.telegram.bot.reply"
|
|
130
|
+
],
|
|
131
|
+
"substrate": "rest",
|
|
132
|
+
"maturity": "preview",
|
|
133
|
+
"deployment_contexts": [
|
|
134
|
+
"hosted",
|
|
135
|
+
"self_managed",
|
|
136
|
+
"desktop",
|
|
137
|
+
"headless"
|
|
138
|
+
],
|
|
139
|
+
"risk": "write",
|
|
140
|
+
"component_ref": "TelegramReplyComponent"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "telegram.bot.notify",
|
|
144
|
+
"display_name": "Telegram: Send Notification",
|
|
145
|
+
"auth_profile_id": "telegram-bot-token",
|
|
146
|
+
"identity": "bot",
|
|
147
|
+
"required_scopes": [],
|
|
148
|
+
"policy_keys": [
|
|
149
|
+
"integrations.telegram.bot.notify"
|
|
150
|
+
],
|
|
151
|
+
"substrate": "rest",
|
|
152
|
+
"maturity": "preview",
|
|
153
|
+
"deployment_contexts": [
|
|
154
|
+
"hosted",
|
|
155
|
+
"self_managed",
|
|
156
|
+
"desktop",
|
|
157
|
+
"headless"
|
|
158
|
+
],
|
|
159
|
+
"risk": "write",
|
|
160
|
+
"component_ref": "TelegramNotifyComponent"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Send a plain-text Telegram message as the connected bot."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import BoolInput, MessageTextInput, MultilineInput, Output
|
|
4
|
+
from lfx.schema.data import Data
|
|
5
|
+
|
|
6
|
+
from lfx_telegram._base import TelegramActionComponent, connection_input
|
|
7
|
+
from lfx_telegram._validation import message_payload
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TelegramNotifyComponent(TelegramActionComponent):
|
|
11
|
+
display_name = "Telegram: Send Notification"
|
|
12
|
+
description = "Use Telegram Send Message with Silent Notification enabled for new flows."
|
|
13
|
+
name = "TelegramNotify"
|
|
14
|
+
legacy = True
|
|
15
|
+
replacement = ["telegram.TelegramSendMessageComponent"]
|
|
16
|
+
capability_id = "telegram.bot.notify"
|
|
17
|
+
inputs = [
|
|
18
|
+
connection_input(capabilities=[capability_id]),
|
|
19
|
+
MessageTextInput(name="chat_id", display_name="Chat ID", required=True),
|
|
20
|
+
MultilineInput(name="text", display_name="Text", required=True),
|
|
21
|
+
MessageTextInput(name="thread_id", display_name="Thread ID", advanced=True),
|
|
22
|
+
BoolInput(name="silent", display_name="Silent Notification", value=True, advanced=True),
|
|
23
|
+
]
|
|
24
|
+
outputs = [Output(display_name="Sent Message", name="message", method="build_message")]
|
|
25
|
+
|
|
26
|
+
async def build_message(self) -> Data:
|
|
27
|
+
return await self.send(
|
|
28
|
+
message_payload(chat_id=self.chat_id, text=self.text, thread_id=self.thread_id, silent=self.silent)
|
|
29
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Declare a trigger for commands addressed to a Telegram bot."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import StrInput
|
|
4
|
+
|
|
5
|
+
from lfx_telegram._triggers import TelegramTriggerComponent, trigger_inputs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TelegramOnCommandTriggerComponent(TelegramTriggerComponent):
|
|
9
|
+
display_name = "Telegram: On Command"
|
|
10
|
+
description = "Receive bot commands in explicitly allowed chats, with optional command and sender filters."
|
|
11
|
+
name = "TelegramOnCommandTrigger"
|
|
12
|
+
trigger_kind = "telegram.command"
|
|
13
|
+
capability_ids = ("telegram.trigger.command.webhook", "telegram.trigger.command.poll")
|
|
14
|
+
config_fields = (*TelegramTriggerComponent.config_fields, "commands")
|
|
15
|
+
inputs = [
|
|
16
|
+
*trigger_inputs("command"),
|
|
17
|
+
StrInput(
|
|
18
|
+
name="commands",
|
|
19
|
+
display_name="Commands",
|
|
20
|
+
is_list=True,
|
|
21
|
+
value=[],
|
|
22
|
+
info=(
|
|
23
|
+
"Up to 100 unique names of 1 to 32 lowercase letters, digits, or underscores, without a slash. "
|
|
24
|
+
"Empty means every command addressed to this bot."
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Declare a trigger for messages from explicitly allowed Telegram chats."""
|
|
2
|
+
|
|
3
|
+
from lfx_telegram._triggers import TelegramTriggerComponent, trigger_inputs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TelegramOnMessageTriggerComponent(TelegramTriggerComponent):
|
|
7
|
+
display_name = "Telegram: On Message"
|
|
8
|
+
description = "Receive new text messages visible to your bot in explicitly allowed chats."
|
|
9
|
+
name = "TelegramOnMessageTrigger"
|
|
10
|
+
trigger_kind = "telegram.message"
|
|
11
|
+
capability_ids = ("telegram.trigger.message.webhook", "telegram.trigger.message.poll")
|
|
12
|
+
inputs = trigger_inputs("message")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Reply using the target and bot identity of a Telegram trigger event."""
|
|
2
|
+
|
|
3
|
+
from lfx.integrations.errors import InvalidRequestError
|
|
4
|
+
from lfx.io import DataInput, MultilineInput, Output
|
|
5
|
+
from lfx.schema.data import Data
|
|
6
|
+
|
|
7
|
+
from lfx_telegram._base import TelegramActionComponent, connection_input
|
|
8
|
+
from lfx_telegram._validation import message_payload, numeric_id
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TelegramReplyComponent(TelegramActionComponent):
|
|
12
|
+
display_name = "Telegram: Reply to Message"
|
|
13
|
+
description = "Reply to a trigger event using the same bot, chat, and topic."
|
|
14
|
+
name = "TelegramReply"
|
|
15
|
+
capability_id = "telegram.bot.reply"
|
|
16
|
+
inputs = [
|
|
17
|
+
connection_input(capabilities=[capability_id]),
|
|
18
|
+
DataInput(name="event", display_name="Trigger Event", required=True),
|
|
19
|
+
MultilineInput(name="text", display_name="Text", required=True),
|
|
20
|
+
]
|
|
21
|
+
outputs = [Output(display_name="Sent Message", name="message", method="build_message")]
|
|
22
|
+
|
|
23
|
+
async def build_message(self) -> Data:
|
|
24
|
+
event = self.event.data if isinstance(self.event, Data) else self.event
|
|
25
|
+
if isinstance(event, dict) and isinstance(event.get("payload"), dict):
|
|
26
|
+
event = event["payload"]
|
|
27
|
+
if isinstance(event, dict):
|
|
28
|
+
event = event.get("data", event.get("delivery", event))
|
|
29
|
+
if not isinstance(event, dict) or event.get("provider") != "telegram":
|
|
30
|
+
msg = "Choose a Telegram trigger event to reply to."
|
|
31
|
+
raise InvalidRequestError(msg, provider="telegram")
|
|
32
|
+
bot_id = str(numeric_id(event.get("bot_id"), positive=True))
|
|
33
|
+
payload = message_payload(
|
|
34
|
+
chat_id=event.get("chat_id"),
|
|
35
|
+
text=self.text,
|
|
36
|
+
thread_id=event.get("thread_id"),
|
|
37
|
+
reply_id=event.get("message_id"),
|
|
38
|
+
)
|
|
39
|
+
return await self.send(payload, expected_bot_id=bot_id)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Send a plain-text Telegram message as the connected bot."""
|
|
2
|
+
|
|
3
|
+
from lfx.io import BoolInput, MessageTextInput, MultilineInput, Output
|
|
4
|
+
from lfx.schema.data import Data
|
|
5
|
+
|
|
6
|
+
from lfx_telegram._base import TelegramActionComponent, connection_input
|
|
7
|
+
from lfx_telegram._validation import message_payload
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TelegramSendMessageComponent(TelegramActionComponent):
|
|
11
|
+
display_name = "Telegram: Send Message"
|
|
12
|
+
description = "Send one plain-text message to a chat your bot can access."
|
|
13
|
+
name = "TelegramSendMessage"
|
|
14
|
+
capability_id = "telegram.bot.send"
|
|
15
|
+
inputs = [
|
|
16
|
+
connection_input(capabilities=[capability_id]),
|
|
17
|
+
MessageTextInput(name="chat_id", display_name="Chat ID", required=True),
|
|
18
|
+
MultilineInput(name="text", display_name="Text", required=True),
|
|
19
|
+
MessageTextInput(name="thread_id", display_name="Thread ID", advanced=True),
|
|
20
|
+
BoolInput(name="silent", display_name="Silent Notification", value=False, advanced=True),
|
|
21
|
+
]
|
|
22
|
+
outputs = [Output(display_name="Sent Message", name="message", method="build_message")]
|
|
23
|
+
|
|
24
|
+
async def build_message(self) -> Data:
|
|
25
|
+
return await self.send(
|
|
26
|
+
message_payload(chat_id=self.chat_id, text=self.text, thread_id=self.thread_id, silent=self.silent)
|
|
27
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.langflow.org/extension/v1.json",
|
|
3
|
+
"id": "lfx-telegram",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"name": "Telegram",
|
|
6
|
+
"description": "Telegram bot message and command triggers, messages, replies, and notifications.",
|
|
7
|
+
"lfx": {
|
|
8
|
+
"compat": [
|
|
9
|
+
"1"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"bundles": [
|
|
13
|
+
{
|
|
14
|
+
"name": "telegram",
|
|
15
|
+
"path": "components/telegram"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"integrations": [
|
|
19
|
+
{
|
|
20
|
+
"provider_id": "telegram",
|
|
21
|
+
"bundle": "telegram",
|
|
22
|
+
"path": "capabilities.v1.json"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lfx-telegram
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Connection-backed Telegram bot actions and triggers for Langflow.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: <3.16,>=3.10
|
|
7
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
8
|
+
Requires-Dist: lfx<2.0.0,>=1.14.0.dev0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Telegram integration
|
|
12
|
+
|
|
13
|
+
This bundle provides connection-backed Telegram bot actions and webhook or polling triggers.
|
|
14
|
+
|
|
15
|
+
See the [Telegram integration setup guide](https://docs.langflow.org/next/bundles-telegram) for installation, permissions, supported environments, receiver setup, and recovery. The guide's source is [bundles-telegram.mdx](../../../docs/docs/Components/bundles-telegram.mdx).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
lfx_telegram/__init__.py,sha256=1OehYfJnFZk9QxvqiNMcfFNBCiqDLOUax01_lTNmdc4,472
|
|
2
|
+
lfx_telegram/_base.py,sha256=k9Kgw-tOktcOOveAScYJpnTg4lPcxLpqI6urO3F1CCk,2723
|
|
3
|
+
lfx_telegram/_client.py,sha256=KioO1orkQi8XNgTE3xzuset_pPZyK_vJIYwAlO7duvk,5763
|
|
4
|
+
lfx_telegram/_triggers.py,sha256=ik4llIc2qOJ2yKbV4ZNUIiRMnJ3UecuRB6VYZyuiS3o,2693
|
|
5
|
+
lfx_telegram/_validation.py,sha256=puknt0syP35jHzXuXMPW_tf8Sv7A0gQqpx_MXmHk_70,2016
|
|
6
|
+
lfx_telegram/extension.json,sha256=M6f6T8Pifhttf4s8n-W0Ys_hgLWxKNQDWPUmK7imWMQ,512
|
|
7
|
+
lfx_telegram/components/__init__.py,sha256=uqUIw8mTuKaYpUBXVqmRs4d3r6NZkmQMD2KB8U4nFBM,36
|
|
8
|
+
lfx_telegram/components/telegram/__init__.py,sha256=Tu3TMmvau_7IuW8lchYOETQlDHZTwYdI3H88LLFxmb8,531
|
|
9
|
+
lfx_telegram/components/telegram/capabilities.v1.json,sha256=YyD_rQJhZHqzPr7w4qG6A2RQhjWSqMeSyGHPl8aZY9M,4331
|
|
10
|
+
lfx_telegram/components/telegram/telegram_notify.py,sha256=70JiocryDhUC55-k9DXwODnqxESwHM94IHaz9UmulOE,1347
|
|
11
|
+
lfx_telegram/components/telegram/telegram_on_command.py,sha256=hPfgLn08r7goHBMO2mUHk-YvvHnsbuBftWiHDG63wbQ,1050
|
|
12
|
+
lfx_telegram/components/telegram/telegram_on_message.py,sha256=mQOioJCqwnNA5t1oiOGGb-oYqbZjF8-HEig0KrBvFVo,566
|
|
13
|
+
lfx_telegram/components/telegram/telegram_reply.py,sha256=uMDlX20cFRkcfCBCQ-ZVwdwg9SUpqqkst1RkitQY2ZY,1824
|
|
14
|
+
lfx_telegram/components/telegram/telegram_send_message.py,sha256=HrhqMmzAJgjwD1uzeI06HmVkJsP2L2nW8bqcF72WtaE,1258
|
|
15
|
+
lfx_telegram-0.1.0.dist-info/METADATA,sha256=LNJwI7_QxiymLs41EZW-5OBT4jAR12AfoaOD2EOroFU,683
|
|
16
|
+
lfx_telegram-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
17
|
+
lfx_telegram-0.1.0.dist-info/entry_points.txt,sha256=WhAd6NmBVIULJ35jXEDdGO6EI13UINggtlAIaQmWopA,155
|
|
18
|
+
lfx_telegram-0.1.0.dist-info/RECORD,,
|