Added ChatMemberCache manager

This commit is contained in:
Zi Xing 2021-12-25 18:54:57 -05:00
parent a96109ff84
commit 11a8032d36
9 changed files with 843 additions and 6 deletions

View File

@ -0,0 +1,14 @@
create table chat_member_cache
(
chat_id varchar(86) not null comment 'Unique identifier for the target chat or username of the
target supergroup or channel (in the format @channelusername)'
primary key,
administrator_permissions blob null comment 'ZiProto encoded blob of the administrator permissions',
chat_member_count int null comment 'The amount of members in the chat/channel',
last_updated_timestamp int null comment 'The Unix Timestamp for when the record was last updated',
created_timestamp int null comment 'The Unix Timestamp for when the record was first registered into the database',
constraint chat_member_cache_chat_id_uindex
unique (chat_id)
)
comment 'A table for housing a cache of the current state of chat members in the group/channel';

View File

@ -0,0 +1,21 @@
<?php
namespace Synical\Exceptions;
use Exception;
use Throwable;
class CannotUpdateChatMembersCacheException extends Exception
{
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Synical\Exceptions;
use Throwable;
class ChatMemberCacheRecordNotFoundException extends \Exception
{
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "The requested chat member cache was not found in the database", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Synical\Exceptions;
use Exception;
use Throwable;
class DatabaseException extends Exception
{
private string $query;
/**
* @param string $message
* @param string $query
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", string $query="", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->query = $query;
$this->code = $code;
}
/**
* @return string
*/
public function getQuery(): string
{
return $this->query;
}
}

View File

@ -0,0 +1,189 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace Synical\Managers;
use Longman\TelegramBot\Entities\Chat;
use Longman\TelegramBot\Entities\ChatMember\ChatMember;
use Longman\TelegramBot\Entities\ChatMember\ChatMemberAdministrator;
use Longman\TelegramBot\Entities\ChatMember\ChatMemberOwner;
use Longman\TelegramBot\Request;
use msqg\QueryBuilder;
use Synical\Exceptions\CannotUpdateChatMembersCacheException;
use Synical\Exceptions\ChatMemberCacheRecordNotFoundException;
use Synical\Exceptions\DatabaseException;
use Synical\Objects\AdminCacheRecord\AdministratorPermissions;
use Synical\Objects\ChatMemberCache;
use Synical\Synical;
use ZiProto\ZiProto;
class ChatMemberCacheManager
{
private $synical;
/**
* @param Synical $synical
*/
public function __construct(Synical $synical)
{
$this->synical = $synical;
}
/**
* Registers the record into the database
*
* @param ChatMemberCache $chatMemberCache
* @return ChatMemberCache
* @throws DatabaseException
* @noinspection PhpCastIsUnnecessaryInspection
*/
public function registerRecord(ChatMemberCache $chatMemberCache): ChatMemberCache
{
$chatMemberCache->LastUpdatedTimestamp = time();
$chatMemberCache->CreatedTimestamp = time();
$admin_permissions = $chatMemberCache->toArray()['administrator_permissions'];
$Query = QueryBuilder::insert_into('chat_member_cache', [
'chat_id' => $this->synical->getDatabase()->real_escape_string($chatMemberCache->ChatID),
'administrator_permissions' => $this->synical->getDatabase()->real_escape_string(ZiProto::encode($admin_permissions)),
'chat_member_count' => (int)$chatMemberCache->ChatMemberCount,
'last_updated_timestamp' => (int)$chatMemberCache->LastUpdatedTimestamp,
'created_timestamp' => (int)$chatMemberCache->CreatedTimestamp
]);
$QueryResults = $this->synical->getDatabase()->query($Query);
if($QueryResults == false)
throw new DatabaseException($this->synical->getDatabase()->error, $Query, $this->synical->getDatabase()->errno);
return $chatMemberCache;
}
/**
* Returns an existing record from the database
*
* @param string $chat_id
* @return ChatMemberCache
* @throws ChatMemberCacheRecordNotFoundException
* @throws DatabaseException
*/
public function getRecord(string $chat_id): ChatMemberCache
{
$Query = QueryBuilder::select('chat_member_cache', [
'chat_id',
'administrator_permissions',
'chat_member_count',
'last_updated_timestamp',
'created_timestamp'
], 'chat_id', $this->synical->getDatabase()->real_escape_string($chat_id));
$QueryResults = $this->synical->getDatabase()->query($Query);
if($QueryResults == false)
throw new DatabaseException($this->synical->getDatabase()->error, $Query, $this->synical->getDatabase()->errno);
if($QueryResults->num_rows == 0)
throw new ChatMemberCacheRecordNotFoundException();
$Row = $QueryResults->fetch_array(MYSQLI_ASSOC);
$Row['administrator_permissions'] = ZiProto::decode($Row['administrator_permissions']);
return ChatMemberCache::fromArray($Row);
}
/**
* Updates an existing record in the database
*
* @param ChatMemberCache $chatMemberCache
* @return ChatMemberCache
* @throws DatabaseException
*/
public function updateRecord(ChatMemberCache $chatMemberCache): ChatMemberCache
{
$chatMemberCache->LastUpdatedTimestamp = time();
$admin_permissions = $chatMemberCache->toArray()['administrator_permissions'];
$Query = QueryBuilder::update('chat_member_cache', [
'administrator_permissions' => $this->synical->getDatabase()->real_escape_string(ZiProto::encode($admin_permissions)),
'chat_member_count' => (int)$chatMemberCache->ChatMemberCount,
'last_updated_timestamp' => (int)$chatMemberCache->LastUpdatedTimestamp,
], 'chat_id', $this->synical->getDatabase()->real_escape_string($chatMemberCache->ChatID));
$QueryResults = $this->synical->getDatabase()->query($Query);
if($QueryResults == false)
throw new DatabaseException($this->synical->getDatabase()->error, $Query, $this->synical->getDatabase()->errno);
return $chatMemberCache;
}
/**
* A smart function to get the chat member cache
*
* @param Chat $chat
* @return ChatMemberCache
* @throws CannotUpdateChatMembersCacheException
* @throws DatabaseException
*/
public function getChatMemberCache(Chat $chat): ChatMemberCache
{
try
{
$returnResults = $this->getRecord($chat->getId());
if((time() - $returnResults->LastUpdatedTimestamp) < 300)
return $returnResults;
$needsUpdate = true;
}
catch(ChatMemberCacheRecordNotFoundException $e)
{
$needsUpdate = false;
$returnResults = null;
unset($e);
}
$Results = Request::getChatAdministrators(['chat_id' => $chat->getId()]);
if($Results->isOk() == false)
{
if($returnResults !== null)
{
return $returnResults;
}
throw new CannotUpdateChatMembersCacheException($Results->getDescription(), $Results->getErrorCode());
}
if($returnResults == null)
$returnResults = new ChatMemberCache();
$chatMembersResponse = $Results->getRawData()["result"];
/** @var ChatMemberOwner $chatMemberOwner */
foreach($chatMembersResponse as $chatMemberOwner)
{
if($chatMemberOwner->getStatus() == 'creator')
{
$returnResults->AdministratorPermissions[] = AdministratorPermissions::fromChatMemberOwner($chatMemberOwner);
}
}
/** @var ChatMemberAdministrator $chatMemberAdministrator */
foreach($chatMembersResponse as $chatMemberAdministrator)
{
if($chatMemberAdministrator->getStatus() == 'administrator')
{
$returnResults->AdministratorPermissions[] = AdministratorPermissions::fromChatMemberAdministrator($chatMemberAdministrator);
}
}
$returnResults->ChatMemberCount = null;
$chatMemberCountResponse = Request::getChatMemberCount(['chat_id' => $chat->getId()]);
if($chatMemberCountResponse->isOk())
$returnResults->ChatMemberCount = (int)$chatMemberCountResponse->getResult();
if($needsUpdate)
return $this->updateRecord($returnResults);
return $this->registerRecord($returnResults);
}
}

View File

@ -0,0 +1,306 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace Synical\Objects\AdminCacheRecord;
use Longman\TelegramBot\Entities\ChatMember\ChatMemberAdministrator;
use Longman\TelegramBot\Entities\ChatMember\ChatMemberOwner;
class AdministratorPermissions
{
/**
* Unique identifier for this user or bot. This number may have more than 32 significant bits and some
* programming languages may have difficulty/silent defects in interpreting it. But it has at most 52
* significant bits, so a 64-bit integer or double-precision float type are safe for storing this identifier.
*
* @var int
*/
public $ID;
/**
* Indicates if the chat member is the owner of the chat (Full permissions)
*
* @var bool
*/
public $IsOwner;
/**
* Indicates if the chat member is an administrator of the chat
*
* @var bool
*/
public $IsAdmin;
/**
* Indicates if the chat member is a bot
*
* @var bool
*/
public $IsBot;
/**
* if the bot is allowed to edit administrator privileges of that user
*
* @var bool
*/
public $CanBeEdited;
/**
* True, If the user's presence in the chat is hidden (Anonymous Admin)
*
* @var bool
*/
public $IsAnonymous;
/**
* True, if the administrator can access the chat event log, chat statistics, message statistics in channels,
* see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by
* any other administrator privilege
*
* @var bool
*/
public $CanManageChat;
/**
* True, if the administrator can delete messages of other users
*
* @var bool
*/
public $CanDeleteMessages;
/**
* True, if the administrator can manage voice chats
*
* @var bool
*/
public $CanManageVoiceChats;
/**
* True, if the administrator can restrict, ban or unban chat members
*
* @var bool
*/
public $CanRestrictMembers;
/**
* True, if the administrator can add new administrators with a subset of their own privileges or demote
* administrators that he has promoted, directly or indirectly (promoted by administrators that were
* appointed by the user)
*
* @var bool
*/
public $CanPromoteMembers;
/**
* True, if the user is allowed to change the chat title, photo and other settings
*
* @var bool
*/
public $CanChangeInfo;
/**
* True, if the user is allowed to invite new users to the chat
*
* @var bool
*/
public $CanInviteUsers;
/**
* Optional. True, if the administrator can post in the channel; channels only
*
* @var bool
*/
public $CanPostMessages;
/**
* Optional. True, if the administrator can edit messages of other users and can pin messages; channels only
*
* @var bool
*/
public $CanEditMessages;
/**
* Optional. True, if the user is allowed to pin messages; groups and supergroups only
*
* @var bool
*/
public $CanPinMessages;
/**
* Optional. Custom title for this user
*
* @var null|bool
*/
public $CustomTitle;
public function __construct()
{
$this->ID = null;
$this->IsOwner = false;
$this->IsAdmin = false;
$this->IsBot = false;
$this->CanBeEdited = false;
$this->IsAnonymous = false;
$this->CanManageChat = false;
$this->CanDeleteMessages = false;
$this->CanManageVoiceChats = false;
$this->CanRestrictMembers = false;
$this->CanPromoteMembers = false;
$this->CanChangeInfo = false;
$this->CanInviteUsers = false;
$this->CanPostMessages = false;
$this->CanEditMessages = false;
$this->CanPinMessages = false;
$this->CustomTitle = null;
}
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->ID,
'is_owner' => $this->IsOwner,
'is_admin' => $this->IsAdmin,
'is_bot' => $this->IsBot,
'can_be_edited' => $this->CanBeEdited,
'is_anonymous' => $this->IsAnonymous,
'can_manage_chat' => $this->CanManageChat,
'can_delete_messages' => $this->CanDeleteMessages,
'can_manage_voice_chats' => $this->CanManageVoiceChats,
'can_restrict_members' => $this->CanRestrictMembers,
'can_promote_members' => $this->CanPromoteMembers,
'can_change_info' => $this->CanChangeInfo,
'can_invite_users' => $this->CanInviteUsers,
'can_post_messages' => $this->CanPostMessages,
'can_edit_messages' => $this->CanEditMessages,
'can_pin_messages' => $this->CanPinMessages,
'custom_title' => $this->CustomTitle
];
}
/**
* Constructs object from an array representation of the object
*
* @param array $data
* @return AdministratorPermissions
*/
public static function fromArray(array $data): AdministratorPermissions
{
$Permissions = new AdministratorPermissions();
if(isset($data['id']))
$Permissions->ID = (int)$data['id'];
if(isset($data['is_owner']))
$Permissions->IsOwner = (bool)$data['is_owner'];
if(isset($data['is_admin']))
$Permissions->IsAdmin = (bool)$data['is_admin'];
if(isset($data['is_bot']))
$Permissions->IsBot = (bool)$data['is_bot'];
if(isset($data['can_be_edited']))
$Permissions->CanBeEdited = (bool)$data['can_be_edited'];
if(isset($data['is_anonymous']))
$Permissions->IsAnonymous = (bool)$data['is_anonymous'];
if(isset($data['can_manage_chat']))
$Permissions->CanManageChat = (bool)$data['can_manage_chat'];
if(isset($data['can_delete_messages']))
$Permissions->CanDeleteMessages = (bool)$data['can_delete_messages'];
if(isset($data['can_manage_voice_chats']))
$Permissions->CanManageVoiceChats = (bool)$data['can_manage_voice_chats'];
if(isset($data['can_restrict_members']))
$Permissions->CanRestrictMembers = (bool)$data['can_restrict_members'];
if(isset($data['can_promote_members']))
$Permissions->CanPromoteMembers = (bool)$data['can_promote_members'];
if(isset($data['can_change_info']))
$Permissions->CanChangeInfo = (bool)$data['can_change_info'];
if(isset($data['can_invite_users']))
$Permissions->CanInviteUsers = (bool)$data['can_invite_users'];
if(isset($data['can_post_messages']))
$Permissions->CanPostMessages = (bool)$data['can_post_messages'];
if(isset($data['can_edit_messages']))
$Permissions->CanEditMessages = (bool)$data['can_edit_messages'];
if(isset($data['can_pin_messages']))
$Permissions->CanPinMessages = (bool)$data['can_pin_messages'];
return $Permissions;
}
/**
* Constructs object from ChatMemberOwner object
*
* @param ChatMemberOwner $chatMemberOwner
* @return AdministratorPermissions
*/
public static function fromChatMemberOwner(ChatMemberOwner $chatMemberOwner): AdministratorPermissions
{
$Permissions = new AdministratorPermissions();
$Permissions->IsOwner = true;
$Permissions->ID = $chatMemberOwner->getUser()->getId();
$Permissions->IsBot = $chatMemberOwner->getUser()->getIsBot();
$Permissions->IsAnonymous = $chatMemberOwner->getIsAnonymous();
$Permissions->CustomTitle = $chatMemberOwner->getCustomTitle();
$Permissions->CanBeEdited = false;
/** @noinspection DuplicatedCode */
$Permissions->CanManageChat = true;
$Permissions->CanDeleteMessages = true;
$Permissions->CanManageVoiceChats = true;
$Permissions->CanRestrictMembers = true;
$Permissions->CanPromoteMembers = true;
$Permissions->CanChangeInfo = true;
$Permissions->CanInviteUsers = true;
$Permissions->CanPostMessages = true;
$Permissions->CanEditMessages = true;
$Permissions->CanPinMessages = true;
return $Permissions;
}
/**
* Constructs object form a ChatMemberAdministrator object
*
* @param ChatMemberAdministrator $chatMemberAdministrator
* @return AdministratorPermissions
*/
public static function fromChatMemberAdministrator(ChatMemberAdministrator $chatMemberAdministrator): AdministratorPermissions
{
$Permissions = new AdministratorPermissions();
$Permissions->IsAdmin = true;
$Permissions->ID = $chatMemberAdministrator->getUser()->getId();
$Permissions->IsBot = $chatMemberAdministrator->getUser()->getIsBot();
$Permissions->IsAnonymous = $chatMemberAdministrator->getIsAnonymous();
$Permissions->CustomTitle = $chatMemberAdministrator->getCustomTitle();
$Permissions->CanBeEdited = $chatMemberAdministrator->getCanBeEdited();
/** @noinspection DuplicatedCode */
$Permissions->CanManageChat = $chatMemberAdministrator->getCanManageChat();
$Permissions->CanDeleteMessages = $chatMemberAdministrator->getCanDeleteMessages();
$Permissions->CanManageVoiceChats = $chatMemberAdministrator->getCanManageVoiceChats();
$Permissions->CanRestrictMembers = $chatMemberAdministrator->getCanRestrictMembers();
$Permissions->CanPromoteMembers = $chatMemberAdministrator->getCanPromoteMembers();
$Permissions->CanChangeInfo = $chatMemberAdministrator->getCanChangeInfo();
$Permissions->CanInviteUsers = $chatMemberAdministrator->getCanInviteUsers();
$Permissions->CanPostMessages = $chatMemberAdministrator->getCanPostMessages();
$Permissions->CanEditMessages = $chatMemberAdministrator->getCanEditMessages();
$Permissions->CanPinMessages = $chatMemberAdministrator->getCanPinMessages();
return $Permissions;
}
}

