Skip to content

Commit f2792a3

Browse files
authored
Merge pull request #22 from WizardLoop/dev
V 3.2.2
2 parents ab90700 + faffee1 commit f2792a3

3 files changed

Lines changed: 85 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,31 +130,27 @@ Added functionality to send initial status messages when gathering peers and sta
130130
## [3.0.1] - 2026-01-11
131131

132132
### Fixed:
133-
134133
* lastBroadcastData
135134

136135
---
137136

138137
## [3.0.2] - 2026-01-11
139138

140139
### Added:
141-
142140
* setDataDir & getDataDir
143141

144142
---
145143

146144
## [3.0.3] - 2026-01-11
147145

148146
### Fixed:
149-
150147
* Fix getDataDir() to handle uninitialized $dataDir
151148

152149
---
153150

154151
## [3.0.4] - 2026-01-13
155152

156153
### Added & Fixed:
157-
158154
* Extracted peer filtering from broadcast execution
159155
* Reduced unnecessary processing during broadcasts
160156

@@ -163,7 +159,6 @@ Added functionality to send initial status messages when gathering peers and sta
163159
## [3.0.5] - 2026-01-18
164160

165161
### Added & Fixed:
166-
167162
* Handle additional RPCErrorException cases
168163

169164
---
@@ -186,7 +181,6 @@ Added functionality to send initial status messages when gathering peers and sta
186181
## [3.2.0] - 2026-06-13
187182

188183
### Added
189-
190184
- Edit last broadcast message with `editLastBroadcastForAll()`.
191185
- Optional `broadcastId` targeting for editing or deleting the last message of a specific broadcast.
192186
- Metadata peer loading for targeted edit/delete calls when `allUsers` is empty and `broadcastId` is provided.
@@ -198,7 +192,6 @@ Added functionality to send initial status messages when gathering peers and sta
198192
- Internal error logging to `data/broadcast-errors.log`.
199193

200194
### Changed
201-
202195
- Safer state handling using shared state references by broadcast id.
203196
- Safer cancel behavior: `cancel()` now marks cancellation without clearing in-flight requests.
204197
- `progress()` now includes edit, scheduled, self-destruct, total, elapsed, and TPS fields.
@@ -207,7 +200,6 @@ Added functionality to send initial status messages when gathering peers and sta
207200
- `deleteAllBroadcastsForAll()` now uses one progress loop instead of concurrent progress edits from workers.
208201

209202
### Fixed
210-
211203
- Pause/resume/cancel state reference issue.
212204
- Workers not stopping after `done`.
213205
- Unsafe watchdog behavior that could duplicate sends.
@@ -218,16 +210,42 @@ Added functionality to send initial status messages when gathering peers and sta
218210
## [3.2.1] - 2026-06-13
219211

220212
### Added
221-
222213
- Added support for editing last broadcast messages with media loaded from `data/{adminId}/media.txt`.
223214
- Added compatibility for passing saved media values / `botApiFileId` into `editLastBroadcastForAll()`.
224215

225216
### Changed
226-
227217
- Relaxed the `$media` parameter in `BroadcastManager::editLastBroadcastForAll()` so it is no longer limited to `?array`.
228218
- Edit-last-broadcast flow can now reuse the same saved media format used by regular broadcast sending.
229219

230220
### Notes
231-
232221
- Passing `null` as media keeps the existing media unchanged.
233222
- Passing a saved media value attempts to update the edited message media/caption.
223+
224+
---
225+
226+
## [3.2.2] - 2026-06-15
227+
228+
### Changed
229+
* Reduced default broadcast concurrency from `20` to `10`.
230+
* Reduced the maximum allowed concurrency limit from `50` to `30` to reduce pressure on the MadelineProto event loop during large broadcasts.
231+
* Progress status messages are now edited every `5` seconds instead of every second.
232+
* Progress status updates now also perform a final update when the operation reaches completion.
233+
* Slowed down broadcast workers with a small delay between processed jobs.
234+
* Added a delay after each media album chunk sent with `sendMultiMedia`.
235+
* Added a delay between sequential messages sent to the same peer.
236+
* Broadcast control buttons are now displayed in English:
237+
* `Pause`
238+
* `Resume`
239+
* `Cancel`
240+
241+
### Fixed
242+
* Reduced the chance of `Timeout while waiting for updates.getChannelDifference` after heavy broadcasts.
243+
* Reduced unnecessary progress-message edit calls during active broadcasts.
244+
* Prevented noisy logs for harmless `MESSAGE_NOT_MODIFIED` errors during progress updates.
245+
* Improved progress update stability by ignoring unchanged status edits instead of logging them as failures.
246+
247+
### Notes
248+
* This release keeps the custom BroadcastManager flow, including saved message IDs, edit-last-broadcast, delete-last-broadcast, scheduled broadcasts, and self-destruct broadcasts.
249+
* This is a stability and load-reduction update; it does not migrate to MadelineProto's official Broadcast API.
250+
251+
---

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# BroadcastManager
22

