@@ -80,6 +80,19 @@ def __init__(self,
8080 def __repr__ (self ) -> str :
8181 return f"{ type (self ).__name__ } (discord={ self ._apiobject } )"
8282
83+ @property
84+ def deleted (self ) -> bool :
85+ """
86+ Returns
87+ -----------
88+ True
89+ The object is no longer in the framework and should no longer
90+ be used.
91+ False
92+ Object is in the framework in normal operation.
93+ """
94+ return self ._deleted
95+
8396 @property
8497 def messages (self ) -> List [BaseMESSAGE ]:
8598 """
@@ -142,6 +155,8 @@ def _delete(self):
142155 Sets the internal _deleted flag to True.
143156 """
144157 self ._deleted = True
158+ for message in self ._messages :
159+ message ._delete ()
145160
146161 @typechecked
147162 async def add_message (self , message : BaseMESSAGE ):
@@ -183,6 +198,7 @@ def remove_message(self, message: BaseMESSAGE):
183198 ValueError
184199 Raised when the message is not present in the list.
185200 """
201+ message ._delete ()
186202 self ._messages .remove (message )
187203
188204 async def initialize (self , parent : Any , getter : Callable ) -> None :
@@ -251,14 +267,18 @@ async def update(self, init_options={}, **kwargs):
251267 raise NotImplementedError
252268
253269 @misc ._async_safe ("update_semaphore" , 1 )
254- async def _advertise (self ):
270+ async def _advertise (self ) -> List [ Coroutine ] :
255271 """
256- Main coroutine responsible for sending all the messages to this specific guild,
272+ Common to all messages, function responsible for sending all the messages to this specific guild,
257273 it is called from the core module's advertiser task.
274+
275+ Returns
276+ -----------
277+ List[Coroutine]
278+ List of coroutines that will call message._send() method.
258279 """
259280 to_await = []
260281 to_remove = []
261- guild_ctx = self .generate_log_context ()
262282 for message in self ._messages :
263283 if message ._check_state ():
264284 to_remove .append (message )
@@ -272,13 +292,7 @@ async def _advertise(self):
272292 for message in to_remove :
273293 self .remove_message (message )
274294
275- # Await coroutines outside the main loop to prevent list modification (by user)
276- # while iterating, this way even if the user removes the message, it will still be shilled
277- # but no exceptions will be raised when trying to remove the message.
278- for coro in to_await :
279- message_ctx = await coro
280- if self .logging and message_ctx is not None :
281- await logging .save_log (guild_ctx , message_ctx )
295+ return to_await
282296
283297 def generate_log_context (self ) -> Dict [str , Union [str , int ]]:
284298 """
@@ -364,6 +378,22 @@ async def initialize(self, parent: Any) -> None:
364378 Raised from .add_message(message_object) method.
365379 """
366380 return await super ().initialize (parent , parent .client .get_guild )
381+
382+ async def _advertise (self ) -> None :
383+ """
384+ Implementation specific _advertise method.
385+ Same as super()._advertise(), except it removes other DirectMESSAGE
386+ instances in case of them got a forbidden request.
387+ """
388+ to_await = await super ()._advertise ()
389+ guild_ctx = self .generate_log_context ()
390+ # Await coroutines outside the main loop to prevent list modification (by user)
391+ # while iterating, this way even if the user removes the message, it will still be shilled
392+ # but no exceptions will be raised when trying to remove the message.
393+ for coro in to_await :
394+ message_ctx = await coro
395+ if self .logging and message_ctx is not None :
396+ await logging .save_log (guild_ctx , message_ctx )
367397
368398 @misc ._async_safe ("update_semaphore" , 2 ) # Take 2 since 2 tasks share access
369399 async def update (self , init_options = {}, ** kwargs ):
@@ -428,6 +458,7 @@ class USER(_BaseGUILD):
428458 """
429459 __slots__ = (
430460 "update_semaphore" ,
461+ "_panic"
431462 )
432463
433464 @typechecked
@@ -437,6 +468,7 @@ def __init__(self,
437468 logging : Optional [bool ] = False ,
438469 remove_after : Optional [Union [timedelta , datetime ]]= None ) -> None :
439470 super ().__init__ (snowflake , messages , logging , remove_after )
471+ self ._panic = False # Set to True whenever message sends detected insufficient permissions
440472 misc ._write_attr_once (self , "update_semaphore" , asyncio .Semaphore (2 )) # Only allows re-referencing this attribute once
441473
442474 def _check_state (self ) -> bool :
@@ -450,7 +482,7 @@ def _check_state(self) -> bool:
450482 False
451483 The user is in proper state, do not delete.
452484 """
453- return super ()._check_state ()
485+ return self . _panic or super ()._check_state ()
454486
455487 async def initialize (self , parent : Any ):
456488 """
@@ -465,6 +497,29 @@ async def initialize(self, parent: Any):
465497 """
466498 return await super ().initialize (parent , parent .client .get_or_fetch_user )
467499
500+
501+ async def _advertise (self ) -> None :
502+ """
503+ Implementation specific _advertise method.
504+ Same as super()._advertise(), except it removes other DirectMESSAGE
505+ instances in case of them got a forbidden request.
506+ """
507+ to_await = await super ()._advertise ()
508+ guild_ctx = self .generate_log_context ()
509+ # Await coroutines outside the main loop to prevent list modification (by user)
510+ # while iterating, this way even if the user removes the message, it will still be shilled
511+ # but no exceptions will be raised when trying to remove the message.
512+ for coro in to_await :
513+ message_ctx , panic = await coro
514+ if self .logging and message_ctx is not None :
515+ await logging .save_log (guild_ctx , message_ctx )
516+
517+ # panic means that the message send resulted in a forbidden error
518+ # signaling all other messages should be removed without send
519+ if panic :
520+ self ._panic = True
521+ break
522+
468523 @misc ._async_safe ("update_semaphore" , 2 )
469524 async def update (self , init_options = {}, ** kwargs ):
470525 """
@@ -603,6 +658,19 @@ def created_at(self) -> datetime:
603658 Returns the datetime of when the object has been created.
604659 """
605660 return self ._created_at
661+
662+ @property
663+ def deleted (self ) -> bool :
664+ """
665+ Returns
666+ -----------
667+ True
668+ The object is no longer in the framework and should no longer
669+ be used.
670+ False
671+ Object is in the framework in normal operation.
672+ """
673+ return self ._deleted
606674
607675 def _delete (self ):
608676 """
0 commit comments