Added ability to upload and send media, and more fixes

Uploading and sending media are different things.
Once you have uploaded a media file, you can send
it to many users without uploading it again, since
you have a handle to the uploaded file.

Other fixes include not showing additional data
on error messages and not generating correct
code for sending bytes
This commit is contained in:
Lonami 2016-09-11 13:10:27 +02:00
parent cdb1674a27
commit 7e78b1b6dc
5 changed files with 96 additions and 4 deletions

View File

@ -163,10 +163,11 @@ class RPCError(Exception):
# Get additional_data, if any
if match.groups():
self.additional_data = int(match.group(1))
super().__init__(self, error_msg.format(self.additional_data))
else:
self.additional_data = None
super().__init__(self, error_msg)
super().__init__(self, error_msg)
called_super = True
break

12
main.py
View File

@ -61,6 +61,7 @@ if __name__ == '__main__':
print('You are now sending messages to "{}". Available commands:'.format(display))
print(' !q: Quits the current chat.')
print(' !h: prints the latest messages (message History) of the chat.')
print(' !p <path>: sends a Photo located at the given path')
# And start a while loop to chat
while True:
@ -80,6 +81,17 @@ if __name__ == '__main__':
date = datetime.fromtimestamp(msg.date)
print('[{}:{}] {}: {}'.format(date.hour, date.minute, name, msg.message))
# Send photo
elif msg.startswith('!p '):
file_path = msg[len('!p '):] # Slice the message to get the path
print('Uploading {}...'.format(file_path))
input_file = client.upload_file(file_path)
# After we have the handle to the uploaded file, send it to our peer
client.send_photo_file(input_file, input_peer)
print('Media sent!')
# Send chat message (if any)
elif msg:
client.send_message(input_peer, msg, markdown=True, no_web_page=True)

View File

@ -1,6 +1,9 @@
# This file structure is based on TLSharp
# https://github.com/sochix/TLSharp/blob/master/TLSharp.Core/TelegramClient.cs
import platform
from datetime import datetime
from hashlib import md5
from os import path
import utils
import network.authenticator
@ -12,11 +15,18 @@ from parser.markdown_parser import parse_message_entities
# For sending and receiving requests
from tl import MTProtoRequest
from tl import Session
from tl.types import PeerUser, PeerChat, PeerChannel, InputPeerUser, InputPeerChat, InputPeerChannel, InputPeerEmpty
# The Requests and types that we'll be using
from tl.types import \
PeerUser, PeerChat, PeerChannel, \
InputPeerUser, InputPeerChat, InputPeerChannel, InputPeerEmpty, \
InputFile, InputMediaUploadedPhoto
from tl.functions import InvokeWithLayerRequest, InitConnectionRequest
from tl.functions.help import GetConfigRequest
from tl.functions.auth import SendCodeRequest, SignInRequest
from tl.functions.messages import GetDialogsRequest, GetHistoryRequest, SendMessageRequest
from tl.functions.upload import SaveFilePartRequest
from tl.functions.messages import GetDialogsRequest, GetHistoryRequest, SendMessageRequest, SendMediaRequest
class TelegramClient:
@ -198,6 +208,66 @@ class TelegramClient:
for msg in result.messages # ...from all the messages...
for usr in result.users]) # ...from all of the available users
# TODO Handle media downloading/uploading in a different session:
# "It is recommended that large queries (upload.getFile, upload.saveFilePart)
# be handled through a separate session and a separate connection"
def upload_file(self, file_path, part_size_kb=64, file_name=None):
"""Uploads the specified media with the given chunk (part) size, in KB.
If no custom file name is specified, the real file name will be used.
This method will fail when trying to upload files larger than 10MB!"""
part_size = int(part_size_kb * 1024)
if part_size % 1024 != 0:
raise ValueError('The part size must be evenly divisible by 1024')
# Multiply the datetime timestamp by 10^6 to get the ticks
# This is high likely going to be unique
file_id = int(datetime.now().timestamp() * (10 ** 6))
part_index = 0
hash_md5 = md5()
with open(file_path, 'rb') as file:
while True:
# Read the file by in chunks of size part_size
part = file.read(part_size)
print('Sending {}'.format(len(part)))
# If we have read no data (0 bytes), the file is over
# So there is nothing left to upload
if not part:
break
# Invoke the file upload and increment both the part index and MD5 checksum
result = self.invoke(SaveFilePartRequest(file_id, part_index, part))
if result:
part_index += 1
hash_md5.update(part)
else:
raise ValueError('Could not upload file part #{}'.format(part_index))
# Set a default file name if None was specified
if not file_name:
file_name = path.basename(file_path)
# After the file has been uploaded, we can return a handle pointing to it
return InputFile(id=file_id,
parts = part_index,
name=file_name,
md5_checksum=hash_md5.hexdigest())
def send_photo_file(self, input_file, input_peer, caption=''):
"""Sends a previously uploaded input_file
(which should be a photo) to an input_peer"""
self.send_media_file(
InputMediaUploadedPhoto(input_file, caption), input_peer)
def send_media_file(self, input_media, input_peer):
"""Sends any input_media (contact, document, photo...) to an input_peer"""
self.invoke(SendMediaRequest(peer=input_peer,
media=input_media,
random_id=utils.generate_random_long()))
def invoke(self, request):
"""Invokes an MTProtoRequest and returns its results"""
if not issubclass(type(request), MTProtoRequest):

View File

@ -282,7 +282,7 @@ def write_onsend_code(builder, arg, args, name=None):
pass # These are actually NOT written! Only used for flags
elif 'bytes' == arg.type:
builder.writeln('writer.write({})'.format(name))
builder.writeln('writer.tgwrite_bytes({})'.format(name))
elif 'date' == arg.type: # Custom format
builder.writeln('writer.tgwrite_date({})'.format(name))

View File

@ -106,6 +106,15 @@ class BinaryReader:
constructor_id = self.read_int(signed=False)
clazz = tlobjects.get(constructor_id, None)
if clazz is None:
# The class was None, but there's still a
# chance of it being a manually parsed value like bool!
value = constructor_id
if value == 0x997275b5: # boolTrue
return True
elif value == 0xbc799737: # boolFalse
return False
# If there was still no luck, give up
raise TypeNotFoundError(constructor_id)
# Now we need to determine the number of parameters of the class, so we can