1
0
mirror of https://github.com/danog/MadelineProto.git synced 2025-01-11 12:48:25 +01:00
MadelineProto/src/EventHandler/Message.php
Mahdi c7b222ed78 Do som fixes
- Run composer cs-fix
2023-09-02 16:22:07 +03:30

461 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php declare(strict_types=1);
/**
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\EventHandler;
use danog\MadelineProto\API;
use danog\MadelineProto\EventHandler\Keyboard\InlineKeyboard;
use danog\MadelineProto\EventHandler\Keyboard\ReplyKeyboard;
use danog\MadelineProto\EventHandler\Media\Audio;
use danog\MadelineProto\EventHandler\Media\Document;
use danog\MadelineProto\EventHandler\Media\DocumentPhoto;
use danog\MadelineProto\EventHandler\Media\Gif;
use danog\MadelineProto\EventHandler\Media\MaskSticker;
use danog\MadelineProto\EventHandler\Media\Photo;
use danog\MadelineProto\EventHandler\Media\RoundVideo;
use danog\MadelineProto\EventHandler\Media\Sticker;
use danog\MadelineProto\EventHandler\Media\Video;
use danog\MadelineProto\EventHandler\Media\Voice;
use danog\MadelineProto\EventHandler\Message\Entities\MessageEntity;
use danog\MadelineProto\EventHandler\Message\ReportReason;
use danog\MadelineProto\MTProto;
use danog\MadelineProto\ParseMode;
use danog\MadelineProto\StrTools;
/**
* Represents an incoming or outgoing message.
*/
abstract class Message extends AbstractMessage
{
/** Content of the message */
public readonly string $message;
/** @var list<int|string> list of our message reactions */
protected array $reactions = [];
/** Info about a forwarded message */
public readonly ?ForwardedInfo $fwdInfo;
/** Bot command (if present) */
public readonly ?string $command;
/** Bot command type (if present) */
public readonly ?CommandType $commandType;
/** @var list<string> Bot command arguments (if present) */
public readonly ?array $commandArgs;
/** Whether this message is protected */
public readonly bool $protected;
/**
* @readonly
*
* @var list<string> Regex matches, if a filter regex is present
*/
public ?array $matches = null;
/**
* Attached media.
*/
public readonly Audio|Document|DocumentPhoto|Gif|MaskSticker|Photo|RoundVideo|Sticker|Video|Voice|null $media;
/** Whether this message is a sent scheduled message */
public readonly bool $fromScheduled;
/** If the message was generated by an inline query, ID of the bot that generated it */
public readonly ?int $viaBotId;
/** Last edit date of the message */
public readonly ?int $editDate;
/** Inline or reply keyboard. */
public readonly InlineKeyboard|ReplyKeyboard|null $keyboard;
/** Whether this message was [imported from a foreign chat service](https://core.telegram.org/api/import) */
public readonly bool $imported;
/** For Public Service Announcement messages, the PSA type */
public readonly ?string $psaType;
/** @readonly For sent messages, contains the next message in the chain if the original message had to be split. */
public ?self $nextSent = null;
// Todo media (photosizes, thumbs), albums, reactions, games eventually
/** View counter for messages from channels or forwarded from channels */
public readonly ?int $views;
/** Forward counter for messages from channels or forwarded from channels */
public readonly ?int $forwards;
/** Author of the post, if signatures are enabled for messages from channels or forwarded from channels */
public readonly ?string $signature;
/** @var list<MessageEntity> Message [entities](https://core.telegram.org/api/entities) for styled text */
public readonly array $entities;
/** @internal */
public function __construct(
MTProto $API,
array $rawMessage,
array $info,
) {
parent::__construct($API, $rawMessage, $info);
$this->views = $rawMessage['views'] ?? null;
$this->forwards = $rawMessage['forwards'] ?? null;
$this->signature = $rawMessage['post_author'] ?? null;
$this->entities = MessageEntity::fromRawEntities($rawMessage['entities'] ?? []);
$this->message = $rawMessage['message'];
$this->fromScheduled = $rawMessage['from_scheduled'];
$this->viaBotId = $rawMessage['via_bot_id'] ?? null;
$this->editDate = $rawMessage['edit_date'] ?? null;
$this->keyboard = isset($rawMessage['reply_markup'])
? Keyboard::fromRawReplyMarkup($rawMessage['reply_markup'])
: null;
if (isset($rawMessage['fwd_from'])) {
$fwdFrom = $rawMessage['fwd_from'];
$this->fwdInfo = new ForwardedInfo(
$fwdFrom['date'],
isset($fwdFrom['from_id'])
? $this->getClient()->getIdInternal($fwdFrom['from_id'])
: null,
$fwdFrom['from_name'] ?? null,
$fwdFrom['channel_post'] ?? null,
$fwdFrom['post_author'] ?? null,
isset($fwdFrom['saved_from_peer'])
? $this->getClient()->getIdInternal($fwdFrom['saved_from_peer'])
: null,
$fwdFrom['saved_from_msg_id'] ?? null
);
$this->psaType = $fwdFrom['psa_type'] ?? null;
$this->imported = $fwdFrom['imported'];
} else {
$this->fwdInfo = null;
$this->psaType = null;
$this->imported = false;
}
$this->protected = $rawMessage['noforwards'];
$this->media = isset($rawMessage['media'])
? $API->wrapMedia($rawMessage['media'], $this->protected)
: null;
if (\in_array($this->message[0] ?? '', ['/', '.', '!'], true)) {
$space = \strpos($this->message, ' ', 1) ?: \strlen($this->message);
$this->command = \substr($this->message, 1, $space-1);
$args = \explode(
' ',
\substr($this->message, $space+1)
);
$this->commandArgs = $args === [''] ? [] : $args;
$this->commandType = match ($this->message[0]) {
'.' => CommandType::DOT,
'/' => CommandType::SLASH,
'!' => CommandType::BANG,
};
} else {
$this->command = null;
$this->commandArgs = null;
$this->commandType = null;
}
foreach ($rawMessage['reactions']['results'] ?? [] as $r) {
if (isset($r['chosen_order'])) {
// Todo: live synchronization using a message database...
$this->reactions []= $r['reaction']['emoticon'] ?? $r['reaction']['document_id'];
}
}
}
/**
* Pin a message.
*
* @param bool $pmOneside Whether the message should only be pinned on the local side of a one-to-one chat
* @param bool $silent Pin the message silently, without triggering a notification
*/
public function pin(bool $pmOneside = false, bool $silent = false): ?AbstractMessage
{
$result = $this->getClient()->methodCallAsyncRead(
'messages.updatePinnedMessage',
[
'peer' => $this->chatId,
'id' => $this->id,
'pm_oneside' => $pmOneside,
'silent' => $silent,
'unpin' => false
]
);
return $this->getClient()->wrapMessage($this->getClient()->extractMessage($result));
}
/**
* Unpin a message.
*
* @param bool $pmOneside Whether the message should only be pinned on the local side of a one-to-one chat
* @param bool $silent Pin the message silently, without triggering a notification
*/
public function unpin(bool $pmOneside = false, bool $silent = false): ?Update
{
$result = $this->getClient()->methodCallAsyncRead(
'messages.updatePinnedMessage',
[
'peer' => $this->chatId,
'id' => $this->id,
'pm_oneside' => $pmOneside,
'silent' => $silent,
'unpin' => true
]
);
return $this->getClient()->wrapUpdate($result);
}
/**
* Get our reactions on the message.
*
* @return list<string|int>
*/
public function getOurReactions(): array
{
return $this->reactions;
}
/**
* Report a message in a chat for violation of telegrams Terms of Service.
*
* @param ReportReason $reason Why are these messages being reported
* @param string $message Comment for report moderation
*/
public function report(ReportReason $reason, string $message): bool
{
return $this->getClient()->methodCallAsyncRead(
'messages.report',
[
'reason' => ['_' => $reason->value],
'message' => $message,
'id' => [$this->id],
'peer' => $this->chatId
]
);
}
/**
* Save message sender to your account contacts.
*
* @param string $firstName First name
* @param string|null $lastName Last name
* @param string|null $phoneNumber Telegram ID of the other user
* @param bool $addPhonePrivacyException Allow the other user to see our phone number?
*/
public function saveContact(
string $firstName,
?string $lastName = null,
?string $phoneNumber = null,
bool $addPhonePrivacyException = false
): ?Update {
$result = $this->getClient()->methodCallAsyncRead(
'contacts.addContact',
[
'first_name' => $firstName,
'last_name' => $lastName,
'phone_number' => $phoneNumber,
'add_phone_privacy_exception' => $addPhonePrivacyException,
'id' => $this->senderId
]
);
return $this->getClient()->wrapUpdate($result);
}
/**
* Remove message sender from your account contacts.
*/
public function removeContact(): ?Update
{
$result = $this->getClient()->methodCallAsyncRead(
'contacts.deleteContacts',
[
'id' => [$this->senderId]
]
);
return $this->getClient()->wrapUpdate($result);
}
/**
* Invite message sender to requested channel.
*
* @param string|int $channel Username, Channel ID
*/
public function inviteToChannel(string|int $channel): ?Update
{
$result = $this->getClient()->methodCallAsyncRead(
'channels.inviteToChannel',
[
'channel' => $channel,
'users' => [$this->senderId]
]
);
return $this->getClient()->wrapUpdate($result);
}
/**
* Add reaction to message.
*
* @param string|int $reaction reaction
* @param bool $big Whether a bigger and longer reaction should be shown
* @param bool $addToRecent Add this reaction to the recent reactions list.
*
* @return list<string|int>
*/
public function addReaction(int|string $reaction, bool $big = false, bool $addToRecent = true): array
{
if (\in_array($reaction, $this->reactions, true)) {
return $this->reactions;
}
$this->getClient()->methodCallAsyncRead(
'messages.sendReaction',
[
'peer' => $this->chatId,
'msg_id' => $this->id,
'reaction' => \is_int($reaction)
? [['_' => 'reactionCustomEmoji', 'document_id' => $reaction]]
: [['_' => 'reactionEmoji', 'emoticon' => $reaction]],
'big' => $big,
'add_to_recent' => $addToRecent
]
);
$this->reactions[] = $reaction;
return $this->reactions;
}
/**
* Delete reaction from message.
*
* @param string|int $reaction string or int Reaction
*
* @return list<string|int>
*/
public function delReaction(int|string $reaction): array
{
$key = \array_search($reaction, $this->reactions, true);
if ($key === false) {
return $this->reactions;
}
unset($this->reactions[$key]);
$this->reactions = \array_values($this->reactions);
$r = \array_map(fn (string|int $r): array => \is_int($r) ? ['_' => 'reactionCustomEmoji', 'document_id' => $r] : ['_' => 'reactionEmoji', 'emoticon' => $r], $this->reactions);
$r[]= ['_' => 'reactionEmpty'];
$this->getClient()->methodCallAsyncRead(
'messages.sendReaction',
[
'peer' => $this->chatId,
'msg_id' => $this->id,
'reaction' => $r,
]
);
return $this->reactions;
}
/**
* Translate text message(for media translate it caption).
*
* @param string $toLang Two-letter ISO 639-1 language code of the language to which the message is translated
*
*/
public function translate(
string $toLang
): string {
if (empty($message = $this->message)) {
return $message;
}
$result = $this->getClient()->methodCallAsyncRead(
'messages.translateText',
[
'peer' => $this->chatId,
'id' => [$this->id],
'to_lang' => $toLang
]
);
return $result['result'][0]['text'];
}
/**
* Mark selected message as read pass 0 to $maxId parameter to read all messages in current chat.
*/
public function read(?int $maxId = null): bool
{
return $this->getClient()->methodCallAsyncRead(
API::isSupergroupOrChannel($this->chatId) ? 'channels.readHistory':'messages.readHistory',
[
'peer' => $this->chatId,
'channel' => $this->chatId,
'max_id' => $maxId ?? $this->id
]
);
}
/**
* Edit message text.
*
* @param string $message New message
* @param ParseMode $parseMode Whether to parse HTML or Markdown markup in the message
* @param array|null $replyMarkup Reply markup for inline keyboards
* @param int|null $scheduleDate Scheduled message date for scheduled messages
* @param bool $noWebpage Disable webpage preview
*
*/
public function editText(
string $message,
?array $replyMarkup = null,
ParseMode $parseMode = ParseMode::TEXT,
?int $scheduleDate = null,
bool $noWebpage = false
): Message {
$result = $this->getClient()->methodCallAsyncRead(
'messages.editMessage',
[
'peer' => $this->chatId,
'id' => $this->id,
'message' => $message,
'reply_markup' => $replyMarkup,
'parse_mode' => $parseMode,
'schedule_date' => $scheduleDate,
'no_webpage' => $noWebpage
]
);
return $this->getClient()->wrapMessage($this->getClient()->extractMessage($result));
}
protected readonly string $html;
protected readonly string $htmlTelegram;
/**
* Get an HTML version of the message.
*
* @psalm-suppress InaccessibleProperty
*
* @param bool $allowTelegramTags Whether to allow telegram-specific tags like tg-spoiler, tg-emoji, mention links and so on...
*/
public function getHTML(bool $allowTelegramTags = false): string
{
if (!$this->entities) {
return \htmlentities($this->message);
}
if ($allowTelegramTags) {
return $this->htmlTelegram ??= StrTools::entitiesToHtml($this->message, $this->entities, $allowTelegramTags);
}
return $this->html ??= StrTools::entitiesToHtml($this->message, $this->entities, $allowTelegramTags);
}
}