Beispiel #1
0
    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)
Beispiel #2
0
    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
Beispiel #3
0
 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")
Beispiel #4
0
    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
Beispiel #5
0
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)
Beispiel #6
0
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")
Beispiel #7
0
    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
Beispiel #8
0
    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
Beispiel #9
0
    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")
Beispiel #10
0
    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
Beispiel #11
0
    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
Beispiel #12
0
    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")
Beispiel #13
0
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."))]
Beispiel #14
0
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
Beispiel #15
0
    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."))
Beispiel #16
0
    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
Beispiel #17
0
 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")
Beispiel #18
0
    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)
Beispiel #19
0
 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")
Beispiel #20
0
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."))
    ]