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.
@@ -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
+ )
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [langflow.extensions]
2
+ lfx-telegram = lfx_telegram
3
+
4
+ [langflow.trigger_providers]
5
+ telegram = langflow.services.triggers.providers.telegram.provider:PROVIDER