danog / MadelineProto

Async PHP client API for the telegram MTProto protocol
https://docs.madelineproto.xyz
GNU Affero General Public License v3.0
2.81k stars 643 forks source link

How send messages.sendMultiMedia #1548

Open SkayperTeam opened 1 week ago

SkayperTeam commented 1 week ago

php: 8.2 laravel/framework": 11.22.0 danog/madelineproto: 8.3.0

Hello! I'm trying to execute the following code:

<?php

namespace App\Console\Commands;

use App\MailingStatusEnum;
use App\Models\Mailing;
use App\Models\TelegramGroup;
use App\Models\TelegramUser;
use App\Models\Topic;
use danog\MadelineProto\API;
use danog\MadelineProto\LocalFile;
use danog\MadelineProto\ParseMode;
use danog\MadelineProto\RemoteUrl;
use Illuminate\Console\Command;
use Throwable;

class SendMailingsCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:send-mailings-command';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Проверяет все рассылки в статусе new и отправляет';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        $madelineProto = new API('session.madeline');
        Mailing::query()
            ->where('status', MailingStatusEnum::NEW)
            ->each(function (Mailing $mailing) use ($madelineProto) {
                try {
                    $mailing->update(['status' => MailingStatusEnum::PROCESSING]);

                    foreach ($mailing->telegram_group_ids as $telegramGroupId) {
                        $group = TelegramGroup::query()->find($telegramGroupId);

                        $text = str_replace('&nbsp;', ' ', $mailing->text) . '<br /><br />';

                        $group
                            ->usersWithRoles()
                            ->whereIn('role_id', $mailing->role_ids)
                            ->each(function (TelegramUser $telegramUser) use (&$text) {
                                $text .= $telegramUser->telegram_tag ? ' @'.$telegramUser->telegram_tag : " <a href='tg://user?id={$telegramUser->id}'>$telegramUser->name</a>";
                            });
                        $media = [];

                        $mediaFiles = $mailing->getMedia();

                        foreach ($mediaFiles as $index => $mediaFile) {
                            $filePath = $mediaFile->getPath();

                            $media[] = [
                                'media' => [
                                    '_' => 'inputMediaUploadedDocument',
                                    'file' => new LocalFile($filePath),
                                    'mime_type' => mime_content_type($filePath),
                                    'attributes' => [
                                        ['_' => 'documentAttributeFilename', 'file_name' => basename($filePath)]
                                    ]
                                ],
//                                'message' => $index === 0 ? $text : '',
//                                'parse_mode' => ParseMode::HTML,
                            ];
                        }

                        $mailing->update([
                            'media_files' => $media
                        ]);

                        $group
                            ->topics()
                            ->whereIn('name', $mailing->topic_ids)
                            ->each(fn(Topic $topic) => count($media) === 0
                                ? $madelineProto
                                    ->sendMessage(
                                        peer: $group->telegram_id,
                                        message: $text,
                                        parseMode: ParseMode::HTML,
                                        replyToMsgId: $topic->topic_id,
                                        topMsgId: $topic->topic_id,
                                    )
                                : $madelineProto
                                    ->messages
                                    ->sendMultiMedia(
                                        peer: $group->telegram_id,
                                        reply_to_msg_id: $topic->topic_id,
                                        top_msg_id: $topic->topic_id,
                                        multi_media: $media,
                                    )
                            );

                    }

                    $mailing->update(['status' => MailingStatusEnum::SENDED]);
                } catch (Throwable $e) {
                    logger("error", [$e]);
                    $mailing->update([
                        'status' => MailingStatusEnum::FAILED,
                        'error' => [$e],
                    ]);
                }

            });
    }
}

When I run it I get the error:

