2023-09-15 17:35:12 -07:00
import logging
import threading
import asyncio
import uuid
2023-09-18 23:25:44 -07:00
import time
2023-09-29 17:22:22 -07:00
import aiofiles
2023-09-15 17:35:12 -07:00
2023-10-01 02:09:44 -07:00
from telegram import Update , Message
2023-09-18 23:25:44 -07:00
from telegram import Bot
from telegram.ext import Updater
from telegram.error import Forbidden , NetworkError
2023-09-28 18:30:07 +08:00
from knowledge.object.object_id import ObjectType
from .knowledge_base import KnowledgeBase
2023-09-15 17:35:12 -07:00
from .tunnel import AgentTunnel
2023-09-25 20:57:10 -07:00
from .storage import AIStorage
2023-09-21 20:51:21 -07:00
from .contact_manager import ContactManager , Contact , FamilyMember
2023-10-18 11:19:11 -07:00
from .agent_base import AgentMsg , AgentMsgType
2023-09-15 17:35:12 -07:00
2023-09-21 20:51:21 -07:00
2023-09-15 17:35:12 -07:00
logger = logging . getLogger ( __name__ )
class TelegramTunnel ( AgentTunnel ):
@classmethod
def register_to_loader ( cls ):
async def load_tg_tunnel ( config : dict ) -> AgentTunnel :
result_tunnel = TelegramTunnel ( "" )
if await result_tunnel . load_from_config ( config ):
return result_tunnel
else :
return None
2023-09-24 16:26:18 +08:00
2023-09-15 17:35:12 -07:00
AgentTunnel . register_loader ( "TelegramTunnel" , load_tg_tunnel )
2023-09-24 16:26:18 +08:00
2023-09-15 17:35:12 -07:00
async def load_from_config ( self , config : dict ) -> bool :
2023-09-26 18:46:26 -07:00
self . type = "TelegramTunnel"
2023-09-15 17:35:12 -07:00
self . tg_token = config [ "token" ]
self . target_id = config [ "target" ]
self . tunnel_id = config [ "tunnel_id" ]
2023-09-26 18:46:26 -07:00
if config . get ( "allow" ) is not None :
self . allow_group = config [ "allow" ]
2023-09-15 17:35:12 -07:00
return True
def dump_to_config ( self ) -> dict :
pass
def __init__ ( self , tg_token : str ) -> None :
super () . __init__ ()
self . is_start = False
self . tg_token = tg_token
2023-09-19 00:23:19 -07:00
self . bot : Bot = None
self . update_queue = None
2023-09-26 18:46:26 -07:00
self . allow_group = "contact"
2023-09-28 21:45:34 -07:00
self . in_process_tg_msg = {}
2023-09-15 17:35:12 -07:00
2023-09-18 23:25:44 -07:00
async def _do_process_raw_message ( self , bot : Bot , update_id : int ) -> int :
# Request updates after the last update_id
updates = await bot . get_updates ( offset = update_id , timeout = 10 , allowed_updates = Update . ALL_TYPES )
for update in updates :
next_update_id = update . update_id + 1
if update . message and update . message . text :
2023-09-19 00:23:19 -07:00
await self . on_message ( bot , update )
2023-09-18 23:25:44 -07:00
return next_update_id
2023-09-24 16:26:18 +08:00
2023-09-18 23:25:44 -07:00
return update_id
2023-09-15 17:35:12 -07:00
async def start ( self ) -> bool :
if self . is_start :
logger . warning ( f "tunnel { self . tunnel_id } is already started" )
return False
2023-09-24 16:26:18 +08:00
self . is_start = True
2023-09-19 15:43:17 -07:00
logger . info ( f "tunnel { self . tunnel_id } is starting..." )
2023-09-15 17:35:12 -07:00
2023-09-18 23:25:44 -07:00
self . bot = Bot ( self . tg_token )
2023-10-01 02:09:44 -07:00
self . bot_username = ( await self . bot . get_me ()) . username
2023-09-18 23:25:44 -07:00
self . update_queue = asyncio . Queue ()
self . bot_updater = Updater ( self . bot , update_queue = self . update_queue )
2023-09-24 16:26:18 +08:00
2023-09-15 17:35:12 -07:00
2023-09-18 23:25:44 -07:00
async def _run_app ():
try :
update_id = ( await self . bot . get_updates ())[ 0 ] . update_id
except IndexError :
update_id = None
#logger.info("listening for new messages...")
while True :
try :
update_id = await self . _do_process_raw_message ( self . bot , update_id )
except NetworkError :
await asyncio . sleep ( 1 )
except Forbidden :
# The user has removed or blocked the bot.
update_id += 1
2023-09-26 18:46:26 -07:00
except Exception as e :
logger . error ( f "tg_tunnel error: { e } " )
await asyncio . sleep ( 1 )
2023-09-18 23:25:44 -07:00
asyncio . create_task ( _run_app ())
2023-09-19 15:43:17 -07:00
logger . info ( f "tunnel { self . tunnel_id } started." )
2023-09-15 17:35:12 -07:00
return True
async def close ( self ) -> None :
pass
async def _process_message ( self , msg : AgentMsg ) -> None :
logger . warn ( f "process message { msg . msg_id } from { msg . sender } to { msg . target } " )
2023-10-01 02:09:44 -07:00
async def conver_tg_msg_to_agent_msg ( self , message : Message ) -> AgentMsg :
2023-09-15 17:35:12 -07:00
agent_msg = AgentMsg ()
agent_msg . topic = "_telegram"
2023-10-01 02:09:44 -07:00
agent_msg . msg_id = "tg_msg#" + str ( message . message_id ) + "#" + uuid . uuid4 () . hex
2023-09-15 17:35:12 -07:00
agent_msg . target = self . target_id
2023-10-01 02:09:44 -07:00
agent_msg . body = message . text
2023-09-18 23:25:44 -07:00
agent_msg . create_time = time . time ()
2023-10-01 02:09:44 -07:00
messag_type = message . chat . type
if messag_type == "supergroup" or messag_type == "group" :
agent_msg . target = f "tg_group { message . chat_id } "
agent_msg . msg_type = AgentMsgType . TYPE_GROUPMSG
agent_msg . mentions = []
else :
agent_msg . msg_type = AgentMsgType . TYPE_MSG
if message . entities :
for entity in message . entities :
if entity . type == 'mention' :
mention = message . text [ entity . offset : entity . offset + entity . length ]
if mention == '@' + self . bot_username :
agent_msg . mentions . append ( self . target_id )
else :
agent_msg . mentions . append ( mention )
if message . caption_entities :
for entity in message . caption_entities :
if entity . type == 'mention' :
mention = message . caption [ entity . offset : entity . offset + entity . length ]
if mention == '@' + self . bot_username :
agent_msg . mentions . append ( self . target_id )
else :
agent_msg . mentions . append ( mention )
2023-09-15 17:35:12 -07:00
return agent_msg
2023-10-01 02:09:44 -07:00
def is_bot_mentioned ( self , message : Message ):
if message . entities :
for entity in message . entities :
if entity . type == 'mention' :
mention = message . text [ entity . offset : entity . offset + entity . length ]
if mention == '@' + self . bot_username :
return True
if message . caption_entities :
for entity in message . caption_entities :
if entity . type == 'mention' :
mention = message . caption [ entity . offset : entity . offset + entity . length ]
if mention == '@' + self . bot_username :
return True
2023-09-15 17:35:12 -07:00
2023-10-01 02:09:44 -07:00
return False
2023-09-15 17:35:12 -07:00
2023-09-19 00:23:19 -07:00
async def on_message ( self , bot : Bot , update : Update ) -> None :
2023-10-01 02:09:44 -07:00
message = update . message
logger . info ( f "on_message: { message . message_id } from { message . from_user . username } ( { update . effective_user . username } ) to { message . chat . title } ( { message . chat . id } )" )
2023-09-21 20:51:21 -07:00
if update . effective_user . is_bot :
logger . warning ( f "ignore message from telegram bot { update . effective_user . id } " )
return None
2023-09-28 21:45:34 -07:00
if self . in_process_tg_msg . get ( update . message . message_id ) is not None :
logger . warning ( f "ignore message from telegram bot { update . effective_user . id } " )
return None
self . in_process_tg_msg [ update . message . message_id ] = True
2023-10-01 02:09:44 -07:00
agent_msg = await self . conver_tg_msg_to_agent_msg ( message )
2023-09-21 20:51:21 -07:00
cm : ContactManager = ContactManager . get_instance ()
2023-09-15 17:35:12 -07:00
reomte_user_name = f " { update . effective_user . id } @telegram"
2023-09-25 20:57:10 -07:00
2023-09-21 20:51:21 -07:00
contact : Contact = cm . find_contact_by_telegram ( update . effective_user . username )
if contact is None :
contact = cm . find_contact_by_telegram ( str ( update . effective_user . id ))
if contact is not None :
reomte_user_name = contact . name
2023-09-26 18:46:26 -07:00
if not contact . is_family_member :
if self . allow_group != "contact" and self . allow_group != "guest" :
2023-09-29 17:22:22 -07:00
await update . message . reply_text ( f "You're not supposed to talk to me! Please contact my father~" )
2023-09-26 18:46:26 -07:00
return
2023-09-21 20:51:21 -07:00
else :
2023-09-26 18:46:26 -07:00
if self . allow_group != "guest" :
2023-09-29 17:22:22 -07:00
await update . message . reply_text ( f "You're not supposed to talk to me! Please contact my father~" )
2023-09-26 18:46:26 -07:00
return
2023-09-21 20:51:21 -07:00
if cm . is_auto_create_contact_from_telegram :
contact_name = update . effective_user . first_name
if update . effective_user . last_name is not None :
contact_name += " " + update . effective_user . last_name
contact = Contact ( contact_name )
contact . telegram = update . effective_user . username if update . effective_user . username is not None else str ( update . effective_user . id )
contact . added_by = self . target_id
cm . add_contact ( contact . name , contact )
reomte_user_name = contact . name
2023-09-25 20:57:10 -07:00
2023-10-01 02:09:44 -07:00
2023-09-15 17:35:12 -07:00
agent_msg . sender = reomte_user_name
2023-09-18 23:25:44 -07:00
logger . info ( f "process message { agent_msg . msg_id } from { agent_msg . sender } to { agent_msg . target } " )
2023-10-01 02:09:44 -07:00
if agent_msg . msg_type == AgentMsgType . TYPE_GROUPMSG :
self . ai_bus . register_message_handler ( agent_msg . target , self . _process_message )
resp_msg = await self . ai_bus . send_message ( agent_msg , self . target_id , agent_msg . target )
else :
self . ai_bus . register_message_handler ( reomte_user_name , self . _process_message )
resp_msg = await self . ai_bus . send_message ( agent_msg )
#await bot.send_chat_action(chat_id=update.effective_chat.id, action="typing")
2023-09-15 17:35:12 -07:00
if resp_msg is None :
2023-09-26 22:50:50 -07:00
await update . message . reply_text ( f "System Error: Timeout, { self . target_id } no resopnse! Please check logs/aios.log for more details!" )
2023-09-15 17:35:12 -07:00
else :
if resp_msg . body_mime is None :
2023-10-01 02:09:44 -07:00
if resp_msg . body is None :
return
if len ( resp_msg . body ) < 1 :
await update . message . reply_text ( "" )
return
knowledge_object = KnowledgeBase () . parse_object_in_message ( resp_msg . body )
if knowledge_object is not None :
if knowledge_object . get_object_type () == ObjectType . Image :
image = KnowledgeBase () . bytes_from_object ( knowledge_object )
try :
async with aiofiles . open ( "tg_send_temp.png" , mode = 'wb' ) as local_file :
if local_file :
await local_file . write ( image )
await update . message . reply_photo ( "tg_send_temp.png" )
except Exception as e :
logger . error ( f "save image error: { e } " )
return
else :
pos = resp_msg . body . find ( "audio file" )
if pos != - 1 :
audio_file = resp_msg . body [ pos + 11 :] . strip ()
if audio_file . startswith ( " \" " ):
audio_file = audio_file [ 1 : - 1 ]
await update . message . reply_voice ( audio_file )
return
2023-09-15 17:35:12 -07:00
await update . message . reply_text ( resp_msg . body )
else :
if resp_msg . body_mime . startswith ( "image" ):
photo_file = open ( resp_msg . body , "rb" )
if photo_file :
await update . message . reply_photo ( resp_msg . body )
2023-09-21 11:46:37 -07:00
photo_file . close ()
2023-09-15 17:35:12 -07:00
else :
await update . message . reply_text ( resp_msg . body )
2023-09-24 16:26:18 +08:00
2023-09-15 17:35:12 -07:00
else :
await update . message . reply_text ( resp_msg . body )
2023-09-24 16:26:18 +08:00