Closed VityaSchel closed 2 years ago
ok so here is the steps without actual implementation yet:
I don't know if it works, but it should be working with @mtproto-core, I will write implementation and post it here. It should also work with big files (> 10 mb) the same way
So this is my test implementation but it doesn't work :(
// 'image' is path to file
// global.mtprotoapi is instance of MTProto
async function sendMedia(image) {
const fileID = Math.ceil(Math.random() * 0xffffff) + Math.ceil(Math.random() * 0xffffff)
const imageBuffer = await fs.readFile(image)
const imageSize = Buffer.byteLength(imageBuffer)
const chunks = Math.ceil(imageSize / 1024)
for(let i = 0; i < chunks; i++) {
const partSize = i === chunks-1 ? imageSize % 1024 : 1024
const part = Buffer.alloc(partSize)
imageBuffer.copy(part, 0, i*1024, i*1024 + partSize)
console.log(i, await global.mtprotoapi.call('upload.saveFilePart', {
file_id: fileID,
file_part: i,
bytes: part.toString()
}))
}
global.mtprotoapi.call('messages.uploadMedia', {
peer: botFatherPeer,
media: {
_: 'inputMediaUploadedPhoto',
file: {
_: 'inputFile',
id: fileID,
parts: chunks,
name: 'testfile.png'
}
}
}).then(console.log).catch(console.error)
}
sendMediaToBotFather()
messages.uploadMedia results in
{
_: 'mt_rpc_error',
error_code: 400,
error_message: 'IMAGE_PROCESS_FAILED'
}
I tried to set md5_checksum
field but it adds another error and it seems to be optional
{
_: 'mt_rpc_error',
error_code: 400,
error_message: 'MD5_CHECKSUM_INVALID'
}
Can anyone give me a hint why image processing is failing?
So far I found that you have to start from 0 chunk index and there are no issues with reading my file and slicing it to buffers. Perhaps, I should cast buffer to string other way than by simply calling .toString()
on each?
I also tried binary method but it doesn't work neither
import _ from 'lodash'
async function sendMediaToBotFather(image) {
await getAPI()
const fileID = Math.ceil(Math.random() * 0xffffff) + Math.ceil(Math.random() * 0xffffff)
const imageBuffer = await fs.readFile(image, 'binary')
const chunks = _.chunk(imageBuffer.split(''), 1024)
for(let i = 0; i < chunks.length; i++) {
const chunk = new Array(1024).fill().map((_, i) => chunks[i] ?? '') // optional
console.log(i, await global.mtprotoapi.call('upload.saveFilePart', {
file_id: fileID,
file_part: i,
bytes: chunk.join('') // can be replaced with chunks[i].join('')
}))
}
global.mtprotoapi.call('messages.uploadMedia', {
peer: botFatherPeer,
media: {
_: 'inputMediaUploadedPhoto',
file: {
_: 'inputFile',
id: fileID,
parts: chunks.length,
name: 'testfile.png'
}
}
}).then(console.log).catch(console.error)
}
sendMediaToBotFather()
{
_: 'mt_rpc_error',
error_code: 400,
error_message: 'IMAGE_PROCESS_FAILED'
}
I guess I'll have to make file upload through bot api because it's much easier, and then forward image to my telegram account which uses mtproto to get access_hash. If @alik0211 or other contributor decides to write a guide, please reopen this issue or leave a link here :)
I found implementation in javascript from another library (tg client): https://github.com/gram-js/gramjs/blob/9e048dcdbff44c8c851a17a2269e7da144917529/gramjs/client/uploads.ts#L100
The only difference is that this code uses Buffer.slice instead of allocating new buffer and copying to it. I decided not to use slice because it's deprecated and is not recommended to use by NodeJS docs, but I'll give it a try anyway and also test my code with other images today.
I just found that when you stringify buffer it becomes larger, so I tried sending parts without toString()
part and it worked!!!!!
Feel free to copy the following script and use it, or you can modify it based on existing Telegram client:
import fs from 'fs/promises'
global.mtprotoapi = new MTProto()
const partsSizes = [1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288]
async function sendMedia(imageBuffer) {
let fileID = ''
for(let i = 0; i < 19; ++i) fileID += Math.floor(Math.random() * 10)
const imageSize = Buffer.byteLength(imageBuffer)
const partMaxSize = imageSize >= _.last(partsSizes) ? _.last(partsSizes) : partsSizes.find(size => imageSize <= size)
const chunks = Math.ceil(imageSize / partMaxSize)
for(let i = 0; i < chunks; i++) {
const partSize = i === chunks-1 ? imageSize % partMaxSize : partMaxSize
const part = imageBuffer.slice(i*partMaxSize, i*partMaxSize + partSize)
await global.mtprotoapi.call('upload.saveFilePart', {
file_id: fileID,
file_part: i,
bytes: part
})
}
await global.mtprotoapi.call('messages.sendMedia', {
media: {
_: 'inputMediaUploadedPhoto',
file: {
_: 'inputFile',
id: fileID,
parts: chunks,
name: fileID,
md5Checksum: ''
}
},
peer: {
_: 'inputPeerUser',
user_id: 123,
access_hash: '1231236671278312'
},
message: '',
random_id: Math.ceil(Math.random() * 0xffffff) + Math.ceil(Math.random() * 0xffffff),
})
}
sendMedia(Buffer.from(await fs.readFile('/Users/You/Desktop/testfile.png')))
⚠️ One last thing to mention (read, it's important!): before uploading you should decide which part size you will be using. Telegram currently allows the following parts sizes:
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
You should pick as big part as possible up until 524288 bytes because this will greatly decrease uploading time. Compare actual file size to these numbers and pick closest (to higher side). This is the results of changing 1024 part size to 65536 part size while uploading picture that has filesize of 33757 bytes:
1024 part size
Uploading time total: 4.639s
65536 part size
Uploading time total: 373.383ms
I already refactored code above so you can continue to use it.
it seems alik is dead so I'll have to write the guide for him, while I'm writing it here is articles that may help: https://core.telegram.org/api/files#uploading-files https://core.telegram.org/type/bytes