@@ -225,7 +225,8 @@ function hasTelegramErrorCode(err: unknown, matches: (code: number) => boolean):
225225 return false ;
226226}
227227
228- function hasTelegramRetryAfter ( err : unknown ) : boolean {
228+ /** Reads Telegram's flood-control retry_after hint (in ms) from any error nesting shape. */
229+ export function readTelegramRetryAfterMs ( err : unknown ) : number | undefined {
229230 for ( const candidate of collectTelegramErrorCandidates ( err ) ) {
230231 if ( ! candidate || typeof candidate !== "object" ) {
231232 continue ;
@@ -250,10 +251,10 @@ function hasTelegramRetryAfter(err: unknown): boolean {
250251 ?. retry_after
251252 : undefined ;
252253 if ( typeof retryAfter === "number" && Number . isFinite ( retryAfter ) ) {
253- return true ;
254+ return retryAfter * 1000 ;
254255 }
255256 }
256- return false ;
257+ return undefined ;
257258}
258259
259260/** Returns true for HTTP 5xx server errors (error may have been processed). */
@@ -264,10 +265,32 @@ export function isTelegramServerError(err: unknown): boolean {
264265export function isTelegramRateLimitError ( err : unknown ) : boolean {
265266 return (
266267 hasTelegramErrorCode ( err , ( code ) => code === 429 ) ||
267- ( hasTelegramRetryAfter ( err ) && / (?: ^ | \b ) 4 2 9 \b | t o o m a n y r e q u e s t s / i. test ( formatErrorMessage ( err ) ) )
268+ ( readTelegramRetryAfterMs ( err ) !== undefined &&
269+ / (?: ^ | \b ) 4 2 9 \b | t o o m a n y r e q u e s t s / i. test ( formatErrorMessage ( err ) ) )
268270 ) ;
269271}
270272
273+ const MESSAGE_NOT_MODIFIED_RE =
274+ / 4 0 0 : \s * B a d R e q u e s t : \s * m e s s a g e i s n o t m o d i f i e d | M E S S A G E _ N O T _ M O D I F I E D / i;
275+ const MESSAGE_HAS_NO_TEXT_RE = / 4 0 0 : \s * B a d R e q u e s t : \s * t h e r e i s n o t e x t i n t h e m e s s a g e t o e d i t / i;
276+ const EDIT_TARGET_MISSING_RE =
277+ / 4 0 0 : \s * B a d R e q u e s t : \s * m e s s a g e t o e d i t n o t f o u n d | 4 0 0 : \s * B a d R e q u e s t : \s * m e s s a g e c a n ' t b e e d i t e d | M E S S A G E _ I D _ I N V A L I D / i;
278+
279+ /** True when Telegram rejected an edit because the content is unchanged; the message already shows the requested text. */
280+ export function isTelegramMessageNotModifiedError ( err : unknown ) : boolean {
281+ return MESSAGE_NOT_MODIFIED_RE . test ( formatErrorMessage ( err ) ) ;
282+ }
283+
284+ /** True when the edit target has no text body (e.g. media message needing a caption edit). */
285+ export function isTelegramMessageHasNoTextError ( err : unknown ) : boolean {
286+ return MESSAGE_HAS_NO_TEXT_RE . test ( formatErrorMessage ( err ) ) ;
287+ }
288+
289+ /** True when the edit target is gone or locked (deleted message, invalid id); retrying the same edit cannot succeed. */
290+ export function isTelegramEditTargetMissingError ( err : unknown ) : boolean {
291+ return EDIT_TARGET_MISSING_RE . test ( formatErrorMessage ( err ) ) ;
292+ }
293+
271294/** Returns true for HTTP 4xx client errors (Telegram explicitly rejected, not applied). */
272295export function isTelegramClientRejection ( err : unknown ) : boolean {
273296 return hasTelegramErrorCode ( err , ( code ) => code >= 400 && code < 500 ) ;
0 commit comments