[2024-09-08 23:38:07] local.DEBUG: error ["[object] (danog\\MadelineProto\\TL\\Exception(code: 0): Predicate (value under _) was not set! at /home/vlad/Desktop/projects/telegram-bot-chicko/vendor/danog/madelineproto/src/TL/TL.php:601)
[stacktrace]
#0 /home/vlad/Desktop/projects/telegram-bot-chicko/vendor/danog/madelineproto/src/TL/TL.php(567): danog\\MadelineProto\\TL\\TL->serializeObject()
#1 /home/vlad/Desktop/projects/telegram-bot-chicko/vendor/danog/madelineproto/src/TL/TL.php(774): danog\\MadelineProto\\TL\\TL->serializeObject()
#2 /home/vlad/Desktop/projects/telegram-bot-chicko/vendor/danog/madelineproto/src/TL/TL.php(638): danog\\MadelineProto\\TL\\TL->serializeParams()
#3 /home/vlad/Desktop/projects/telegram-bot-chicko/vendor/danog/madelineproto/src/Connection.php(562): danog\\MadelineProto\\TL\\TL->serializeMethod()

I can’t figure out what attribute I need to pass and how to proceed? In my case, in theory, json, mp4, png, jpeg and others can be transmitted. Please tell me what to do?

*If necessary, I can send you the full error trace.

SkayperTeam commented 1 week ago

I will also add the code of my new attempt. I have taken out the file processing and now I am trying to understand more clearly what file I am dealing with when forming "multi_media"

Output logger('mediaResultArray', [$media]):

[
  {
    "media": {
      "_": "inputMediaUploadedPhoto",
      "file": {
        "danog\\MadelineProto\\LocalFile": {
          "file": "/home/vlad/Desktop/projects/telegram-bot-chicko/storage/app/public/2/01J79Z355SW6DQMCMATD0VFZTY.png"
        }
      }
    },
    "message": "<p>test</p><br /><br /> @eteeeeernal",
    "parse_mode": {
      "danog\\MadelineProto\\ParseMode": "HTML"
    }
  }
]
<?php

namespace App\Console\Commands;

use App\MailingStatusEnum;
use App\Models\Mailing;
use App\Models\TelegramGroup;
use App\Models\TelegramUser;
use App\Models\Topic;
use danog\MadelineProto\API;
use danog\MadelineProto\LocalFile;
use danog\MadelineProto\ParseMode;
use danog\MadelineProto\RemoteUrl;
use Illuminate\Console\Command;
use Throwable;

class SendMailingsCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:send-mailings-command';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Проверяет все рассылки в статусе new и отправляет';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        $madelineProto = new API('session.madeline');
        Mailing::query()
            ->where('status', MailingStatusEnum::NEW)
            ->each(function (Mailing $mailing) use ($madelineProto) {
                try {
                    $mailing->update(['status' => MailingStatusEnum::PROCESSING]);

                    foreach ($mailing->telegram_group_ids as $telegramGroupId) {
                        $group = TelegramGroup::query()->find($telegramGroupId);

                        $text = str_replace('&nbsp;', ' ', $mailing->text) . '<br /><br />';

                        $group
                            ->usersWithRoles()
                            ->whereIn('role_id', $mailing->role_ids)
                            ->each(function (TelegramUser $telegramUser) use (&$text) {
                                $text .= $telegramUser->telegram_tag ? ' @'.$telegramUser->telegram_tag : " <a href='tg://user?id={$telegramUser->id}'>$telegramUser->name</a>";
                            });
                        $media = [];

                        $mediaFiles = $mailing->getMedia();

                        foreach ($mediaFiles as $index => $mediaFile) {
//                            $filePath = $mediaFile->getPath();

                            if ($file = $this->handleFile($mediaFile, $index, $text)) {
                                logger('file', [$file]);
                                $media[] = $file;
                            }
                        }

                        logger('mediaResultArray', [$media]);

                        $mailing->update([
                            'media_files' => $media
                        ]);

                        $group
                            ->topics()
                            ->whereIn('name', $mailing->topic_ids)
                            ->each(fn(Topic $topic) => count($media) === 0
                                ? $madelineProto
                                    ->sendMessage(
                                        peer: $group->telegram_id,
                                        message: $text,
                                        parseMode: ParseMode::HTML,
                                        replyToMsgId: $topic->topic_id,
                                        topMsgId: $topic->topic_id,
                                    )
                                : $madelineProto
                                    ->messages
                                    ->sendMultiMedia(
                                        peer: $group->telegram_id,
                                        reply_to_msg_id: $topic->topic_id,
                                        top_msg_id: $topic->topic_id,
                                        multi_media: $media,
                                    )
                            );

                    }

                    $mailing->update(['status' => MailingStatusEnum::SENDED]);
                } catch (Throwable $e) {
                    logger("error", [$e]);
                    $mailing->update([
                        'status' => MailingStatusEnum::FAILED,
                        'error' => [$e],
                    ]);
                }

            });
    }

    protected function handleFile($mediaFile, int $index, string $text): array|null
    {
        $filePath = $mediaFile->getPath();
        $mimeType = mime_content_type($filePath);

        // Логика для обработки различных типов медиафайлов
        if (strpos($mimeType, 'image') !== false) {
            // images
            return [
                'media' => [
                    '_' => 'inputMediaUploadedPhoto',
                    'file' => new LocalFile($filePath),
                ],
                'message' => $index === 0 ? $text : '',
                'parse_mode' => ParseMode::HTML,
            ];
        } elseif (strpos($mimeType, 'video') !== false) {
            // videos
            return [
                'media' => [
                    '_' => 'inputMediaUploadedDocument',
                    'file' => new LocalFile($filePath),
                    'mime_type' => $mimeType,
                    'attributes' => [
                        ['_' => 'documentAttributeVideo', 'supports_streaming' => true]
                    ]
                ],
                'message' => $index === 0 ? $text : '',
                'parse_mode' => ParseMode::HTML,
            ];
        } elseif (strpos($mimeType, 'audio') !== false) {
            // audios
            return [
                'media' => [
                    '_' => 'inputMediaUploadedDocument',
                    'file' => new LocalFile($filePath),
                    'mime_type' => $mimeType,
                    'attributes' => [
                        ['_' => 'documentAttributeAudio', 'voice' => false]
                    ]
                ],
                'message' => $index === 0 ? $text : '',
                'parse_mode' => ParseMode::HTML,
            ];
        } elseif (strpos($mimeType, 'application') !== false) {
            //(PDF, JSON, ZIP, ...)
            return [
                'media' => [
                    '_' => 'inputMediaUploadedDocument',
                    'file' => new LocalFile($filePath),
                    'mime_type' => $mimeType,
                    'attributes' => [
                        ['_' => 'documentAttributeFilename', 'file_name' => basename($filePath)]
                    ]
                ],
                'message' => $index === 0 ? $text : '',
                'parse_mode' => ParseMode::HTML,
            ];
        } elseif (strpos($mimeType, 'text') !== false) {
            // (HTML, plain text, ...)
            return [
                'media' => [
                    '_' => 'inputMediaUploadedDocument',
                    'file' => new LocalFile($filePath),
                    'mime_type' => $mimeType,
                    'attributes' => [
                        ['_' => 'documentAttributeFilename', 'file_name' => basename($filePath)]
                    ]
                ],
                'message' => $index === 0 ? $text : '',
                'parse_mode' => ParseMode::HTML,
            ];
        }
        return null;
    }
}
SkayperTeam commented 1 week ago

