From 856561ebe7b9b7ce06c140f3bd8d9230aa45ecef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:01:19 +0300 Subject: [PATCH 01/11] Add .gitignore file with common exclusions --- .gitignore | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15e5acc --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +.env +.env.local +.env.*.local +/vendor/ +/node_modules/ +/.idea/ +/.vscode/ +.DS_Store +*.log +*.cache + +docker-compose.override.yml +*.pid + +composer.lock + +.phpunit.result.cache +/tests/output/ + +/build/ +/dist/ + +*.bak +*.swp +*.swo + +sessions +docs_md +session.mad +*.madeline +*.madeline.* +madeline.phar +madeline.phar.version +madeline.php + +*.save +*.save.1 + +*.save.* From 91cde40e0c46bc81d2f9348622f25b298b285374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:02:34 +0300 Subject: [PATCH 02/11] Add PHP CS Fixer configuration file --- .php-cs-fixer.dist.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .php-cs-fixer.dist.php diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..7ba95ac --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,18 @@ +in(__DIR__) + ->exclude('vendor') + ->exclude('data'); + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + 'array_syntax' => ['syntax' => 'short'], + 'no_unused_imports' => true, + 'single_quote' => true, + 'no_empty_phpdoc' => true, + 'not_operator_with_successor_space' => false, + ]) + ->setFinder($finder); From 2903e69b7349f74966c593daa7dadd1a926901fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:04:58 +0300 Subject: [PATCH 03/11] Create phpcs.xml for PHP CodeSniffer setup Add PHP CodeSniffer configuration for BroadcastManager --- phpcs.xml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 phpcs.xml diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..691a623 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,7 @@ + + + PHP CodeSniffer configuration + src + vendor/* + + From 0afb6d15494258700302591770304f27b46238b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:05:42 +0300 Subject: [PATCH 04/11] Add .gitattributes to manage line endings and exports --- .gitattributes | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ac9acf9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,20 @@ +# Force LF line endings +* text=auto eol=lf + +# Set file types +*.php text +*.sh text eol=lf +*.md text +*.yml text +*.json text +*.xml text +*.env text + +# Exclude tests and dev files from export +phpcs.xml export-ignore +php-cs-fixer.dist.php export-ignore +.dockerignore export-ignore +composer.lock export-ignore + +README.md export-ignore +CHANGELOG.md export-ignore From 610067fdb1bd914b29575c539e83fd60b2100f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:10:54 +0300 Subject: [PATCH 05/11] Add PHPStan configuration --- phpstan.neon | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 phpstan.neon diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..db37782 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 6 + paths: + - src From d23bf1a4ee2a777b01d606ed816b7489c14e5195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:15:00 +0300 Subject: [PATCH 06/11] Refactor BroadcastManager for improved functionality ## [3.1.0] - 2026-04-13 # Refactor BroadcastManager for improved functionality - Updated BroadcastManager class to improve functionality and code structure. - Added support for handling broadcast IDs, enhanced error handling, and refactored methods to accept optional chat IDs. ### Added: - added isActive() to check active - added option to set chatId as null ### Fixed: - fixed progress() to update progress state from all methods --- src/BroadcastManager.php | 282 +++++++++++++++++++++++++++++++-------- 1 file changed, 223 insertions(+), 59 deletions(-) diff --git a/src/BroadcastManager.php b/src/BroadcastManager.php index e8c2a63..18b605f 100644 --- a/src/BroadcastManager.php +++ b/src/BroadcastManager.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * Broadcast Manager + * Broadcast Manager Tool * * @author - WizardLoop * @copyright - WizardLoop @@ -22,7 +22,6 @@ class BroadcastManager { private API $api; private ?array $currentBroadcastState = null; - private array $albumTimers = []; private static string $dataDir = ''; public function __construct(API $api) @@ -32,14 +31,14 @@ public function __construct(API $api) /** * Set data dir - */ + */ public static function setDataDir(string $path): void { self::$dataDir = rtrim($path, '/'); } /** * Get data dir - */ + */ private static function getDataDir(): string { if (!self::$dataDir) { self::$dataDir = __DIR__ . '/../data'; @@ -48,29 +47,39 @@ private static function getDataDir(): string { } /** - * Broadcast messages to users/channels with progress tracking + * Send broadcast. + * + * @return integer ID that can be used */ public function broadcastWithProgress( array $allUsers, array $messages, - $chatId, + $chatId = null, bool $pin = false, int $concurrency = 20 - ): array { + ): string { $api = $this->api; + $statusId = null; + if ($chatId) { + try { /* ===== INIT STATUS ===== */ - $status = $api->messages->sendMessage([ + $status = $api->messages->sendMessage([ 'peer' => $chatId, 'message' => 'โŒ› GATHERING PEERS...', 'parse_mode' => 'HTML' ]); $statusId = $api->extractMessageId($status); + } catch (\Throwable) { } + } $total = count($allUsers); + $broadcastId = bin2hex(random_bytes(8)); + /* ===== STATE ===== */ $state = [ + 'type' => 'send', 'sent' => 0, 'failed' => 0, 'queue' => new \SplQueue(), @@ -82,8 +91,7 @@ public function broadcastWithProgress( 'startedAt' => microtime(true), ]; -/* ===== SET CURRENT BROADCAST STATE FOR PAUSE/CANCEL ===== */ - $this->currentBroadcastState = &$state; + $this->currentBroadcastState[$broadcastId] = $state; foreach ($allUsers as $peer) { $state['queue']->enqueue([ @@ -113,7 +121,7 @@ public function broadcastWithProgress( ($state['paused'] ? "\nโธ Paused" : ''). ($state['cancel'] ? "\n๐Ÿ›‘ Cancelled" : ''); - if ($text !== $last) { + if ($chatId && $statusId && $text !== $last) { try { $api->messages->editMessage([ 'peer' => $chatId, @@ -270,14 +278,17 @@ public function broadcastWithProgress( "โšก TPS: {$tps} msg/s\n". ($state['cancel'] ? "๐Ÿ›‘ Cancelled" : "โœ… Finished"); - try { $api->messages->editMessage(['peer'=>$chatId,'id'=>$statusId,'message'=>$finalText,'parse_mode'=>'HTML']); } catch (\Throwable) {} - $dir1= self::getDataDir(); - if(!is_dir($dir1))@mkdir($dir1,0777,true); + if ($chatId) { + try { $api->messages->editMessage(['peer'=>$chatId,'id'=>$statusId,'message'=>$finalText,'parse_mode'=>'HTML']); } catch (\Throwable) {} + } + + $dir1 = self::getDataDir(); + try { if(!is_dir($dir1))@mkdir($dir1,0777,true); } catch (\Throwable) {} try { \Amp\File\write("$dir1/LastBrodDATA.txt",$finalText); } catch (\Throwable) {} foreach ($state['lastMessageIds'] as $peer=>$id) { $dir = self::getDataDir() . "/$peer"; - if(!is_dir($dir))@mkdir($dir,0777,true); + try { if(!is_dir($dir))@mkdir($dir,0777,true); } catch (\Throwable) {} try { $fh = \Amp\File\openFile("$dir/messages.txt", "a"); $fh->write((string)$id . "\n"); @@ -286,22 +297,28 @@ public function broadcastWithProgress( try{\Amp\File\write("$dir/lastBroadcast.txt",$id);}catch(\Throwable){} } - return $state; + $this->currentBroadcastState[$broadcastId] = $state; + return $broadcastId; } /** - * Delete last broadcast message for all users + * Deletes the last broadcast message for all users. + * + * @return integer ID that can be used */ public function deleteLastBroadcastForAll( array $allUsers, - $chatId, + $chatId = null, int $concurrency = 20 - ): array { + ): string { $api = $this->api; $total = count($allUsers); + $broadcastId = bin2hex(random_bytes(8)); + /* ===== STATE ===== */ $state = [ + 'type' => 'deletelast', 'deleted' => 0, 'failed' => 0, 'flood' => 0, @@ -312,6 +329,8 @@ public function deleteLastBroadcastForAll( 'startedAt' => microtime(true), ]; + $this->currentBroadcastState[$broadcastId] = $state; + foreach ($allUsers as $peer) { $state['queue']->enqueue([ 'peer' => (string)$peer, @@ -321,6 +340,9 @@ public function deleteLastBroadcastForAll( ]); } + $statusId = null; + if ($chatId) { + try { /* ===== STATUS MESSAGE ===== */ $status = $api->messages->sendMessage([ 'peer' => $chatId, @@ -328,6 +350,8 @@ public function deleteLastBroadcastForAll( 'parse_mode' => 'HTML' ]); $statusId = $api->extractMessageId($status); + } catch (\Throwable) { } + } /* ===== PROGRESS LOOP ===== */ \Amp\async(function () use ($api, $chatId, $statusId, &$state, $total) { @@ -347,7 +371,7 @@ public function deleteLastBroadcastForAll( "โšก TPS: {$tps} msg/s". ($state['cancel'] ? "\n๐Ÿ›‘ Cancelled" : ''); - if ($text !== $last) { + if ($chatId && $statusId && $text !== $last) { try { $api->messages->editMessage([ 'peer' => $chatId, @@ -482,6 +506,7 @@ public function deleteLastBroadcastForAll( "โŒ Failed: {$state['failed']}\n". ($state['cancel'] ? "๐Ÿ›‘ Cancelled" : "โœ… Finished"); + if ($chatId) { try { $api->messages->editMessage([ 'peer' => $chatId, @@ -490,22 +515,29 @@ public function deleteLastBroadcastForAll( 'parse_mode' => 'HTML' ]); } catch (\Throwable) {} + } - return $state; + $this->currentBroadcastState[$broadcastId] = $state; + return $broadcastId; } /** - * Delete all broadcasts message for all users + * Deletes all broadcast message for all users. + * + * @return integer ID that can be used */ public function deleteAllBroadcastsForAll( array $allUsers, - $chatId, + $chatId = null, int $concurrency = 20 - ): array { + ): string { $api = $this->api; $total = count($allUsers); + $broadcastId = bin2hex(random_bytes(8)); + $state = [ + 'type' => 'deleteall', 'deleted' => 0, 'failed' => 0, 'flood' => 0, @@ -516,6 +548,8 @@ public function deleteAllBroadcastsForAll( 'startedAt' => microtime(true), ]; + $this->currentBroadcastState[$broadcastId] = $state; + foreach ($allUsers as $peer) { $state['queue']->enqueue([ 'peer' => (string)$peer, @@ -525,12 +559,17 @@ public function deleteAllBroadcastsForAll( ]); } + $statusId = null; + if ($chatId) { + try { $status = $api->messages->sendMessage([ 'peer' => $chatId, 'message' => "โŒ› Deleting all broadcasts...", 'parse_mode' => 'HTML' ]); $statusId = $api->extractMessageId($status); + } catch (\Throwable) { } + } \Amp\async(function () use (&$state) { while (!$state['done']) { @@ -623,8 +662,8 @@ public function deleteAllBroadcastsForAll( else $state['failed']++; $file2 = self::getDataDir() ."/$peer/lastBroadcast.txt"; - @unlink($file2); - @unlink($file); + try { @unlink($file2); } catch (\Throwable) { } + try { @unlink($file); } catch (\Throwable) { } unset($state['inFlight'][$peer]); $processed = $state['deleted'] + $state['failed']; @@ -640,6 +679,7 @@ public function deleteAllBroadcastsForAll( "โณ Pending: $pending\n". "โšก TPS: {$tps} msg/s"; + if ($chatId) { try { $api->messages->editMessage([ 'peer' => $chatId, @@ -648,6 +688,7 @@ public function deleteAllBroadcastsForAll( 'parse_mode' => 'HTML' ]); } catch (\Throwable) {} + } } catch (\Throwable) { unset($state['inFlight'][$peer]); @@ -678,6 +719,7 @@ public function deleteAllBroadcastsForAll( "โŒ Failed: {$state['failed']}\n". ($state['cancel'] ? "๐Ÿ›‘ Cancelled" : "โœ… Finished"); + if ($chatId) { try { $api->messages->editMessage([ 'peer' => $chatId, @@ -686,20 +728,27 @@ public function deleteAllBroadcastsForAll( 'parse_mode' => 'HTML' ]); } catch (\Throwable) {} + } - return $state; + $this->currentBroadcastState[$broadcastId] = $state; + return $broadcastId; } /** * Unpin all messages for all users + * + * @return integer ID that can be used */ public function unpinAllMessagesForAll( array $allUsers, - $chatId, + $chatId = null, int $concurrency = 20 - ): array { + ): string { $api = $this->api; + $statusId = null; + if ($chatId) { + try { /* ===== STATUS MESSAGE ===== */ $status = $api->messages->sendMessage([ 'peer' => $chatId, @@ -707,11 +756,16 @@ public function unpinAllMessagesForAll( 'parse_mode' => 'HTML' ]); $statusId = $api->extractMessageId($status); + } catch (\Throwable) { } + } $total = count($allUsers); + $broadcastId = bin2hex(random_bytes(8)); + /* ===== STATE ===== */ $state = [ + 'type' => 'unpin', 'unpin' => 0, 'failed' => 0, 'flood' => 0, @@ -722,6 +776,8 @@ public function unpinAllMessagesForAll( 'startedAt' => microtime(true), ]; + $this->currentBroadcastState[$broadcastId] = $state; + foreach ($allUsers as $peer) { $state['queue']->enqueue([ 'peer' => $peer, @@ -731,12 +787,16 @@ public function unpinAllMessagesForAll( ]); } + if ($chatId) { + try { $api->messages->editMessage([ 'peer' => $chatId, 'id' => $statusId, 'message' => "๐Ÿ“ŒโŒ› Starting unpin for all subscribers...", 'parse_mode' => 'HTML' ]); + } catch (\Throwable) { } + } /* ===== PROGRESS LOOP ===== */ \Amp\async(function () use ($api, $chatId, $statusId, &$state, $total) { @@ -757,7 +817,7 @@ public function unpinAllMessagesForAll( "โšก TPS: {$tps} msg/s". ($state['cancel'] ? "\n๐Ÿ›‘ Cancelled" : ''); - if ($text !== $last) { + if ($chatId && $statusId && $text !== $last) { try { $api->messages->editMessage([ 'peer' => $chatId, @@ -867,6 +927,7 @@ public function unpinAllMessagesForAll( "โŒ Failed: {$state['failed']}\n". ($state['cancel'] ? "๐Ÿ›‘ Cancelled" : "โœ… Finished"); + if ($chatId) { try { $api->messages->editMessage([ 'peer' => $chatId, @@ -875,8 +936,10 @@ public function unpinAllMessagesForAll( 'parse_mode' => 'HTML' ]); } catch (\Throwable) {} + } - return $state; + $this->currentBroadcastState[$broadcastId] = $state; + return $broadcastId; } /** @@ -891,43 +954,62 @@ private function progressBar(int $current, int $total): string { /** * Pause running broadcast */ - public function pause(): void { - if ($this->currentBroadcastState) { - $this->currentBroadcastState['paused'] = true; + public function pause(string $id): void { + if (isset($this->currentBroadcastState[$id])) { + $this->currentBroadcastState[$id]['paused'] = true; } } /** * Resume running broadcast */ - public function resume(): void { - if ($this->currentBroadcastState) { - $this->currentBroadcastState['paused'] = false; + public function resume(string $id): void { + if (isset($this->currentBroadcastState[$id])) { + $this->currentBroadcastState[$id]['paused'] = false; } } /** * cancel running broadcast */ - public function cancel(): void { - if ($this->currentBroadcastState) { - $this->currentBroadcastState['cancel'] = true; - $this->currentBroadcastState['inFlight'] = []; + public function cancel(string $id): void { + if (isset($this->currentBroadcastState[$id])) { + $this->currentBroadcastState[$id]['cancel'] = true; + $this->currentBroadcastState[$id]['inFlight'] = []; } } /** * Check if broadcast is paused */ - public function isPaused(): bool { - return $this->currentBroadcastState['paused'] ?? false; + public function isPaused(string $id): bool { + return $this->currentBroadcastState[$id]['paused'] ?? false; } /** * Check if broadcast is cancelled */ - public function isCancelled(): bool { - return $this->currentBroadcastState['cancel'] ?? false; + public function isCancelled(string $id): bool { + return $this->currentBroadcastState[$id]['cancel'] ?? false; + } + + /** + * Check if broadcast is active + */ + public function isActive(?string $id = null): bool { + if (!$id || !isset($this->currentBroadcastState[$id])) { + return false; + } + + $state = $this->currentBroadcastState[$id]; + + if (!$state) return false; + + return ( + empty($state['done']) && + empty($state['cancel']) && + empty($state['paused']) + ); } /** @@ -955,23 +1037,97 @@ public function hasAllBroadcast(): bool { } /** - * Get current broadcast progress + * normalize broadcast state */ - public function progress(): ?array { - if (!$this->currentBroadcastState) return null; + private function normalizeBroadcastState(array $state): array { + return [ + 'sent' => $state['sent'] ?? 0, + 'deleted' => $state['deleted'] ?? 0, + 'unpin' => $state['unpin'] ?? 0, + 'failed' => $state['failed'] ?? 0, + 'flood' => $state['flood'] ?? 0, + + 'queue' => $state['queue'] ?? null, + 'inFlight' => $state['inFlight'] ?? [], - $state = $this->currentBroadcastState; - $processed = $state['sent'] + $state['failed']; - $pending = ($state['queue'] ? $state['queue']->count() : 0); + 'done' => $state['done'] ?? false, + 'paused' => $state['paused'] ?? false, + 'cancel' => $state['cancel'] ?? false, + + 'startedAt' => $state['startedAt'] ?? null, + ]; + } + + /** + * Get current broadcast progress + * + * @return array|null { + * processed: int, // total processed items (sent + deleted + unpin + failed) + * success: int, // successful operations (sent + deleted + unpin) + * failed: int, // failed operations count + * pending: int, // remaining items in queue + * flood: int, // FLOOD_WAIT occurrences + * + * progressPercent: float, // completion percentage (processed / total) + * + * breakdown: array { + * sent: int, + * deleted: int, + * unpin: int + * }, + * + * done: bool, // process finished + * paused: bool, // process paused + * cancel: bool, // process cancelled + * + * startedAt: float // microtime start timestamp + * } + */ + public function progress(?string $id = null): ?array { + if (!$id || !isset($this->currentBroadcastState[$id])) { + return null; + } + + $state = $this->normalizeBroadcastState($this->currentBroadcastState[$id]); + + $sent = (int)($state['sent'] ?? 0); + $deleted = (int)($state['deleted'] ?? 0); + $unpin = (int)($state['unpin'] ?? 0); + $failed = (int)($state['failed'] ?? 0); + $flood = (int)($state['flood'] ?? 0); + + $processed = $sent + $deleted + $unpin + $failed; + $success = $sent + $deleted + $unpin; + + $pending = ($state['queue'] instanceof \SplQueue) + ? $state['queue']->count() + : 0; + + $total = $processed + $pending; + + $progressPercent = $total > 0 + ? round(($processed / $total) * 100, 2) + : 0; return [ - 'sent' => $state['sent'], - 'failed' => $state['failed'], - 'flood' => $state['flood'] ?? 0, - 'pending' => $pending, - 'done' => $state['done'] ?? false, - 'paused' => $state['paused'] ?? false, - 'cancelled' => $state['cancel'] ?? false, + 'processed' => $processed, + 'success' => $success, + 'failed' => $failed, + 'pending' => $pending, + 'flood' => $flood, + + 'progressPercent' => $progressPercent, + + 'breakdown' => [ + 'sent' => $sent, + 'deleted' => $deleted, + 'unpin' => $unpin, + ], + + 'done' => (bool)($state['done'] ?? false), + 'paused' => (bool)($state['paused'] ?? false), + 'cancel' => (bool)($state['cancel'] ?? false), + 'startedAt' => $state['startedAt'] ?? null, ]; } @@ -991,11 +1147,18 @@ public function lastBroadcastData(): string|false { return false; } -return \Amp\File\read($path); + return \Amp\File\read($path); } /** * Filter peers + * allowedTypes: all / users / groups / channels + * + * @return array { + * targets: array, // filtered peers + * failed: int, // count of failed + * total: int, // count filtered peers + * } */ public function filterPeers( array $allUsers, @@ -1034,4 +1197,5 @@ public function filterPeers( ]; } + } From dddfdd1a12b0e00ecd998ee865833bde188723fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:16:58 +0300 Subject: [PATCH 07/11] Refactor BroadcastManager and update CHANGELOG Refactor BroadcastManager to improve functionality and code structure, including enhanced error handling and support for optional chat IDs. --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 823ae63..f0ab89e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -165,3 +165,20 @@ Added functionality to send initial status messages when gathering peers and sta ### Added & Fixed: * Handle additional RPCErrorException cases + +--- + +## [3.1.0] - 2026-04-13 + +# Refactor BroadcastManager for improved functionality +- Updated BroadcastManager class to improve functionality and code structure. +- Added support for handling broadcast IDs, enhanced error handling, and refactored methods to accept optional chat IDs. + +### Added: +- added `isActive()` to check active +- added option to set chatId as null + +### Fixed: +- fixed `progress()` to update progress state from all methods + +--- From 0e0acb2c089c8cb5bd09023079263c86456ccabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:18:47 +0300 Subject: [PATCH 08/11] Revise README with updated broadcast methods Updated README to reflect changes in broadcast management methods and usage examples. --- README.md | 159 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 110 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index bdbab61..7a99e44 100644 --- a/README.md +++ b/README.md @@ -58,29 +58,6 @@ Version](https://img.shields.io/packagist/v/wizardloop/broadcastmanager)](https: --- -## ๐Ÿ“ Repository Structure - -``` -BroadcastManager/ -โ”œโ”€โ”€ src/ -โ”‚ โ””โ”€โ”€ BroadcastManager.php -โ”œโ”€โ”€ data/ -โ”‚ โ””โ”€โ”€ .gitkeep -โ”œโ”€โ”€ composer.json -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ LICENSE -โ””โ”€โ”€ CHANGELOG.md -``` - ---- - -## ๐Ÿ’ป Requirements - -* [MadelineProto](https://docs.madelineproto.xyz/) -* [amphp/amp](https://amphp.org/) - ---- - ## โšก Installation ```bash @@ -97,6 +74,10 @@ require 'vendor/autoload.php'; ## ๐Ÿš€ Usage Example +--- +## Send Broadcast + +### 1) live progress update in message to admin: ```php use BroadcastTool\BroadcastManager; @@ -105,10 +86,90 @@ $manager = new BroadcastManager($api); $manager->broadcastWithProgress($users, $messages, $adminChatId, true, 20); ``` +### 2) track on progress without message: +This method returns an integer ID that can be used. + +```php +use BroadcastTool\BroadcastManager; + +$manager = new BroadcastManager($api); + +$broadcastId = $manager->broadcastWithProgress($users, $messages, null, true, 20); + +/** + * Get progress (can be polled) + */ +$progress = $manager->progress($broadcastId); + +if ($progress !== null) { + + // ๐Ÿ“Š Core stats + $processed = $progress['processed']; + $success = $progress['success']; + $failed = $progress['failed']; + $pending = $progress['pending']; + $flood = $progress['flood']; + + // ๐Ÿ“ˆ Progress % + $progressPercent = $progress['progressPercent']; + + // ๐Ÿ“ฆ Breakdown + $sent = $progress['breakdown']['sent']; + $deleted = $progress['breakdown']['deleted']; + $unpin = $progress['breakdown']['unpin']; + + // โš™๏ธ State + $done = $progress['done']; + $paused = $progress['paused']; + $cancel = $progress['cancel']; + + // โฑ Timing + $startedAt = $progress['startedAt']; + + /** + * Example usage + */ + echo "Progress: {$progressPercent}%\n"; + echo "Sent: {$sent}\n"; + echo "Failed: {$failed}\n"; + + if ($done) { + echo "Broadcast finished!"; + } + + if ($paused) { + echo "Broadcast paused..."; + } +} +``` + +```php +* progress return array|null { +* processed: int, // total processed items (sent + deleted + unpin + failed) +* success: int, // successful operations (sent + deleted + unpin) +* failed: int, // failed operations count +* pending: int, // remaining items in queue +* flood: int, // FLOOD_WAIT occurrences +* +* progressPercent: float, // completion percentage (processed / total) +* +* breakdown: array { +* sent: int, +* deleted: int, +* unpin: int +* }, +* +* done: bool, // process finished +* paused: bool, // process paused +* cancel: bool, // process cancelled +* +* startedAt: float // microtime start timestamp +* } +``` + --- ## Filer Peers - ```php $filterSub = $manager->filterPeers($users, 'users'); $targets = $filterSub['targets']; # array @@ -121,32 +182,32 @@ $total = $filterSub['total']; # int ## โธ Control Broadcasts ```php -$manager->pause(); -$manager->resume(); -$manager->cancel(); +$manager->pause($broadcastId); +$manager->resume($broadcastId); +$manager->cancel($broadcastId); ``` -Check state: +### Check state: ```php -if ($manager->isPaused()) echo "Paused"; -if ($manager->isCancelled()) echo "Cancelled"; -if (!$manager->hasLastBroadcast()) echo "No last Broadcast do delete"; -if (!$manager->hasAllBroadcast()) echo "No all Broadcast to delete"; -print_r($manager->progress()); +if ($manager->isActive($broadcastId)); +if ($manager->isPaused($broadcastId)); +if ($manager->isCancelled($broadcastId)); +if (!$manager->hasLastBroadcast($broadcastId)); +if (!$manager->hasAllBroadcast($broadcastId)); +print_r($manager->progress($broadcastId)); ``` -Set data dir: +### Set data dir: ```php -BroadcastManager::setDataDir(__DIR__ . '/data'); +BroadcastManager::setDataDir(__DIR__ . '/data'); // default: __DIR__ . '/../data' ``` -_default is: __DIR__ . '/../data'_ --- ## ๐Ÿงน Delete Last Broadcast ```php -$manager->deleteLastBroadcastForAll($users, $adminChatId, 20); +$broadcastId = $manager->deleteLastBroadcastForAll($users, $adminChatId, 20); ``` --- @@ -154,7 +215,7 @@ $manager->deleteLastBroadcastForAll($users, $adminChatId, 20); ## โ™ป๏ธ Delete All Broadcast ```php -$manager->deleteAllBroadcastsForAll($users, $adminChatId, 20); +$broadcastId = $manager->deleteAllBroadcastsForAll($users, $adminChatId, 20); ``` --- @@ -162,23 +223,23 @@ $manager->deleteAllBroadcastsForAll($users, $adminChatId, 20); ## ๐Ÿ“Š Get Last Broadcast Data ```php -$manager->lastBroadcastData(); +$broadcastId = $manager->lastBroadcastData(); ``` --- ## ๐Ÿ“Œ Pin / Unpin Messages -Pin last broadcast automatically: +## Pin last broadcast automatically: ```php -$manager->broadcastWithProgress(..., pin: true); +$broadcastId = $manager->broadcastWithProgress(..., pin: true); ``` -Unpin all messages: +## Unpin all messages: ```php -$manager->unpinAllMessagesForAll(...); +$broadcastId = $manager->unpinAllMessagesForAll(...); ``` --- @@ -199,11 +260,11 @@ $message = [ ## โš™๏ธ Advanced Options -* **Concurrency** โ€“ Number of parallel workers. -* **Filter Types** โ€“ 'users', 'groups', 'channels', 'all' -* **Album Handling** โ€“ JSON-based albums with multiple media files. -* **Retries & Delays** โ€“ Automatic retries with backoff. -* **Progress Tracking** โ€“ Real-time broadcast stats with `progress()`. +* **Concurrency** - Number of parallel workers. +* **Filter Types** - 'users', 'groups', 'channels', 'all' +* **Album Handling** - JSON-based albums with multiple media files. +* **Retries & Delays** - Automatic retries with backoff. +* **Progress Tracking** - Real-time broadcast stats with `progress()`. --- @@ -219,7 +280,7 @@ $message = [ ## ๐Ÿ“„ License -**GNU AGPL-3.0** โ€” see [LICENSE](LICENSE). +**GNU AGPL-3.0** - see [LICENSE](LICENSE). --- From 326cf795d6eb3a7e9b367d68e6c4af5f89f5857f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:23:49 +0300 Subject: [PATCH 09/11] Adjust PHPStan configuration parameters Lower PHPStan level from 6 to 5 and add new parameters. --- phpstan.neon | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index db37782..e0142f9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,10 @@ parameters: - level: 6 + level: 5 paths: - src + + checkMissingIterableValueType: true + checkGenericClassInNonGenericObjectType: true + + ignoreErrors: + - '#Call to an undefined method#' From be54df8435abe094dc87d5bd3385a1dcd3549e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:32:17 +0300 Subject: [PATCH 10/11] Add CI workflow for PHP project --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..704a97f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + tools: composer:v2 + + - name: Validate composer.json + run: composer validate --strict + continue-on-error: true + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-interaction + continue-on-error: true + + - name: PHP Syntax check + run: find src -name "*.php" -exec php -l {} \; + continue-on-error: true From 4c9d9f9d655e2ac13dad097462c5b166427a7b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wizard=20Loop=20=F0=9F=A7=99=E2=80=8D=E2=99=82=EF=B8=8F?= <67387949+WizardLoop@users.noreply.github.com> Date: Mon, 13 Apr 2026 09:35:33 +0300 Subject: [PATCH 11/11] Add CI badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7a99e44..a02bea4 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Manage broadcasts efficiently: send messages, media albums, pin/unpin messages, [![Packagist Version](https://img.shields.io/packagist/v/wizardloop/broadcastmanager)](https://packagist.org/packages/wizardloop/broadcastmanager) [![Packagist Downloads](https://img.shields.io/packagist/dt/wizardloop/broadcastmanager?color=blue)](https://packagist.org/packages/wizardloop/broadcastmanager) +![CI](https://github.com/WizardLoop/BroadcastManager/actions/workflows/ci.yml/badge.svg) ---