def get_root_data_api_token(self, token: str) -> GetRootUserDataApiResult: api_u_data = self._mgr_api.get_user_data_token(token) entry = None onplat_list = [] if api_u_data is None: outcome = GetOutcome.X_NOT_FOUND_FIRST_QUERY else: root_u_data = self.get_root_data_api_oid(api_u_data.id) if root_u_data is None: outcome = GetOutcome.X_NOT_FOUND_SECOND_QUERY else: entry = root_u_data outcome = GetOutcome.O_CACHE_DB if outcome.is_success and entry.has_onplat_data: missing = [] for oid in entry.on_plat_oids: onplat_data = self._mgr_onplat.get_onplat_by_oid(oid) if onplat_data: onplat_list.append(onplat_data) else: missing.append(oid) if missing: MailSender.send_email_async( f"On Platform Data not found while getting the root user data by providing api token.\n" f"API Token: {token} / OID of Missing On Platform Data: {' | '.join([str(i) for i in missing])}", subject="On Platform Data not found") return GetRootUserDataApiResult(outcome, entry, api_u_data, onplat_list)
def collate_child_channel_data(root_oid: ObjectId, child_channel_oids: List[ObjectId]) \ -> List[CollatedChannelData]: accessible: List[CollatedChannelData] = [] inaccessible: List[CollatedChannelData] = [] missing_oids = [] for ccoid in child_channel_oids: cdata = ChannelManager.get_channel_oid(ccoid) if cdata: ccd = CollatedChannelData( channel_name=cdata.get_channel_name(root_oid), channel_data=cdata) if cdata.bot_accessible: accessible.append(ccd) else: inaccessible.append(ccd) else: missing_oids.append(ccoid) if missing_oids: MailSender.send_email_async( f"No associated channel data found of the channel IDs below:<br>" f"<pre>{' / '.join([str(oid) for oid in missing_oids])}</pre>") accessible = sorted(accessible, key=lambda data: data.channel_data.id, reverse=True) inaccessible = sorted(inaccessible, key=lambda data: data.channel_data.id, reverse=True) return accessible + inaccessible
async def on_private_channel_update(self, before: GroupChannel, after: GroupChannel): if str(before) != str(after) \ and not ChannelManager.update_channel_default_name(Platform.DISCORD, after.id, str(after)).is_success: warn_txt = f"Private Channel Name UPDATED but the name was not updated. Channel token: {after.id} / " \ f"Name: {str(before)} to {str(after)}" DISCORD.logger.warning(warn_txt) MailSender.send_email_async(warn_txt, subject="Discord private channel name update failed")
def integrate(old_oid: ObjectId, new_oid: ObjectId) -> bool: """ :return: Integration succeed or not. """ # In function import to prevent circular import from mongodb.factory import BaseCollection, get_collection_subclasses, MONGO_CLIENT, RootUserManager # Replace UID entries failed_names: List[str] = [] cls: BaseCollection for cls in get_collection_subclasses(): if cls.model_class: col = MONGO_CLIENT.get_database( cls.database_name).get_collection(cls.collection_name) failed_names.extend( cls.model_class.replace_uid(col, old_oid, new_oid)) if failed_names: MailSender.send_email_async( f"Fields value replacements failed.<hr><pre>{'<br>'.join(failed_names)}</pre>", subject="User Data Integration Failed.") return False else: return RootUserManager.merge_onplat_to_api(old_oid, new_oid).is_success
def handle_error(e: Exception): subject = "Error on Discord Message Processing" html = f"<h4>{e}</h4>\n" \ f"<hr>\n" \ f"<pre>Traceback:\n" \ f"{traceback.format_exc()}</pre>\n" MailSender.send_email_async(html, subject=subject) DISCORD.logger.error(subject, exc_info=True)
def __send_email__(msg, event, destination): html = f"<h4>{msg}</h4>\n" \ f"<hr>\n" \ f"<pre>Traceback:\n" \ f"{traceback.format_exc()}</pre>\n" \ f"<hr>\n" \ f"Event:\n" \ f"{event}\n" \ f"Destination: {destination}" MailSender.send_email_async(html, subject="Error on LINE webhook")
def _ensure_channel_(platform: Platform, token: Union[int, str], default_name: str = None) \ -> Optional[ChannelModel]: ret = ChannelManager.register(platform, token, default_name=default_name) if ret.success: # Use Thread so no need to wait until the update is completed Thread(target=ChannelManager.mark_accessibility, args=(platform, token, True)).start() else: MailSender.send_email_async(f"Platform: {platform} / Token: {token}", subject="Channel Registration Failed") return ret.model
def post(self, request, *args, **kwargs): s = str(_("An unknown error occurred.")) s_contact = " " + str(_("Contact the administrator of the website.")) token = None try: result = RootUserManager.register_google( get_identity_data(request.POST.get("idtoken"))) if result.outcome.is_success: s = AccountLoginView.PASS_SIGNAL token = result.idt_reg_result.token elif result.outcome == WriteOutcome.X_NOT_EXECUTED: s = _("Registration process not performed.") elif result.outcome == WriteOutcome.X_NOT_ACKNOWLEDGED: s = _("New user data creation failed.") elif result.outcome == WriteOutcome.X_NOT_SERIALIZABLE: s = _("The data cannot be passed into the server.") else: s = _( "An unknown error occurred during the new user data registration. " "Code: {} / Registration Code: {}.").format( result.outcome.code, result.idt_reg_result.outcome.code) if not result.outcome.is_success: MailSender.send_email_async( f"Result: {result.serialize()}<br>" f"Outcome: {result.outcome}<br>" f"Exception: {result.exception}<br>" f"Registration: {result.idt_reg_result.outcome}<br>" f"Registration Exception: {result.idt_reg_result.exception}<br>", subject="New user data registration failed") except IDIssuerIncorrect as ex1: s = str(ex1) except Exception as ex2: # EXNOTE: Insert `raise ex2` when any error occurred during login # raise ex2 s += f" ({ex2})" if s != AccountLoginView.PASS_SIGNAL: s += s_contact response = simple_json_response(s) if token is not None: response.set_cookie(keys.Cookies.USER_TOKEN, token) if s == AccountLoginView.PASS_SIGNAL and token is None: return simple_json_response( _("User token is null however login succeed. {}").format( s_contact)) else: return response
async def on_guild_channel_update( self, before: Union[TextChannel, VoiceChannel, CategoryChannel], after: Union[TextChannel, VoiceChannel, CategoryChannel]): if str(before) != str(after): update_result = ChannelCollectionManager.update_default_name( Platform.DISCORD, after.guild.id, channel_full_repr(after)) if not update_result.is_success: warn_txt = f"Guild Channel Name UPDATED but the name was not updated. " \ f"Channel token: {after.id}" DISCORD.logger.warning(warn_txt) MailSender.send_email_async(warn_txt, subject="Discord guild channel name update failed")
def _ensure_user_idt_(platform: Platform, token: Union[int, str]) -> Optional[RootUserModel]: if token: result = RootUserManager.register_onplat(platform, token) if not result.success: MailSender.send_email_async( f"Platform: {platform} / Token: {token}<hr>" f"Outcome: {result.outcome}<hr>" f"Conn Outcome: {result.conn_outcome}<hr>" f"Identity Registration Result: {result.idt_reg_result.serialize()}", subject="User Registration Failed") return result.model else: return None
def get_root_data_uname( self, root_oid: ObjectId, channel_data: Union[ChannelModel, ChannelCollectionModel, None] = None, str_not_found: Optional[str] = None) -> Optional[namedtuple]: """ Get the name of the user with UID = `root_oid`. Returns `None` if no corresponding user data found. Returns the root oid if no On Platform Identity found. :return: namedtuple(user_oid, user_name) """ udata = self.find_one_casted({RootUserModel.Id.key: root_oid}, parse_cls=RootUserModel) # No user data found? Return None if not udata: return str_not_found if str_not_found else None UserNameEntry = namedtuple("UserNameEntry", ["user_id", "user_name"]) # Name has been set? if udata.config.name: return UserNameEntry(user_id=root_oid, user_name=udata.config.name) # On Platform Identity found? if udata.has_onplat_data: for onplatoid in udata.on_plat_oids: onplat_data: Optional[ OnPlatformUserModel] = self._mgr_onplat.get_onplat_by_oid( onplatoid) if onplat_data: uname = onplat_data.get_name(channel_data) if not uname: if str_not_found: uname = str_not_found else: uname = onplat_data.get_name_str(channel_data) return UserNameEntry(user_id=root_oid, user_name=uname) else: MailSender.send_email( f"OnPlatOid {onplatoid} was found to bind with the root data of {root_oid}, but no " f"corresponding On-Platform data found.") return str_not_found if str_not_found else None
async def on_guild_channel_create(self, channel: Union[TextChannel, VoiceChannel, CategoryChannel]): if channel.type == ChannelType.text: reg_result = ChannelManager.register(Platform.DISCORD, channel.id, channel_full_repr(channel)) if not reg_result.success: warn_txt = f"Guild Channel CREATED but the registration was failed. Channel token: {channel.id}" DISCORD.logger.warning(warn_txt) MailSender.send_email_async(warn_txt, subject="Discord guild channel creation failed") if not ChannelCollectionManager.register( Platform.DISCORD, channel.guild.id, reg_result.model.id, str(channel.guild)).success: warn_txt = f"Guild Channel CREATED but the collection registration was failed. " \ f"Channel token: {channel.id}" DISCORD.logger.warning(warn_txt) MailSender.send_email_async(warn_txt, subject="Discord channel collection creation failed")
def handle_error_main(e: MessageEventObject, ex: Exception) -> List[HandledMessageEvent]: if e.platform == Platform.LINE: handle_line_error(ex, "Error handling LINE message", e.raw, e.user_model.id) elif e.platform == Platform.DISCORD: handle_discord_error(ex) else: html = f"<h4>{e}</h4>\n" \ f"<hr>\n" \ f"<pre>Traceback:\n" \ f"{traceback.format_exc()}</pre>\n" MailSender.send_email_async(html, subject=f"Error on Message Processing ({e.platform})") if not isinstance(ex, LineBotApiError) or not ex.status_code == 500: return [ HandledMessageEventText(content=_( "An error occurred while handling message. An error report was sent for investigation."))]
def _check_on_prof_conn_( prof_conn: ChannelProfileConnectionModel, set_name_to_cache: bool, dict_onplat_oids: Dict[ObjectId, List[ObjectId]], dict_onplat_data: Dict[ObjectId, OnPlatformUserModel], dict_channel: Dict[ObjectId, ChannelModel]) -> bool: """Return ``True`` if marked unavailable. Otherwise ``False``.""" oid_user = prof_conn.user_oid list_onplat_oids = dict_onplat_oids.get(oid_user) if not list_onplat_oids: return False model_channel = dict_channel.get(prof_conn.channel_oid) if not model_channel: return False attempts = 0 attempts_allowed = len(list_onplat_oids) for oid_onplat in list_onplat_oids: model_onplat = dict_onplat_data.get(oid_onplat) if not model_onplat: MailSender.send_email_async( f"Missing OnPlatform data of data ID: {oid_onplat}<br>" f"Root User ID: {prof_conn.user_oid}", subject="Missing OnPlatform Data in Root User Model" ) continue n = model_onplat.get_name(model_channel) if n: if set_name_to_cache: set_uname_cache(model_onplat.id, n) break attempts += 1 if attempts >= attempts_allowed: ProfileManager.mark_unavailable_async(model_channel.id, oid_user) return attempts >= attempts_allowed
def __init__(self, holder: HandledMessageEventsHolder, config_class: Type[PlatformConfig]): self.config_class = config_class self.to_send: List[Tuple[MessageType, str]] = [] self.to_site: List[Tuple[str, str]] = [] self._sort_data_(holder, config_class) if len(self.to_send) > config_class.max_responses: self.to_site.append( (ToSiteReason.TOO_MANY_RESPONSES, self.to_send.pop(config_class.max_responses - 1)[1])) if len(self.to_site) > 0: rec_result = ExtraContentManager.record_extra_message( self.to_site, datetime.now(tz=timezone.utc).strftime("%m-%d %H:%M:%S UTC%z"), channel_oid=holder.channel_model.id) if rec_result.success: self.to_send.append(( MessageType.TEXT, _("{} content(s) needs to be viewed on the website because of the following reason(s):{}\n" "URL: {}").format( len(self.to_site), "".join([ f"\n - {reason}" for reason, content in self.to_site ]), rec_result.url))) else: MailSender.send_email_async( f"Failed to record extra content.<hr>Result: {rec_result.outcome}<hr>" f"To Send:<br>{str(self.to_send)}<br>To Site:<br>{str(self.to_site)}<hr>" f"Exception:<br><pre>" f"{traceback.format_exception(None, rec_result.exception, rec_result.exception.__traceback__)}" f"</pre>", subject="Failure on Recording Extra Content") self.to_send.append( _("Content(s) is supposed to be recorded to database but failed. " "An error report should be sent for investigation."))
def search_channel( keyword: str, root_oid: Optional[ObjectId] = None) -> List[ChannelData]: """The keyword could be - A piece of the message comes from a channel - The name of the channel""" checked_choid: Set[ObjectId] = set() ret: List[ChannelData] = [] missing = [] for ch_model in ChannelManager.get_channel_default_name( keyword, hide_private=True): checked_choid.add(ch_model.id) ret.append( ChannelData(ch_model, ch_model.get_channel_name(root_oid))) for channel_oid in MessageRecordStatisticsManager.get_messages_distinct_channel( keyword): if channel_oid not in checked_choid: checked_choid.add(channel_oid) ch_model = ChannelManager.get_channel_oid(channel_oid, hide_private=True) if ch_model: ret.append( ChannelData(ch_model, ch_model.get_channel_name(root_oid))) else: missing.append(channel_oid) if missing: MailSender.send_email_async( f"Channel OID have no corresponding channel data in message record.\n" f"{' | '.join([str(i) for i in missing])}", subject="Missing Channel Data") return ret
async def on_guild_channel_delete(self, channel: Union[TextChannel, VoiceChannel, CategoryChannel]): if channel.type == ChannelType.text \ and not ChannelManager.deregister(Platform.DISCORD, channel.id).is_success: warn_txt = f"Guild Channel DELETED but the deregistration was failed. Channel token: {channel.id}" DISCORD.logger.warning(warn_txt) MailSender.send_email_async(warn_txt, subject="Discord guild channel deletion failed")
def get_user_channel_profiles(self, root_uid: Optional[ObjectId], inside_only: bool = True, accessbible_only: bool = True) \ -> List[ChannelProfileListEntry]: if root_uid is None: return [] ret = [] not_found_channel = [] not_found_prof_oids_dict = {} channel_oid_list = [] profile_oid_list = [] prof_conns = [] for d in self._conn.get_user_channel_profiles(root_uid, inside_only): channel_oid_list.append(d.channel_oid) profile_oid_list.extend(d.profile_oids) prof_conns.append(d) channel_dict = ChannelManager.get_channel_dict(channel_oid_list, accessbible_only=False) profile_dict = self._prof.get_profile_dict(profile_oid_list) for prof_conn in prof_conns: not_found_prof_oids = [] # Get Channel Model cnl_oid = prof_conn.channel_oid cnl = channel_dict.get(cnl_oid) if cnl is None: not_found_channel.append(cnl_oid) continue elif accessbible_only and not cnl.bot_accessible: continue default_profile_oid = cnl.config.default_profile_oid # Get Profile Model prof = [] for p in prof_conn.profile_oids: pm = profile_dict.get(p) if pm: prof.append(pm) else: not_found_prof_oids.append(p) if len(not_found_prof_oids) > 0: # There's some profile not found in the database while ID is registered not_found_prof_oids_dict[cnl_oid] = not_found_prof_oids perms = self.get_permissions(prof) can_ced_profile = self.can_ced_profile(perms) ret.append( ChannelProfileListEntry( channel_data=cnl, channel_name=cnl.get_channel_name(root_uid), profiles=prof, starred=prof_conn.starred, default_profile_oid=default_profile_oid, can_ced_profile=can_ced_profile )) if len(not_found_channel) > 0 or len(not_found_prof_oids_dict) > 0: not_found_prof_oids_txt = "\n".join( [f'{cnl_id}: {" / ".join([str(oid) for oid in prof_ids])}' for cnl_id, prof_ids in not_found_prof_oids_dict.items()]) MailSender.send_email_async( f"User ID: <code>{root_uid}</code><hr>" f"Channel IDs not found in DB:<br>" f"<pre>{' & '.join([str(c) for c in not_found_channel])}</pre><hr>" f"Profile IDs not found in DB:<br>" f"<pre>{not_found_prof_oids_txt}</pre>", subject="Possible Data Corruption on Getting User Profile Connection" ) return sorted(ret, key=lambda item: item.channel_data.bot_accessible, reverse=True)
async def on_private_channel_create(self, channel: Union[DMChannel, GroupChannel]): if not ChannelManager.register(Platform.DISCORD, channel.id, str(channel)).success: warn_txt = f"Private Channel CREATED but the registration was failed. Channel token: {channel.id}" DISCORD.logger.warning(warn_txt) MailSender.send_email_async(warn_txt, subject="Discord private channel creation failed")
def add_auto_reply_module_execode( e: TextMessageEventObject, execode: str) -> List[HandledMessageEventText]: get_excde_result = ExecodeManager.get_execode_entry( execode, Execode.AR_ADD) if not get_excde_result.success: return [ HandledMessageEventText(content=_( "Failed to get the Execode data using the Execode `{}`. Code: `{}`" ).format(execode, get_excde_result.outcome)) ] excde_entry = get_excde_result.model if e.user_model.id != excde_entry.creator_oid: return [ HandledMessageEventText(content=_( "The ID of the creator of this Execode: `{}` does not match your ID: `{}`.\n" "Only the creator of this Execode can complete this action." ).format(excde_entry.creator_oid, e.user_model.id)) ] try: ar_model = AutoReplyModuleExecodeModel(**excde_entry.data, from_db=True).to_actual_model( e.channel_oid, excde_entry.creator_oid) except Exception as ex: MailSender.send_email_async( "Failed to construct an Auto-reply module using Execode.<br>" f"User ID: {e.user_model.id}<br>" f"Channel ID: {e.channel_oid}<br>" f"Execode: {excde_entry.execode}<br>" f"Exception: <pre>{traceback.format_exception(None, ex, ex.__traceback__)}</pre>", subject="Failed to construct AR module") return [ HandledMessageEventText(content=_( "Failed to create auto-reply module. An error report was sent for investigation." )) ] add_result = AutoReplyManager.add_conn_by_model(ar_model) if not add_result.success: MailSender.send_email_async( "Failed to register an Auto-reply module using model.\n" f"User ID: {e.user_model.id}\n" f"Channel ID: {e.channel_oid}\n" f"Execode: {excde_entry.execode}\n" f"Add result json: {add_result.serialize()}", subject="Failed to construct AR module") return [ HandledMessageEventText(content=_( "Failed to register the auto-reply module. Code: `{}`").format( add_result.outcome)) ] ExecodeManager.remove_execode(execode) return [ HandledMessageEventText(content=_("Auto-reply module registered.")) ]