Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions samples/tdlib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### TDLib integration sample

This is an example of integration with the TDLib. It utilizes auxiliary plugins that extend the agent's capabilities through a standard set of interaction tasks, to which skills are applied that implement message processing and state management using TDLib.

### Environments

To set up and run the example, use the following environment variables. They are necessary for
proper connection to external suppliers/consumers.

```properties
export SIDUS_AI_CORE_PATH="SIDUS_AI_CORE_PATH"
export TDLIB_API_ID="API_ID"
export TDLIB_API_HASH="API_HASH"
export TDLIB_PATH="TDLIB_PATH"
```
90 changes: 90 additions & 0 deletions samples/tdlib/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import os
import sys
import time
sys.path.append(os.environ.get('SIDUS_AI_CORE_PATH'))
from sidusai.plugins.tdlib import TDLibAiAgent
from ctypes.util import find_library

def main():
print("TDLib Assistent")
print("="*60)

API_ID = os.environ.get('TDLIB_API_ID') #94575
API_HASH = os.environ.get('TDLIB_API_HASH') #"a3406de8d171bb422bb6ddf3bbd800e2"
TDLIB_PATH = os.environ.get('TDLIB_PATH') #find_library("tdjson")

print(f"Using API ID: {API_ID}")

try:
agent = TDLibAiAgent(
api_id=API_ID,
api_hash=API_HASH,
system_prompt="Assistant",
database_directory="./tdlib",
tdlib_directory=TDLIB_PATH
)

print(f"\n✓ Agent created")
print(f"Client ID: {agent.tdlib_manager.client_id}")

def auth_handler(client_id: int, auth_type: str):
print(f"\n[Authentication required] Type: {auth_type}")

if auth_type == 'phone':
phone = input("Please enter your phone number (international format): ")
if phone:
agent.send({
"@type": "setAuthenticationPhoneNumber",
"phone_number": phone
})

elif auth_type == 'code':
code = input("Please enter the authentication code you received: ")
if code:
agent.send({
"@type": "checkAuthenticationCode",
"code": code
})

elif auth_type == 'password':
password = input("Please enter your password: ")
if password:
agent.send({
"@type": "checkAuthenticationPassword",
"password": password
})

agent.on_auth_required = auth_handler

print("\n" + "="*60)
print("Agent is running. Waiting for TDLib response...")
print("Press Ctrl+C to stop")
print("="*60 + "\n")

try:
counter = 0
while True:
agent._tdlib_polling_loop()

counter += 1
if counter % 100 == 0:
print(f"[{counter}] Running...")

time.sleep(0.1)

except KeyboardInterrupt:
print("\n\nStopping agent...")

finally:
agent.close()

except Exception as e:
print(f"\n✗ Error: {e}")
import traceback
traceback.print_exc()
return 1

return 0

if __name__ == "__main__":
sys.exit(main())
126 changes: 126 additions & 0 deletions sidusai/plugins/tdlib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import sidusai as sai
import json
import time
from typing import Any, Dict, Optional, Callable
from .components import TDLibManager

__default_database_directory__ = "./tdlib_data"
__default_tdlib_directory__ = "./"

class TDLibAiAgent(sai.Agent):
def __init__(self, api_id: int, api_hash: str, system_prompt: str = "",
database_directory: str = __default_database_directory__, tdlib_directory: str = __default_tdlib_directory__):
super().__init__("tdlib_ai_agent")

self.api_id = api_id
self.api_hash = api_hash
self.database_directory = database_directory
self.system_prompt = system_prompt,
self.tdlib_directory = tdlib_directory

print(f"Initializing TDLib agent...")

self.tdlib_manager = TDLibManager(
api_id=api_id,
api_hash=api_hash,
database_directory=database_directory,
tdlib_directory=tdlib_directory
)

self.on_auth_required: Optional[Callable] = None

print("Setting TDLib parameters...")
self.tdlib_manager.set_tdlib_parameters(self.tdlib_manager.client_id)

self.add_loop_method(self._tdlib_polling_loop)

print("✓ TDLib AiAgent initialized")

def _tdlib_polling_loop(self):
try:
event = self.tdlib_manager.receive(0.1)
if event:
self._process_event(event)
except Exception as e:
print(f"Error in polling loop: {e}")
time.sleep(0.1)

def _process_event(self, event: Dict[str, Any]):
event_type = event.get("@type", "")

if event_type != "updateAuthorizationState":
print(f"Receive: {json.dumps(event, indent=2)}")

if event_type == "updateAuthorizationState":
self._handle_auth_state(event)

elif event_type == "updateNewMessage":
self._handle_new_message(event)

def _handle_auth_state(self, event: Dict[str, Any]):
auth_state = event.get("authorization_state", {})
auth_type = auth_state.get("@type", "")

print(f"Authorization state: {auth_type}")

if auth_type == "authorizationStateWaitTdlibParameters":
pass

elif auth_type == "authorizationStateWaitPhoneNumber":
if self.on_auth_required:
self.on_auth_required(self.tdlib_manager.client_id, 'phone')
else:
print("Phone number required (set on_auth_required callback)")

