dfstorm 3 months ago
parent
commit
923c18e507
2 changed files with 501 additions and 1 deletions
  1. 47
    1
      README.md
  2. 454
    0
      src/main.py

+ 47
- 1
README.md View File

@@ -1,3 +1,49 @@
1 1
 # python-telegram
2 2
 
3
-A small python CLI telegram client
3
+A small python CLI telegram client. Please note that this application was a
4
+"test" and is not intended to be used other than testing purpose.
5
+
6
+The app only retrieve message recived while active and keep them in logs files (./logs/).
7
+
8
+## Installation
9
+
10
+### libtdjson.so (1.6.6)
11
+
12
+The script will look for the telegram library at "./libtdjson.so". You may either
13
+download it from my website (https://ggenois.dev/pub/libtdjson.so (1.6.6, 64bit Fedora Machine))
14
+or build it by folowing those instructions: https://github.com/tdlib/td#building
15
+
16
+### API Keys
17
+
18
+I do not provide API credentials; you must provide your own. 
19
+
20
+To get one, create a Telegram App via https://my.telegram.org/apps and then fill "iApiID" (line 14) with your "App api_id" and "sApiHash" (Line 15) with "App api_hash:".
21
+
22
+
23
+## Usage
24
+
25
+While no user is logged in, the app is in "Authentification mode". Once logged in, you get into an interactive console with those commands:
26
+
27
+### exit
28
+exit the application without loging out.
29
+
30
+### updateChatsList
31
+Actualise Chats List.
32
+
33
+### updateMe
34
+Update Personnals informartions
35
+
36
+### listChatRoom
37
+List Available chat room(s)
38
+
39
+### getKnowUsers
40
+List Known users of this instance.
41
+
42
+### join
43
+Join (set active) a chat room by it's index. Use "listChatRoom" to get rooms indexes.
44
+
45
+### send
46
+Send a message into the active room.
47
+
48
+### logout
49
+Logout then quit the app.

+ 454
- 0
src/main.py View File

@@ -0,0 +1,454 @@
1
+#!/usr/bin/env python3
2
+
3
+from threading import Thread
4
+from cmd import Cmd
5
+from queue import Queue
6
+from ctypes import *
7
+import json
8
+import sys
9
+import os
10
+import time
11
+import array
12
+
13
+# Telegram API ID and API Hash.
14
+iApiID = 0
15
+sApiHash = ''
16
+
17
+# Used to kill all the loops
18
+bMasterSwitch = True
19
+
20
+# Queue Client >>> Server
21
+qInputQueue = Queue()
22
+
23
+# Queue Server >>> Client
24
+qOutputQueue = Queue()
25
+
26
+# State of the authentification
27
+bConStatus = False
28
+
29
+# Personnal Informations Object
30
+objMe = None
31
+
32
+# List of known Users
33
+objUsers = []
34
+
35
+# List of Known ChatRoom
36
+objChatRooms = []
37
+
38
+# Index of the active Chatroom
39
+iActiveRoom = -1
40
+
41
+# td Lib Json 1.6.6
42
+tdjson = CDLL("./libtdjson.so")
43
+
44
+td_json_client_create = tdjson.td_json_client_create
45
+td_json_client_create.restype = c_void_p
46
+td_json_client_create.argtypes = []
47
+
48
+td_json_client_receive = tdjson.td_json_client_receive
49
+td_json_client_receive.restype = c_char_p
50
+td_json_client_receive.argtypes = [c_void_p, c_double]
51
+
52
+td_json_client_send = tdjson.td_json_client_send
53
+td_json_client_send.restype = None
54
+td_json_client_send.argtypes = [c_void_p, c_char_p]
55
+
56
+td_json_client_execute = tdjson.td_json_client_execute
57
+td_json_client_execute.restype = c_char_p
58
+td_json_client_execute.argtypes = [c_void_p, c_char_p]
59
+
60
+td_json_client_destroy = tdjson.td_json_client_destroy
61
+td_json_client_destroy.restype = None
62
+td_json_client_destroy.argtypes = [c_void_p]
63
+
64
+fatal_error_callback_type = CFUNCTYPE(None, c_char_p)
65
+
66
+td_set_log_fatal_error_callback = tdjson.td_set_log_fatal_error_callback
67
+td_set_log_fatal_error_callback.restype = None
68
+td_set_log_fatal_error_callback.argtypes = [fatal_error_callback_type]
69
+
70
+def on_fatal_error_callback(error_message):
71
+    print('TDLib fatal error: ', error_message)
72
+
73
+def td_execute(query):
74
+    query = json.dumps(query).encode('utf-8')
75
+    result = td_json_client_execute(None, query)
76
+    if result:
77
+        result = json.loads(result.decode('utf-8'))
78
+    return result
79
+
80
+c_on_fatal_error_callback = fatal_error_callback_type(on_fatal_error_callback)
81
+td_set_log_fatal_error_callback(c_on_fatal_error_callback)
82
+
83
+# setting TDLib log verbosity level to 1 (errors)
84
+td_execute({'@type': 'setLogVerbosityLevel', 'new_verbosity_level': 1, '@extra': 1.01234})
85
+
86
+client = td_json_client_create()
87
+
88
+# simple wrappers for client usage
89
+def td_send(query):
90
+    query = json.dumps(query).encode('utf-8')
91
+    td_json_client_send(client, query)
92
+
93
+def td_receive():
94
+    result = td_json_client_receive(client, 1.0)
95
+    if result:
96
+        result = json.loads(result.decode('utf-8'))
97
+    return result
98
+
99
+
100
+# another test for TDLib execute method
101
+td_execute({'@type': 'getTextEntities', 'text': '@telegram /test_command https://telegram.org telegram.me', '@extra': ['5', 7.0]})
102
+
103
+
104
+# testing TDLib send method
105
+td_send({'@type': 'getAuthorizationState', '@extra': 1.01234})
106
+
107
+# Personnal informations object
108
+class objPersonnal:
109
+    def __init__(self, ftid, first_name, last_name, username, phone_number):
110
+        self.id = ftid
111
+        self.first_name = first_name
112
+        self.last_name = last_name
113
+        self.username = username
114
+        self.phone_number = phone_number
115
+    def toJSON(self):
116
+        return json.dumps(self, default=lambda o: o.__dict__,
117
+            sort_keys=True, indent=4)
118
+
119
+# Know Users object
120
+class createUser:
121
+    def __init__(self, ftid, first_name, last_name, username, phone_number):
122
+        self.id = ftid
123
+        self.first_name = first_name
124
+        self.last_name = last_name
125
+        self.username = username
126
+        self.phone_number = phone_number
127
+    def toJSON(self):
128
+        return json.dumps(self, default=lambda o: o.__dict__,
129
+            sort_keys=True, indent=4)
130
+
131
+# Known Chat room Object
132
+class createChatRoom:
133
+      def __init__(self, ftid, sType, iUserId, sTitle):
134
+          self.id = ftid
135
+          self.sType = sType
136
+          self.iUserId = iUserId
137
+          self.sTitle = sTitle
138
+          self.rMessage = []
139
+
140
+# Update user if present. Otherwise create it
141
+def updateUsers(ftid, first_name, last_name, username, phone_number):
142
+    i = 0;
143
+    bFound = False
144
+    while i < len(objUsers):
145
+        if (objUsers[i].id == ftid):
146
+            bFound = True
147
+            objUsers[i].first_name = first_name
148
+            objUsers[i].last_name = last_name
149
+            objUsers[i].username = username
150
+            objUsers[i].phone_number = phone_number
151
+        i = i + 1
152
+    if bFound is False:
153
+        tmpUser = createUser(ftid, first_name, last_name, username, phone_number)
154
+        objUsers.append(tmpUser)
155
+
156
+# Update Chatroom if present. Otherwise create it
157
+def updateChatRooms(ftid, sType, iUserId, sTitle):
158
+    i = 0;
159
+    bFound = False
160
+    while i < len(objChatRooms):
161
+        if (objChatRooms[i].id == ftid):
162
+            bFound = True
163
+            objChatRooms[i].sType = sType
164
+            objChatRooms[i].iUserId = iUserId
165
+            objChatRooms[i].sTitle = sTitle
166
+        i = i + 1
167
+    if bFound is False:
168
+        if not os.path.exists('./logs/' + str(ftid) + '.txt'):
169
+            with open('./logs/' + str(ftid) + '.txt', 'w'): pass
170
+        tmpChatRoom = createChatRoom(ftid, sType, iUserId, sTitle)
171
+        objChatRooms.append(tmpChatRoom)
172
+
173
+# Object for recieved Message
174
+class createMessages:
175
+    def __init__(self, message_id, sender_user_id, messageText):
176
+        self.message_id = message_id
177
+        self.sender_user_id = sender_user_id
178
+        self.messageText = messageText
179
+
180
+# Return Index of chat room (objChatRooms) from ID
181
+def getChatIndexById(chat_id):
182
+    i = 0
183
+    while (i < len(objChatRooms)):
184
+        if objChatRooms[i].id == chat_id:
185
+            return i
186
+        i = i + 1
187
+    return -1
188
+
189
+# Add a new recieved message
190
+def addNewMessage(message_id, chat_id, sender_user_id, messageText):
191
+    global iActiveRoom
192
+
193
+    iIndex = getChatIndexById(chat_id)
194
+    if (iIndex >= 0):
195
+        tmpMsg = createMessages(message_id, sender_user_id, messageText)
196
+        objChatRooms[iIndex].rMessage.append(tmpMsg)
197
+        f = open("./logs/" + str(chat_id) +".txt", "a+")
198
+        f.write(formatMessage(tmpMsg) + '\n')
199
+        f.close()
200
+        if iActiveRoom == iIndex:
201
+            print(formatMessage(tmpMsg))
202
+    else:
203
+        print("No chat found for that message")
204
+
205
+# Format a recieved message
206
+def formatMessage(objMessage):
207
+    i = 0
208
+    sUserCaption = "Anomimous"
209
+    while i < len(objUsers):
210
+        if objUsers[i].id == objMessage.sender_user_id:
211
+            sUserCaption = objUsers[i].first_name + " " + objUsers[i].last_name + " [" + objUsers[i].username + "]"
212
+        i = i + 1
213
+    sReturn = sUserCaption + ": " + objMessage.messageText
214
+    return sReturn
215
+
216
+# The client; Handle servers prompts and it's message.
217
+class ftClient(Thread):
218
+    def __init__(self):
219
+        Thread.__init__(self)
220
+        self.daemon = True
221
+        self.start()
222
+    def run(self):
223
+        global bMasterSwitch
224
+        global qInputQueue
225
+        global qOutputQueue
226
+        global objMe
227
+        global objUsers;
228
+
229
+        while bMasterSwitch:
230
+            #print('Waiting instruction...')
231
+            query = qOutputQueue.get()
232
+            sResponse = None
233
+            if query == "askPhoneNumber":
234
+                sResponse = input('askPhoneNumber: ')
235
+            elif query == "askAuthCode":
236
+                sResponse = input('askAuthCode: ')
237
+            elif query == "askFistName":
238
+                sResponse = input('askFistName: ')
239
+            elif query == "askLastName":
240
+                sResponse = input('askLastName: ')
241
+            elif query == "askPassword":
242
+                sResponse = input('askPassword: ')
243
+            elif query['@type'] is not None:
244
+                if query['@type'] == 'updateUser':
245
+                    updateUsers(query['user']['id'], query['user']['first_name'], query['user']['last_name'], query['user']['username'], query['user']['phone_number'])
246
+                elif query['@type'] == 'updateNewChat':
247
+                    tmpId = 0
248
+                    if query['chat']['type']['@type'] == "chatTypePrivate":
249
+                        tmpId = query['chat']['type']['user_id']
250
+                    if query['chat']['type']['@type'] == "chatTypeSupergroup":
251
+                        tmpId = query['chat']['type']['supergroup_id']
252
+                    #print(query['chat']['title'])
253
+                    updateChatRooms(query['chat']['id'],query['chat']['type']['@type'], tmpId,query['chat']['title'])
254
+                elif query['@type'] == 'updateNewMessage':
255
+                    #print(query)
256
+                    tmpContent = "Unsupported"
257
+                    if query['message']['content']['@type'] == "messageText":
258
+                        tmpContent = query['message']['content']['text']['text']
259
+                    #print(tmpContent)
260
+                    addNewMessage(query['message']['id'], query['message']['chat_id'], query['message']['sender_user_id'], tmpContent)
261
+                elif query['@type'] == 'updateChatPosition':
262
+                    # ==
263
+                    dummy = True
264
+                elif query['@type'] == 'updateChatLastMessage':
265
+                    # ==
266
+                    dummy = True
267
+                elif query['@type'] == 'updateChatReadOutbox':
268
+                    dummy = True
269
+                elif query['@type'] == 'updateUnreadMessageCount':
270
+                    dummy = True
271
+                elif query['@type'] == 'updateUserStatus':
272
+                    dummy = True
273
+                elif query['@type'] == 'updateChatReadInbox':
274
+                    dummy = True
275
+                elif query['@type'] == 'updateMessageContent':
276
+                    dummy = True
277
+                elif query['@type'] == 'updateUserChatAction':
278
+                    dummy = True
279
+                elif query['@type'] == 'updateUnreadChatCount':
280
+                    dummy = True
281
+                elif query['@type'] == 'updateDeleteMessages':
282
+                    dummy = True
283
+                elif query['@type'] == 'updateHavePendingNotifications':
284
+                    dummy = True
285
+                elif query['@type'] == 'updateSupergroupFullInfo':
286
+                    dummy = True
287
+                elif query['@type'] == 'message':
288
+                    dummy = True
289
+                elif query['@type'] == 'updateMessageSendSucceeded':
290
+                    dummy = True
291
+                elif query['@type'] == 'updateSupergroup':
292
+                    dummy = True
293
+                elif query['@type'] == 'chats':
294
+                    dummy = True
295
+                elif query['@type'] == 'updateUserFullInfo':
296
+                    dummy = True
297
+                elif query['@type'] == 'user':
298
+                    dummy = True
299
+                #else:
300
+                    #print(query)
301
+            #else:
302
+                #print(query)
303
+            if sResponse is not None:
304
+                qInputQueue.put(sResponse)
305
+
306
+# The Server
307
+class ftServer(Thread):
308
+    def __init__(self):
309
+        Thread.__init__(self)
310
+        self.daemon = True
311
+        self.start()
312
+    def run(self):
313
+        global bMasterSwitch
314
+        global qInputQueue
315
+        global qOutputQueue
316
+        global bConStatus
317
+        global objMe
318
+        global iApiID
319
+        global sApiHash
320
+    
321
+        while bMasterSwitch:
322
+            event = td_receive()
323
+            if event:
324
+                if event['@type'] == 'updateAuthorizationState':
325
+                    auth_state = event['authorization_state']
326
+                    #if auth_state['@type'] == 'authorizationStateClosed':
327
+                        #break
328
+                    if auth_state['@type'] == 'authorizationStateWaitTdlibParameters':
329
+                        print("authorizationStateWaitTdlibParameters")
330
+                        td_send({'@type': 'setTdlibParameters', 'parameters': {
331
+                                                               'database_directory': 'tdlib',
332
+                                                               'use_message_database': True,
333
+                                                               'use_secret_chats': True,
334
+                                                               'api_id': iApiID,
335
+                                                               'api_hash': sApiHash,
336
+                                                               'system_language_code': 'en',
337
+                                                               'device_model': 'Desktop',
338
+                                                               'system_version': 'Linux',
339
+                                                               'application_version': '1.0',
340
+                                                               'enable_storage_optimizer': True}})
341
+                    if auth_state['@type'] == 'authorizationStateWaitEncryptionKey':
342
+                        print("authorizationStateWaitEncryptionKey")
343
+                        td_send({'@type': 'checkDatabaseEncryptionKey', 'encryption_key': ''})
344
+                    if auth_state['@type'] == 'authorizationStateWaitPhoneNumber':
345
+                        print("authorizationStateWaitPhoneNumber")
346
+                        qOutputQueue.put("askPhoneNumber")
347
+                        print("authorizationStateWaitPhoneNumber: Waiting input")
348
+                        phone_number = qInputQueue.get()
349
+                        #phone_number = input('Please enter your phone number: ')
350
+                        td_send({'@type': 'setAuthenticationPhoneNumber', 'phone_number': phone_number})
351
+                    if auth_state['@type'] == 'authorizationStateWaitCode':
352
+                        print("authorizationStateWaitCode")
353
+                        #code = input('Please enter the authentication code you received: ')
354
+                        qOutputQueue.put("askAuthCode")
355
+                        code = qInputQueue.get()
356
+                        td_send({'@type': 'checkAuthenticationCode', 'code': code})
357
+                    if auth_state['@type'] == 'authorizationStateWaitRegistration':
358
+                        print("authorizationStateWaitRegistration")
359
+                        #first_name = input('Please enter your first name: ')
360
+                        qOutputQueue.put("askFistName")
361
+                        first_name = qInputQueue.get()
362
+                        #last_name = input('Please enter your last name: ')
363
+                        qOutputQueue.put("askLastName")
364
+                        last_name = qInputQueue.get()
365
+                        td_send({'@type': 'registerUser', 'first_name': first_name, 'last_name': last_name})
366
+                    if auth_state['@type'] == 'authorizationStateWaitPassword':
367
+                        print("authorizationStateWaitPassword")
368
+                        #password = input('Please enter your password: ')
369
+                        qOutputQueue.put("askPassword")
370
+                        password = qInputQueue.get()
371
+                        td_send({'@type': 'checkAuthenticationPassword', 'password': password})
372
+                    if auth_state['@type'] == 'authorizationStateReady':
373
+                        bConStatus = True
374
+                else:
375
+                    if (event['@type'] == 'user' and objMe is None):
376
+                        objMe = objPersonnal(event['id'], event['first_name'], event['last_name'], event['username'], event['phone_number'])
377
+                    #if event['@type'] == 'updateConnectionState':
378
+                        #if event['state']['@type'] == 'connectionStateReady':
379
+                            #bConStatus = True
380
+                    qOutputQueue.put(event)
381
+                sys.stdout.flush()
382
+
383
+# The interactive console
384
+class ftConsole(Cmd):
385
+    def do_exit(self, inp):
386
+        '''exit the application without loging out.'''
387
+        global bMasterSwitch
388
+        bMasterSwitch = False
389
+        exit()
390
+    def do_updateChatsList(self, inp):
391
+        '''Actualise Chats List.'''
392
+        td_send({'@type': 'getChats', 'limit': 2})
393
+    def do_updateMe(self, inp):
394
+        '''Update Personnals informartions.'''
395
+        td_send({'@type': 'getMe'})
396
+    def do_listChatRoom(self, inp):
397
+        '''List Available chat room(s)'''
398
+        print("Chat Room(s)\nTo join a room, use `join ` + The Room index ([x]).")
399
+        global objChatRooms
400
+        i = 0
401
+        while (i < len(objChatRooms)):
402
+            #print("[" + str(i) + "]" + str(objChatRooms[i].id) + " " + objChatRooms[i].sType + "[" + str(objChatRooms[i].iUserId)  + "]" + objChatRooms[i].sTitle )
403
+            print("[" + str(i) + "] " + objChatRooms[i].sTitle )
404
+            i = i + 1;
405
+    def do_getKnowUsers(self, inp):
406
+        '''List Known users of this instance.'''
407
+        global objUsers
408
+
409
+        i = 0
410
+        print(len(objUsers));
411
+        while (i < len(objUsers)):
412
+            print("[" + str(i) + "]" + objUsers[i].first_name + " " + objUsers[i].last_name + "[" +objUsers[i].username  + "]")
413
+            i = i + 1;
414
+    def do_join(self, inp):
415
+        '''Join (set active) a chat room by it's index. use "listChatRoom" to get rooms indexes.'''
416
+        global objChatRooms
417
+        global iActiveRoom
418
+        if objChatRooms[int(inp)] is not None:
419
+            print("Joining " + objChatRooms[int(inp)].sTitle)
420
+            iActiveRoom = int(inp)
421
+            f = open( "./logs/" + str(objChatRooms[int(inp)].id) + ".txt", "r+")
422
+            tmpTxt = f.read()
423
+            if tmpTxt:
424
+                print(tmpTxt)
425
+        else:
426
+            print("No room with that ID")
427
+
428
+    def do_send (self, inp):
429
+        '''Send a message into the active room.'''
430
+        global objChatRooms
431
+        global iActiveRoom
432
+        if objChatRooms[iActiveRoom] is not None:
433
+            td_send({'@type': 'sendMessage','chat_id': objChatRooms[iActiveRoom].id,'input_message_content': {'@type': 'inputMessageText','text': {'@type': 'formattedText','text': inp}}})
434
+
435
+    def do_logout(self, inp):
436
+        '''Logout then quit the app.'''
437
+        global bMasterSwitch
438
+        print("td_send logout")
439
+        td_send({'@type': 'logOut'})
440
+        bMasterSwitch = False
441
+        exit()
442
+
443
+ftClient()
444
+ftServer()
445
+while bMasterSwitch:
446
+    if bConStatus:
447
+        print("Connected. Launching interactive console...")
448
+        td_send({'@type': 'getMe'})
449
+        td_send({'@type': 'getChats', 'limit': 2})
450
+        ftConsole().cmdloop()
451
+        break
452
+    time.sleep(1)
453
+td_json_client_destroy(client)
454
+

Loading…
Cancel
Save