i solved my problem. Solve:

return [
            **'_' => 'inputSingleMedia',**
            'media' => [
                '_' => 'inputMediaUploadedDocument',
                'file' => new LocalFile($filePath),
                'mime_type' => $mimeType,
                'attributes' => [
                    ['_' => 'documentAttributeFilename', 'file_name' => basename($filePath)]
                ]
            ],
            'message' => $index === 0 ? $text : '',
            'parse_mode' => ParseMode::HTML,
        ];

Added '_' => 'inputSingleMedia', in my media array

SkayperTeam commented 1 week ago

I would like to add to my answer. After solving the problem, I still had difficulty loading video+any other file. My salvation was 'force_file' => true. In this case, all media are compressed, but no longer conflict when loading, because otherwise tg does not allow loading a video with another file in one message. New working structure:

return [
    '_' => 'inputSingleMedia',
    'media' => [
        '_' => 'inputMediaUploadedDocument',
        'force_file' => true,
        'file' => new LocalFile($filePath),
        'mime_type' => $mimeType,
        'attributes' => [
            ['_' => 'documentAttributeFilename', 'file_name' => basename($filePath)]
        ]
    ],
    'message' => $index === $count - 1 ? $text : '',
    'parse_mode' => ParseMode::HTML,
];

I will also be happy to share my fully working script. *To work with media inside the project, I use spatie/laravel-medialibrary package working code:

<?php

namespace App\Console\Commands;

use App\MailingStatusEnum;
use App\Models\Mailing;
use App\Models\TelegramGroup;
use App\Models\TelegramUser;
use App\Models\Topic;
use danog\MadelineProto\API;
use danog\MadelineProto\LocalFile;
use danog\MadelineProto\ParseMode;
use Illuminate\Console\Command;
use Throwable;

class SendMailingsCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:send-mailings-command';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Проверяет все рассылки в статусе new и отправляет';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        if (Mailing::query()->where('status', MailingStatusEnum::PROCESSING)->count() > 0) {
            return; // Если что-то уже участвует в рассылке, то мы пропускаем и ждём пока освободится пул отправок
        }

        $madelineProto = new API('session.madeline');

        $mailing = Mailing::query()
            ->where('status', MailingStatusEnum::NEW)
            ->first();

        if (!$mailing) {
            return; // Если новых рассылок нет, то просто завершаем скрипт
        }

        try {
            $mailing->update(['status' => MailingStatusEnum::PROCESSING]);

            foreach ($mailing->telegram_group_ids as $telegramGroupId) {
                $group = TelegramGroup::query()->find($telegramGroupId);

                $text = str_replace('&nbsp;', ' ', $mailing->text) . '<br /><br />';

                $group
                    ->usersWithRoles()
                    ->whereIn('role_id', $mailing->role_ids)
                    ->each(function (TelegramUser $telegramUser) use (&$text) {
                        $text .= $telegramUser->telegram_tag ? ' @'.$telegramUser->telegram_tag : " <a href='tg://user?id={$telegramUser->id}'>$telegramUser->name</a>";
                    });
                $media = [];

                $mediaFiles = $mailing->getMedia();

                foreach ($mediaFiles as $index => $mediaFile) {
                    if ($file = $this->handleFile($mediaFile, $index, $text, $mediaFiles->count())) {
                        logger('file', [$file]);
                        $media[] = $file;
                    }
                }

                logger('mediaResultArray', [$media]);

                $mailing->update([
                    'media_files' => $media
                ]);

                $group
                    ->topics()
                    ->whereIn('name', $mailing->topic_ids)
                    ->each(fn(Topic $topic) => count($media) === 0
                        ? $madelineProto
                            ->sendMessage(
                                peer: $group->telegram_id,
                                message: $text,
                                parseMode: ParseMode::HTML,
                                replyToMsgId: $topic->topic_id,
                                topMsgId: $topic->topic_id,
                            )
                        : $madelineProto
                            ->messages
                            ->sendMultiMedia(
                                peer: $group->telegram_id,
                                reply_to_msg_id: $topic->topic_id,
                                top_msg_id: $topic->topic_id,
                                multi_media: $media,
                            )
                    );

            }

            $mailing->update(['status' => MailingStatusEnum::SENDED]);
        } catch (Throwable $e) {
            logger("error", [$e]);
            $mailing->update([
                'status' => MailingStatusEnum::FAILED,
                'error' => [$e],
            ]);
        }
    }

    protected function handleFile($mediaFile, int $index, string $text, $count): array|null
    {
        $filePath = $mediaFile->getPath();
        $mimeType = mime_content_type($filePath);

        return [
            '_' => 'inputSingleMedia',
            'media' => [
                '_' => 'inputMediaUploadedDocument',
                'force_file' => true,
                'file' => new LocalFile($filePath),
                'mime_type' => $mimeType,
                'attributes' => [
                    ['_' => 'documentAttributeFilename', 'file_name' => basename($filePath)]
                ]
            ],
            'message' => $index === $count - 1 ? $text : '',
            'parse_mode' => ParseMode::HTML,
        ];
    }
}