elif auth_type == "authorizationStateWaitCode":
if self.on_auth_required:
self.on_auth_required(self.tdlib_manager.client_id, 'code')

elif auth_type == "authorizationStateWaitPassword":
if self.on_auth_required:
self.on_auth_required(self.tdlib_manager.client_id, 'password')

elif auth_type == "authorizationStateReady":
print("✓ Authorization complete! You are now logged in.")

def _handle_new_message(self, event: Dict[str, Any]):
message = event.get('message', {})
content = message.get('content', {})

if content.get('@type') == 'messageText':
text = content.get('text', {}).get('text', '')
chat_id = message.get('chat_id', 0)

print(f"New message in chat {chat_id}: {text[:100]}")

if text and chat_id:
self.send_message(f"Echo: {text}", chat_id)

def send(self, query: Dict[str, Any]) -> None:
self.tdlib_manager.send(query)

def send_message(self, text: str, chat_id: int):
message = {
"@type": "sendMessage",
"chat_id": chat_id,
"input_message_content": {
"@type": "inputMessageText",
"text": {
"@type": "formattedText",
"text": text
}
}
}

self.send(message)
print(f"Sent to chat {chat_id}: {text[:50]}...")

def execute(self, query: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return self.tdlib_manager.execute(query)

def receive(self, timeout: float = 1.0) -> Optional[Dict[str, Any]]:
return self.tdlib_manager.receive(timeout)

def close(self):
print("Closing TDLib agent")
self.tdlib_manager.close()
107 changes: 107 additions & 0 deletions sidusai/plugins/tdlib/components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import json
import os
import sys
from ctypes import CDLL, CFUNCTYPE, c_char_p, c_double, c_int
from typing import Any, Dict

class TDLibManager:
def __init__(self, api_id: int, api_hash: str, database_directory: str = "./tdlib_data", tdlib_directory: str = "./"):
self.api_id = api_id
self.api_hash = api_hash
self.database_directory = os.path.abspath(database_directory)
self.tdlib_directory = tdlib_directory

print(f"TDLibManager initialized with API ID: {api_id}")

os.makedirs(self.database_directory, exist_ok=True)

self._load_library()
self._setup_functions()
self._setup_logging()

self.client_id = self._td_create_client_id()

print(f"TDLibManager: Created client ID {self.client_id}")

def _load_library(self):
tdjson_path = self.tdlib_directory
if tdjson_path is None:
if os.name == "nt":
tdjson_path = os.path.join(os.path.dirname(__file__), "tdjson.dll")
else:
sys.exit("Error: Can't find 'tdjson' library.")

try:
self.tdjson = CDLL(tdjson_path)
print(f"Loaded TDLib from: {tdjson_path}")
except Exception as e:
sys.exit(f"Error loading TDLib: {e}")

def _setup_functions(self):
self._td_create_client_id = self.tdjson.td_create_client_id
self._td_create_client_id.restype = c_int
self._td_create_client_id.argtypes = []

self._td_receive = self.tdjson.td_receive
self._td_receive.restype = c_char_p
self._td_receive.argtypes = [c_double]

self._td_send = self.tdjson.td_send
self._td_send.restype = None
self._td_send.argtypes = [c_int, c_char_p]

self._td_execute = self.tdjson.td_execute
self._td_execute.restype = c_char_p
self._td_execute.argtypes = [c_char_p]

self.log_message_callback_type = CFUNCTYPE(None, c_int, c_char_p)
self._td_set_log_message_callback = self.tdjson.td_set_log_message_callback
self._td_set_log_message_callback.restype = None
self._td_set_log_message_callback.argtypes = [
c_int,
self.log_message_callback_type,
]

def _setup_logging(self, verbosity_level: int = 1):
@self.log_message_callback_type
def on_log_message_callback(verbosity_level, message):
if verbosity_level == 0:
sys.exit(f"TDLib fatal error: {message.decode('utf-8')}")

self._td_set_log_message_callback(2, on_log_message_callback)
self.execute({"@type": "setLogVerbosityLevel", "new_verbosity_level": verbosity_level})

def set_tdlib_parameters(self, client_id: int):
params = {
"@type": "setTdlibParameters",
"database_directory": self.database_directory,
"use_message_database": True,
"use_secret_chats": True,
"api_id": self.api_id,
"api_hash": self.api_hash,
"system_language_code": "en",
"device_model": "Python TDLib Client",
"application_version": "1.1",
}
print(f"Setting TDLib parameters...")
self.send(params)

def execute(self, query: Dict[str, Any]):
query_json = json.dumps(query).encode("utf-8")
result = self._td_execute(query_json)
if result:
return json.loads(result.decode("utf-8"))
return None

def send(self, query: Dict[str, Any]):
query_json = json.dumps(query).encode("utf-8")
self._td_send(self.client_id, query_json)

def receive(self, timeout: float = 1.0):
result = self._td_receive(timeout)
if result:
return json.loads(result.decode("utf-8"))
return None

def close(self):
print("Closing TDLib manager")