33
**High-Performance Telegram Broadcast Manager** for [MadelineProto](https://docs.madelineproto.xyz/).
4-
Manage Telegram broadcasts efficiently: send text, media, albums, inline buttons, pin/unpin messages, delete broadcasts, edit broadcast, schedule broadcasts, run self-destruct deletion jobs, and track live progress.
4+
Manage Telegram broadcasts efficiently: send text, media, albums, inline buttons, pin/unpin messages, delete previous broadcasts, edit the last broadcast, schedule broadcasts, run self-destruct deletion jobs, and track live progress.
55

66
[![AGPL License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
77
[![Made with PHP](https://img.shields.io/badge/Made%20with-PHP-blue)](https://github.com/WizardLoop/BroadcastManager)
@@ -94,10 +94,12 @@ $progress = $manager->progress($broadcastId);
9494
Concurrency is clamped internally:
9595

9696
* Minimum: `1`
97-
* Maximum: `50`
98-
* Default: `20`
97+
* Maximum: `30`
98+
* Default: `10`
9999
* Recommended examples: `10`
100100

101+
Live status messages are updated at most once every `5` seconds and once again when the operation finishes.
102+
101103
---
102104

103105
## Message Payloads
@@ -578,7 +580,7 @@ public function broadcastWithProgress(
578580
array $messages,
579581
$chatId = null,
580582
bool $pin = false,
581-
int $concurrency = 20,
583+
int $concurrency = 10,
582584
?int $selfDestructHours = null
583585
): string;
584586

@@ -587,8 +589,8 @@ public function editLastBroadcastForAll(
587589
string $newText,
588590
$chatId = null,
589591
?array $buttons = null,
590-
?array $media = null,
591-
int $concurrency = 20,
592+
$media = null,
593+
int $concurrency = 10,
592594
string $parseMode = 'HTML',
593595
?string $broadcastId = null
594596
): string;
@@ -599,7 +601,7 @@ public function scheduleBroadcastForAll(
599601
int $scheduledAt,
600602
$chatId = null,
601603
bool $pin = false,
602-
int $concurrency = 20,
604+
int $concurrency = 10,
603605
?int $selfDestructHours = null
604606
): string;
605607

@@ -610,11 +612,11 @@ public function listScheduledBroadcasts(): array;
610612
public function deleteLastBroadcastForAll(
611613
array $allUsers,
612614
$chatId = null,
613-
int $concurrency = 20,
615+
int $concurrency = 10,
614616
?string $broadcastId = null
615617
): string;
616-
public function deleteAllBroadcastsForAll(array $allUsers, $chatId = null, int $concurrency = 20): string;
617-
public function unpinAllMessagesForAll(array $allUsers, $chatId = null, int $concurrency = 20): string;
618+
public function deleteAllBroadcastsForAll(array $allUsers, $chatId = null, int $concurrency = 10): string;
619+
public function unpinAllMessagesForAll(array $allUsers, $chatId = null, int $concurrency = 10): string;
618620

619621
public function runDueSelfDestructJobs(): array;
620622
public function cancelSelfDestructJob(string $jobId): bool;

src/BroadcastManager.php

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,15 @@
2323
class BroadcastManager
2424
{
2525
private const MAX_ATTEMPTS = 3;
26-
private const DEFAULT_CONCURRENCY = 20;
27-
private const MAX_CONCURRENCY = 50;
26+
private const DEFAULT_CONCURRENCY = 10;
27+
private const MAX_CONCURRENCY = 30;
28+
private const PROGRESS_UPDATE_INTERVAL = 5.0;
29+
private const WORKER_IDLE_SLEEP = 1.0;
30+
private const WORKER_RETRY_SLEEP = 1.0;
31+
private const WORKER_PAUSED_SLEEP = 1.5;
32+
private const WORKER_AFTER_JOB_SLEEP = 0.75;
33+
private const SEND_MESSAGE_SLEEP = 0.35;
34+
private const SEND_CHUNK_SLEEP = 0.75;
2835

2936
private const SEND_HARD_FAIL_RPCS = [
3037
'INPUT_USER_DEACTIVATED',
@@ -919,25 +926,25 @@ private function startQueueWorkers(
919926
\Amp\async(function () use (&$state, $handler, $hardFailRpcs, $rpcHandler, $retryThrowable): void {
920927
while (!$state['cancel'] && !$state['done']) {
921928
if ($state['queue']->isEmpty()) {
922-
$this->api->sleep(0.5);
929+
$this->api->sleep(self::WORKER_IDLE_SLEEP);
923930
continue;
924931
}
925932

926933
if ($state['paused']) {
927-
$this->api->sleep(1);
934+
$this->api->sleep(self::WORKER_PAUSED_SLEEP);
928935
continue;
929936
}
930937

931938
$job = $state['queue']->dequeue();
932939

933940
if (($job['availableAt'] ?? 0) > microtime(true)) {
934941
$state['queue']->enqueue($job);
935-
$this->api->sleep(0.5);
942+
$this->api->sleep(self::WORKER_RETRY_SLEEP);
936943
continue;
937944
}
938945

939946
while ($state['paused'] && !$state['cancel'] && !$state['done']) {
940-
$this->api->sleep(1);
947+
$this->api->sleep(self::WORKER_PAUSED_SLEEP);
941948
}
942949

943950
if ($state['cancel'] || $state['done']) {
@@ -981,7 +988,7 @@ private function startQueueWorkers(
981988
}
982989
}
983990

984-
$this->api->sleep(0.25);
991+
$this->api->sleep(self::WORKER_AFTER_JOB_SLEEP);
985992
}
986993
});
987994
}
@@ -1093,6 +1100,8 @@ private function sendMessagesToPeer(string $peer, array $messages): array
10931100
$messageIds[] = $messageId;
10941101
}
10951102
}
1103+
1104+
$this->api->sleep(self::SEND_CHUNK_SLEEP);
10961105
}
10971106

10981107
return $messageIds;
@@ -1115,6 +1124,8 @@ private function sendMessagesToPeer(string $peer, array $messages): array
11151124
if ($messageId > 0) {
11161125
$messageIds[] = $messageId;
11171126
}
1127+
1128+
$this->api->sleep(self::SEND_MESSAGE_SLEEP);
11181129
}
11191130

11201131
return $messageIds;
@@ -1642,13 +1653,13 @@ private function buildStatusControls(array $state): ?array
16421653

16431654
$id = (string) $state['id'];
16441655
$toggleAction = !empty($state['paused']) ? 'resume' : 'pause';
1645-
$toggleText = !empty($state['paused']) ? '▶️ המשך' : '⏸ השהייה';
1656+
$toggleText = !empty($state['paused']) ? 'Resume' : 'Pause';
16461657

16471658
return [
16481659
'inline_keyboard' => [
16491660
[
16501661
['text' => $toggleText, 'callback_data' => 'bm:' . $toggleAction . ':' . $id],
1651-
['text' => '🛑 ביטול', 'callback_data' => 'bm:cancel:' . $id],
1662+
['text' => 'Cancel', 'callback_data' => 'bm:cancel:' . $id],
16521663
],
16531664
],
16541665
];
@@ -1710,6 +1721,20 @@ private function startProgressLoop($chatId, ?int $statusId, array &$state, strin
17101721

17111722
$this->api->messages->editMessage($payload);
17121723
$last = $fingerprint;
1724+
} catch (RPCErrorException $e) {
1725+
if (
1726+
($e->rpc ?? '') === 'MESSAGE_NOT_MODIFIED'
1727+
|| str_contains($e->getMessage(), 'MESSAGE_NOT_MODIFIED')
1728+
) {
1729+
$last = $fingerprint;
1730+
$this->api->sleep(self::PROGRESS_UPDATE_INTERVAL);
1731+
continue;
1732+
}
1733+
1734+
if ($loggedFailures < 3) {
1735+
$loggedFailures++;
1736+
$this->logError('Failed to update status message.', $e);
1737+
}
17131738
} catch (Throwable $e) {
17141739
if ($loggedFailures < 3) {
17151740
$loggedFailures++;
@@ -1718,7 +1743,7 @@ private function startProgressLoop($chatId, ?int $statusId, array &$state, strin
17181743
}
17191744
}
17201745

1721-
$this->api->sleep(1);
1746+
$this->api->sleep(self::PROGRESS_UPDATE_INTERVAL);
17221747
}
17231748
});
17241749
}
@@ -1742,6 +1767,15 @@ private function editStatusMessage($chatId, ?int $statusId, string $text, ?array
17421767
}
17431768

17441769
$this->api->messages->editMessage($payload);
1770+
} catch (RPCErrorException $e) {
1771+
if (
1772+
($e->rpc ?? '') === 'MESSAGE_NOT_MODIFIED'
1773+
|| str_contains($e->getMessage(), 'MESSAGE_NOT_MODIFIED')
1774+
) {
1775+
return;
1776+
}
1777+
1778+
$this->logError('Failed to edit final status message.', $e);
17451779
} catch (Throwable $e) {
17461780
$this->logError('Failed to edit final status message.', $e);
17471781
}

0 commit comments

Comments
 (0)