455 lines
18 KiB
Python
455 lines
18 KiB
Python
|
#!/usr/bin/env python3
|
||
|
|
||
|
from threading import Thread
|
||
|
from cmd import Cmd
|
||
|
from queue import Queue
|
||
|
from ctypes import *
|
||
|
import json
|
||
|
import sys
|
||
|
import os
|
||
|
import time
|
||
|
import array
|
||
|
|
||
|
# Telegram API ID and API Hash.
|
||
|
iApiID = 0
|
||
|
sApiHash = ''
|
||
|
|
||
|
# Used to kill all the loops
|
||
|
bMasterSwitch = True
|
||
|
|
||
|
# Queue Client >>> Server
|
||
|
qInputQueue = Queue()
|
||
|
|
||
|
# Queue Server >>> Client
|
||
|
qOutputQueue = Queue()
|
||
|
|
||
|
# State of the authentification
|
||
|
bConStatus = False
|
||
|
|
||
|
# Personnal Informations Object
|
||
|
objMe = None
|
||
|
|
||
|
# List of known Users
|
||
|
objUsers = []
|
||
|
|
||
|
# List of Known ChatRoom
|
||
|
objChatRooms = []
|
||
|
|
||
|
# Index of the active Chatroom
|
||
|
iActiveRoom = -1
|
||
|
|
||
|
# td Lib Json 1.6.6
|
||
|
tdjson = CDLL("./libtdjson.so")
|
||
|
|
||
|
td_json_client_create = tdjson.td_json_client_create
|
||
|
td_json_client_create.restype = c_void_p
|
||
|
td_json_client_create.argtypes = []
|
||
|
|
||
|
td_json_client_receive = tdjson.td_json_client_receive
|
||
|
td_json_client_receive.restype = c_char_p
|
||
|
td_json_client_receive.argtypes = [c_void_p, c_double]
|
||
|
|
||
|
td_json_client_send = tdjson.td_json_client_send
|
||
|
td_json_client_send.restype = None
|
||
|
td_json_client_send.argtypes = [c_void_p, c_char_p]
|
||
|
|
||
|
td_json_client_execute = tdjson.td_json_client_execute
|
||
|
td_json_client_execute.restype = c_char_p
|
||
|
td_json_client_execute.argtypes = [c_void_p, c_char_p]
|
||
|
|
||
|
td_json_client_destroy = tdjson.td_json_client_destroy
|
||
|
td_json_client_destroy.restype = None
|
||
|
td_json_client_destroy.argtypes = [c_void_p]
|
||
|
|
||
|
fatal_error_callback_type = CFUNCTYPE(None, c_char_p)
|
||
|
|
||
|
td_set_log_fatal_error_callback = tdjson.td_set_log_fatal_error_callback
|
||
|
td_set_log_fatal_error_callback.restype = None
|
||
|
td_set_log_fatal_error_callback.argtypes = [fatal_error_callback_type]
|
||
|
|
||
|
def on_fatal_error_callback(error_message):
|
||
|
print('TDLib fatal error: ', error_message)
|
||
|
|
||
|
def td_execute(query):
|
||
|
query = json.dumps(query).encode('utf-8')
|
||
|
result = td_json_client_execute(None, query)
|
||
|
if result:
|
||
|
result = json.loads(result.decode('utf-8'))
|
||
|
return result
|
||
|
|
||
|
c_on_fatal_error_callback = fatal_error_callback_type(on_fatal_error_callback)
|
||
|
td_set_log_fatal_error_callback(c_on_fatal_error_callback)
|
||
|
|
||
|
# setting TDLib log verbosity level to 1 (errors)
|
||
|
td_execute({'@type': 'setLogVerbosityLevel', 'new_verbosity_level': 1, '@extra': 1.01234})
|
||
|
|
||
|
client = td_json_client_create()
|
||
|
|
||
|
# simple wrappers for client usage
|
||
|
def td_send(query):
|
||
|
query = json.dumps(query).encode('utf-8')
|
||
|
td_json_client_send(client, query)
|
||
|
|
||
|
def td_receive():
|
||
|
result = td_json_client_receive(client, 1.0)
|
||
|
if result:
|
||
|
result = json.loads(result.decode('utf-8'))
|
||
|
return result
|
||
|
|
||
|
|
||
|
# another test for TDLib execute method
|
||
|
td_execute({'@type': 'getTextEntities', 'text': '@telegram /test_command https://telegram.org telegram.me', '@extra': ['5', 7.0]})
|
||
|
|
||
|
|
||
|
# testing TDLib send method
|
||
|
td_send({'@type': 'getAuthorizationState', '@extra': 1.01234})
|
||
|
|
||
|
# Personnal informations object
|
||
|
class objPersonnal:
|
||
|
def __init__(self, ftid, first_name, last_name, username, phone_number):
|
||
|
self.id = ftid
|
||
|
self.first_name = first_name
|
||
|
self.last_name = last_name
|
||
|
self.username = username
|
||
|
self.phone_number = phone_number
|
||
|
def toJSON(self):
|
||
|
return json.dumps(self, default=lambda o: o.__dict__,
|
||
|
sort_keys=True, indent=4)
|
||
|
|
||
|
# Know Users object
|
||
|
class createUser:
|
||
|
def __init__(self, ftid, first_name, last_name, username, phone_number):
|
||
|
self.id = ftid
|
||
|
self.first_name = first_name
|
||
|
self.last_name = last_name
|
||
|
self.username = username
|
||
|
self.phone_number = phone_number
|
||
|
def toJSON(self):
|
||
|
return json.dumps(self, default=lambda o: o.__dict__,
|
||
|
sort_keys=True, indent=4)
|
||
|
|
||
|
# Known Chat room Object
|
||
|
class createChatRoom:
|
||
|
def __init__(self, ftid, sType, iUserId, sTitle):
|
||
|
self.id = ftid
|
||
|
self.sType = sType
|
||
|
self.iUserId = iUserId
|
||
|
self.sTitle = sTitle
|
||
|
self.rMessage = []
|
||
|
|
||
|
# Update user if present. Otherwise create it
|
||
|
def updateUsers(ftid, first_name, last_name, username, phone_number):
|
||
|
i = 0;
|
||
|
bFound = False
|
||
|
while i < len(objUsers):
|
||
|
if (objUsers[i].id == ftid):
|
||
|
bFound = True
|
||
|
objUsers[i].first_name = first_name
|
||
|
objUsers[i].last_name = last_name
|
||
|
objUsers[i].username = username
|
||
|
objUsers[i].phone_number = phone_number
|
||
|
i = i + 1
|
||
|
if bFound is False:
|
||
|
tmpUser = createUser(ftid, first_name, last_name, username, phone_number)
|
||
|
objUsers.append(tmpUser)
|
||
|
|
||
|
# Update Chatroom if present. Otherwise create it
|
||
|
def updateChatRooms(ftid, sType, iUserId, sTitle):
|
||
|
i = 0;
|
||
|
bFound = False
|
||
|
while i < len(objChatRooms):
|
||
|
if (objChatRooms[i].id == ftid):
|
||
|
bFound = True
|
||
|
objChatRooms[i].sType = sType
|
||
|
objChatRooms[i].iUserId = iUserId
|
||
|
objChatRooms[i].sTitle = sTitle
|
||
|
i = i + 1
|
||
|
if bFound is False:
|
||
|
if not os.path.exists('./logs/' + str(ftid) + '.txt'):
|
||
|
with open('./logs/' + str(ftid) + '.txt', 'w'): pass
|
||
|
tmpChatRoom = createChatRoom(ftid, sType, iUserId, sTitle)
|
||
|
objChatRooms.append(tmpChatRoom)
|
||
|
|
||
|
# Object for recieved Message
|
||
|
class createMessages:
|
||
|
def __init__(self, message_id, sender_user_id, messageText):
|
||
|
self.message_id = message_id
|
||
|
self.sender_user_id = sender_user_id
|
||
|
self.messageText = messageText
|
||
|
|
||
|
# Return Index of chat room (objChatRooms) from ID
|
||
|
def getChatIndexById(chat_id):
|
||
|
i = 0
|
||
|
while (i < len(objChatRooms)):
|
||
|
if objChatRooms[i].id == chat_id:
|
||
|
return i
|
||
|
i = i + 1
|
||
|
return -1
|
||
|
|
||
|
# Add a new recieved message
|
||
|
def addNewMessage(message_id, chat_id, sender_user_id, messageText):
|
||
|
global iActiveRoom
|
||
|
|
||
|
iIndex = getChatIndexById(chat_id)
|
||
|
if (iIndex >= 0):
|
||
|
tmpMsg = createMessages(message_id, sender_user_id, messageText)
|
||
|
objChatRooms[iIndex].rMessage.append(tmpMsg)
|
||
|
f = open("./logs/" + str(chat_id) +".txt", "a+")
|
||
|
f.write(formatMessage(tmpMsg) + '\n')
|
||
|
f.close()
|
||
|
if iActiveRoom == iIndex:
|
||
|
print(formatMessage(tmpMsg))
|
||
|
else:
|
||
|
print("No chat found for that message")
|
||
|
|
||
|
# Format a recieved message
|
||
|
def formatMessage(objMessage):
|
||
|
i = 0
|
||
|
sUserCaption = "Anomimous"
|
||
|
while i < len(objUsers):
|
||
|
if objUsers[i].id == objMessage.sender_user_id:
|
||
|
sUserCaption = objUsers[i].first_name + " " + objUsers[i].last_name + " [" + objUsers[i].username + "]"
|
||
|
i = i + 1
|
||
|
sReturn = sUserCaption + ": " + objMessage.messageText
|
||
|
return sReturn
|
||
|
|
||
|
# The client; Handle servers prompts and it's message.
|
||
|
class ftClient(Thread):
|
||
|
def __init__(self):
|
||
|
Thread.__init__(self)
|
||
|
self.daemon = True
|
||
|
self.start()
|
||
|
def run(self):
|
||
|
global bMasterSwitch
|
||
|
global qInputQueue
|
||
|
global qOutputQueue
|
||
|
global objMe
|
||
|
global objUsers;
|
||
|
|
||
|
while bMasterSwitch:
|
||
|
#print('Waiting instruction...')
|
||
|
query = qOutputQueue.get()
|
||
|
sResponse = None
|
||
|
if query == "askPhoneNumber":
|
||
|
sResponse = input('askPhoneNumber: ')
|
||
|
elif query == "askAuthCode":
|
||
|
sResponse = input('askAuthCode: ')
|
||
|
elif query == "askFistName":
|
||
|
sResponse = input('askFistName: ')
|
||
|
elif query == "askLastName":
|
||
|
sResponse = input('askLastName: ')
|
||
|
elif query == "askPassword":
|
||
|
sResponse = input('askPassword: ')
|
||
|
elif query['@type'] is not None:
|
||
|
if query['@type'] == 'updateUser':
|
||
|
updateUsers(query['user']['id'], query['user']['first_name'], query['user']['last_name'], query['user']['username'], query['user']['phone_number'])
|
||
|
elif query['@type'] == 'updateNewChat':
|
||
|
tmpId = 0
|
||
|
if query['chat']['type']['@type'] == "chatTypePrivate":
|
||
|
tmpId = query['chat']['type']['user_id']
|
||
|
if query['chat']['type']['@type'] == "chatTypeSupergroup":
|
||
|
tmpId = query['chat']['type']['supergroup_id']
|
||
|
#print(query['chat']['title'])
|
||
|
updateChatRooms(query['chat']['id'],query['chat']['type']['@type'], tmpId,query['chat']['title'])
|
||
|
elif query['@type'] == 'updateNewMessage':
|
||
|
#print(query)
|
||
|
tmpContent = "Unsupported"
|
||
|
if query['message']['content']['@type'] == "messageText":
|
||
|
tmpContent = query['message']['content']['text']['text']
|
||
|
#print(tmpContent)
|
||
|
addNewMessage(query['message']['id'], query['message']['chat_id'], query['message']['sender_user_id'], tmpContent)
|
||
|
elif query['@type'] == 'updateChatPosition':
|
||
|
# ==
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateChatLastMessage':
|
||
|
# ==
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateChatReadOutbox':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateUnreadMessageCount':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateUserStatus':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateChatReadInbox':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateMessageContent':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateUserChatAction':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateUnreadChatCount':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateDeleteMessages':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateHavePendingNotifications':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateSupergroupFullInfo':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'message':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateMessageSendSucceeded':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateSupergroup':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'chats':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'updateUserFullInfo':
|
||
|
dummy = True
|
||
|
elif query['@type'] == 'user':
|
||
|
dummy = True
|
||
|
#else:
|
||
|
#print(query)
|
||
|
#else:
|
||
|
#print(query)
|
||
|
if sResponse is not None:
|
||
|
qInputQueue.put(sResponse)
|
||
|
|
||
|
# The Server
|
||
|
class ftServer(Thread):
|
||
|
def __init__(self):
|
||
|
Thread.__init__(self)
|
||
|
self.daemon = True
|
||
|
self.start()
|
||
|
def run(self):
|
||
|
global bMasterSwitch
|
||
|
global qInputQueue
|
||
|
global qOutputQueue
|
||
|
global bConStatus
|
||
|
global objMe
|
||
|
global iApiID
|
||
|
global sApiHash
|
||
|
|
||
|
while bMasterSwitch:
|
||
|
event = td_receive()
|
||
|
if event:
|
||
|
if event['@type'] == 'updateAuthorizationState':
|
||
|
auth_state = event['authorization_state']
|
||
|
#if auth_state['@type'] == 'authorizationStateClosed':
|
||
|
#break
|
||
|
if auth_state['@type'] == 'authorizationStateWaitTdlibParameters':
|
||
|
print("authorizationStateWaitTdlibParameters")
|
||
|
td_send({'@type': 'setTdlibParameters', 'parameters': {
|
||
|
'database_directory': 'tdlib',
|
||
|
'use_message_database': True,
|
||
|
'use_secret_chats': True,
|
||
|
'api_id': iApiID,
|
||
|
'api_hash': sApiHash,
|
||
|
'system_language_code': 'en',
|
||
|
'device_model': 'Desktop',
|
||
|
'system_version': 'Linux',
|
||
|
'application_version': '1.0',
|
||
|
'enable_storage_optimizer': True}})
|
||
|
if auth_state['@type'] == 'authorizationStateWaitEncryptionKey':
|
||
|
print("authorizationStateWaitEncryptionKey")
|
||
|
td_send({'@type': 'checkDatabaseEncryptionKey', 'encryption_key': ''})
|
||
|
if auth_state['@type'] == 'authorizationStateWaitPhoneNumber':
|
||
|
print("authorizationStateWaitPhoneNumber")
|
||
|
qOutputQueue.put("askPhoneNumber")
|
||
|
print("authorizationStateWaitPhoneNumber: Waiting input")
|
||
|
phone_number = qInputQueue.get()
|
||
|
#phone_number = input('Please enter your phone number: ')
|
||
|
td_send({'@type': 'setAuthenticationPhoneNumber', 'phone_number': phone_number})
|
||
|
if auth_state['@type'] == 'authorizationStateWaitCode':
|
||
|
print("authorizationStateWaitCode")
|
||
|
#code = input('Please enter the authentication code you received: ')
|
||
|
qOutputQueue.put("askAuthCode")
|
||
|
code = qInputQueue.get()
|
||
|
td_send({'@type': 'checkAuthenticationCode', 'code': code})
|
||
|
if auth_state['@type'] == 'authorizationStateWaitRegistration':
|
||
|
print("authorizationStateWaitRegistration")
|
||
|
#first_name = input('Please enter your first name: ')
|
||
|
qOutputQueue.put("askFistName")
|
||
|
first_name = qInputQueue.get()
|
||
|
#last_name = input('Please enter your last name: ')
|
||
|
qOutputQueue.put("askLastName")
|
||
|
last_name = qInputQueue.get()
|
||
|
td_send({'@type': 'registerUser', 'first_name': first_name, 'last_name': last_name})
|
||
|
if auth_state['@type'] == 'authorizationStateWaitPassword':
|
||
|
print("authorizationStateWaitPassword")
|
||
|
#password = input('Please enter your password: ')
|
||
|
qOutputQueue.put("askPassword")
|
||
|
password = qInputQueue.get()
|
||
|
td_send({'@type': 'checkAuthenticationPassword', 'password': password})
|
||
|
if auth_state['@type'] == 'authorizationStateReady':
|
||
|
bConStatus = True
|
||
|
else:
|
||
|
if (event['@type'] == 'user' and objMe is None):
|
||
|
objMe = objPersonnal(event['id'], event['first_name'], event['last_name'], event['username'], event['phone_number'])
|
||
|
#if event['@type'] == 'updateConnectionState':
|
||
|
#if event['state']['@type'] == 'connectionStateReady':
|
||
|
#bConStatus = True
|
||
|
qOutputQueue.put(event)
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
# The interactive console
|
||
|
class ftConsole(Cmd):
|
||
|
def do_exit(self, inp):
|
||
|
'''exit the application without loging out.'''
|
||
|
global bMasterSwitch
|
||
|
bMasterSwitch = False
|
||
|
exit()
|
||
|
def do_updateChatsList(self, inp):
|
||
|
'''Actualise Chats List.'''
|
||
|
td_send({'@type': 'getChats', 'limit': 2})
|
||
|
def do_updateMe(self, inp):
|
||
|
'''Update Personnals informartions.'''
|
||
|
td_send({'@type': 'getMe'})
|
||
|
def do_listChatRoom(self, inp):
|
||
|
'''List Available chat room(s)'''
|
||
|
print("Chat Room(s)\nTo join a room, use `join ` + The Room index ([x]).")
|
||
|
global objChatRooms
|
||
|
i = 0
|
||
|
while (i < len(objChatRooms)):
|
||
|
#print("[" + str(i) + "]" + str(objChatRooms[i].id) + " " + objChatRooms[i].sType + "[" + str(objChatRooms[i].iUserId) + "]" + objChatRooms[i].sTitle )
|
||
|
print("[" + str(i) + "] " + objChatRooms[i].sTitle )
|
||
|
i = i + 1;
|
||
|
def do_getKnowUsers(self, inp):
|
||
|
'''List Known users of this instance.'''
|
||
|
global objUsers
|
||
|
|
||
|
i = 0
|
||
|
print(len(objUsers));
|
||
|
while (i < len(objUsers)):
|
||
|
print("[" + str(i) + "]" + objUsers[i].first_name + " " + objUsers[i].last_name + "[" +objUsers[i].username + "]")
|
||
|
i = i + 1;
|
||
|
def do_join(self, inp):
|
||
|
'''Join (set active) a chat room by it's index. use "listChatRoom" to get rooms indexes.'''
|
||
|
global objChatRooms
|
||
|
global iActiveRoom
|
||
|
if objChatRooms[int(inp)] is not None:
|
||
|
print("Joining " + objChatRooms[int(inp)].sTitle)
|
||
|
iActiveRoom = int(inp)
|
||
|
f = open( "./logs/" + str(objChatRooms[int(inp)].id) + ".txt", "r+")
|
||
|
tmpTxt = f.read()
|
||
|
if tmpTxt:
|
||
|
print(tmpTxt)
|
||
|
else:
|
||
|
print("No room with that ID")
|
||
|
|
||
|
def do_send (self, inp):
|
||
|
'''Send a message into the active room.'''
|
||
|
global objChatRooms
|
||
|
global iActiveRoom
|
||
|
if objChatRooms[iActiveRoom] is not None:
|
||
|
td_send({'@type': 'sendMessage','chat_id': objChatRooms[iActiveRoom].id,'input_message_content': {'@type': 'inputMessageText','text': {'@type': 'formattedText','text': inp}}})
|
||
|
|
||
|
def do_logout(self, inp):
|
||
|
'''Logout then quit the app.'''
|
||
|
global bMasterSwitch
|
||
|
print("td_send logout")
|
||
|
td_send({'@type': 'logOut'})
|
||
|
bMasterSwitch = False
|
||
|
exit()
|
||
|
|
||
|
ftClient()
|
||
|
ftServer()
|
||
|
while bMasterSwitch:
|
||
|
if bConStatus:
|
||
|
print("Connected. Launching interactive console...")
|
||
|
td_send({'@type': 'getMe'})
|
||
|
td_send({'@type': 'getChats', 'limit': 2})
|
||
|
ftConsole().cmdloop()
|
||
|
break
|
||
|
time.sleep(1)
|
||
|
td_json_client_destroy(client)
|
||
|
|