View File

@ -0,0 +1,102 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace Synical\Objects;
use Synical\Objects\AdminCacheRecord\AdministratorPermissions;
class ChatMemberCache
{
/**
* Unique identifier for the target chat or username of the
* target supergroup or channel (in the format @channelusername)
*
* @var int|string
*/
public $ChatID;
/**
* An array of chat administrators with their allocated permissions
*
* @var AdministratorPermissions[]
*/
public $AdministratorPermissions;
/**
* The number of members in the chat
*
* @var int|null
*/
public $ChatMemberCount;
/**
* The Unix Timestamp for when this record was last updated
*
* @var int
*/
public $LastUpdatedTimestamp;
/**
* The Unix Timestamp for when this record was first created
*
* @var int
*/
public $CreatedTimestamp;
public function __construct()
{
$this->AdministratorPermissions = [];
}
/**
* Returns an aray representation of the object
*
* @return array
*/
public function toArray(): array
{
$AdministratorPermissions = [];
foreach($this->AdministratorPermissions as $permission)
$AdministratorPermissions[] = $permission->toArray();
return [
'chat_id' => $this->ChatID,
'administrator_permissions' => $AdministratorPermissions,
'chat_member_count' => $this->ChatMemberCount,
'last_updated_timestamp' => $this->LastUpdatedTimestamp,
'created_timestamp' => $this->CreatedTimestamp
];
}
/**
* Returns an array representation of the object
*
* @param array $data
* @return ChatMemberCache
*/
public static function fromArray(array $data): ChatMemberCache
{
$ChatMemberCache = new ChatMemberCache();
if(isset($data['chat_id']))
$ChatMemberCache->ChatID = $data['chat_id'];
if(isset($data['administrator_permissions']))
{
foreach($data['administrator_permissions'] as $administrator_permission)
$ChatMemberCache->AdministratorPermissions[] = AdministratorPermissions::fromArray($administrator_permission);
}
if(isset($data['chat_member_count']))
$ChatMemberCache->ChatMemberCount = (int)$data['chat_member_count'];
if(isset($data['last_updated_timestamp']))
$ChatMemberCache->LastUpdatedTimestamp = (int)$data['last_updated_timestamp'];
if(isset($data['created_timestamp']))
$ChatMemberCache->CreatedTimestamp = (int)$data['created_timestamp'];
return $ChatMemberCache;
}
}

View File

@ -1,8 +1,109 @@
<?php
namespace Synical;
/** @noinspection PhpMissingFieldTypeInspection */
class Synical
{
namespace Synical;
}
use acm2\acm2;
use acm2\Exceptions\ConfigurationNotDefinedException;
use acm2\Objects\Schema;
use mysqli;
use Synical\Managers\ChatMemberCacheManager;
class Synical
{
/**
* @var mixed
*/
private $DatabaseConfiguration;
/**
* @var null|mysqli
*/
private $DatabaseConnection;
/**
* @var acm2
*/
private $acm;
/**
* @var ChatMemberCacheManager
*/
private $ChatMemberCacheManager;
/**
* @throws ConfigurationNotDefinedException
*/
public function __construct()
{
$this->acm = new acm2('Synical');
// Database Schema Configuration
$DatabaseSchema = new Schema();
$DatabaseSchema->setName('Database');
$DatabaseSchema->setDefinition('Host', '127.0.0.1');
$DatabaseSchema->setDefinition('Port', '3306');
$DatabaseSchema->setDefinition('Username', 'root');
$DatabaseSchema->setDefinition('Password', 'root');
$DatabaseSchema->setDefinition('Name', 'synical');
$this->acm->defineSchema($DatabaseSchema);
// Update the configuration
$this->acm->updateConfiguration();
$this->DatabaseConfiguration = $this->acm->getConfiguration('Database');
$this->DatabaseConnection = null;
$this->ChatMemberCacheManager = new ChatMemberCacheManager($this);
}
/**
* @return mysqli
*/
public function getDatabase(): mysqli
{
if($this->DatabaseConnection == null)
{
$this->connectDatabase();
}
return $this->DatabaseConnection;
}
/**
* Closes the current database connection
*/
public function disconnectDatabase()
{
$this->DatabaseConnection->close();
$this->DatabaseConnection = null;
}
/**
* Creates a new database connection
*/
public function connectDatabase()
{
if($this->DatabaseConnection !== null)
{
$this->disconnectDatabase();
}
$this->DatabaseConnection = new mysqli(
$this->DatabaseConfiguration['Host'],
$this->DatabaseConfiguration['Username'],
$this->DatabaseConfiguration['Password'],
$this->DatabaseConfiguration['Name'],
$this->DatabaseConfiguration['Port']
);
}
/**
* @return ChatMemberCacheManager
*/
public function getChatMemberCacheManager(): ChatMemberCacheManager
{
return $this->ChatMemberCacheManager;
}
}

View File

@ -7,7 +7,32 @@
"organization": "Intellivoid Technologies",
"description": "The backend provider for the Synical engine",
"url": "https://github.com/intellivoid/synical",
"dependencies": [],
"dependencies": [
{
"package": "net.intellivoid.ziproto",
"version": "latest",
"source": "default@github/intellivoid/ZiProto",
"required": true
},
{
"package": "net.intellivoid.msqg",
"version": "latest",
"source": "default@github/intellivoid/msqg",
"required": true
},
{
"package": "net.intellivoid.acm2",
"version": "latest",
"source": "default@github/intellivoid/acm2",
"required": true
},
{
"package": "net.intellivoid.tdlib",
"version": "latest",
"source": "default@github/intellivoid/tdlib",
"required": true
}
],
"configuration": {
"autoload_method": "generated_spl",
"main": null,
@ -19,7 +44,33 @@
{
"required": true,
"file": "Synical.php"
},
{
"required": true,
"file": "Exceptions/DatabaseException.php"
},
{
"required": true,
"file": "Exceptions/CannotUpdateChatMembersCacheException.php"
},
{
"required": true,
"file": "Exceptions/ChatMemberCacheRecordNotFoundException.php"
},
{
"required": true,
"file": "Objects/ChatMemberCache.php"
},
{
"required": true,
"file": "Objects/AdminCacheRecord/AdministratorPermissions.php"
},
{
"required": true,
"file": "Managers/ChatMemberCacheManager.php"
}
],
"files": []
"files": [
"package.json"
]
}