Пример #1
0
    def get_member_info(channel_model: ChannelModel) -> List[MemberInfoEntry]:
        ret = []

        prof_conns = ProfileManager.get_channel_members(channel_model.id,
                                                        available_only=True)
        user_oids = [mdl.user_oid for mdl in prof_conns]

        user_name_dict = IdentitySearcher.get_batch_user_name(
            user_oids, channel_model)
        last_message_oids = MessageRecordStatisticsManager.get_user_last_message_ts(
            channel_model.id, user_oids)

        for prof_conn in prof_conns:
            user_oid = prof_conn.user_oid
            user_name = user_name_dict.get(user_oid) or str(user_oid)
            first_joined = localtime(prof_conn.id.generation_time)
            last_message_at = last_message_oids.get(user_oid)
            if last_message_at:
                last_message_at = localtime(last_message_at)

            ret.append(
                MemberInfoEntry(user_oid=user_oid,
                                user_name=user_name,
                                first_joined=first_joined,
                                last_message_at=last_message_at))

        return ret
Пример #2
0
def handle_message_main(e: MessageEventObject) -> HandledMessageEventsHolder:
    try:
        if e.user_model:
            # Ensure User existence in channel
            ProfileManager.register_new_default_async(e.channel_model.id,
                                                      e.user_model.id)

            # Translation activation
            activate(e.user_model.config.language)
        else:
            # User model could be `None` if user token is not provided. This happens on LINE.
            # Notify users when they attempted to use any features related of the Jelly Bot
            from .spec.no_utoken import handle_no_user_token

            return HandledMessageEventsHolder(e.channel_model,
                                              handle_no_user_token(e))

        # Main handle process
        event_type = type(e)
        if event_type in fn_box:
            ret = HandledMessageEventsHolder(e.channel_model,
                                             fn_box[event_type](e))
        else:
            logger.logger.info(
                f"Message handle object not handled. Raw: {e.raw}")
            ret = HandledMessageEventsHolder(e.channel_model)

        # Translation deactivation
        deactivate()

        # Record message for stats / User model could be `None` on LINE
        if e.user_model:
            MessageRecordStatisticsManager.record_message_async(
                e.channel_model.id, e.user_model.id, e.message_type, e.content,
                e.constructed_time)

        return ret
    except Exception as ex:
        from .spec.error import handle_error_main

        return HandledMessageEventsHolder(e.channel_model,
                                          handle_error_main(e, ex))
Пример #3
0
 def get_user_channel_messages(
         channel_data: ChannelModel, *,
         hours_within: Optional[int] = None, start: Optional[datetime] = None, end: Optional[datetime] = None,
         available_only: bool = True) \
         -> UserMessageStats:
     return MessageStatsDataProcessor._get_user_msg_stats_(
         MessageRecordStatisticsManager.get_user_messages_by_category(
             channel_data.id,
             hours_within=hours_within,
             start=start,
             end=end), channel_data, available_only)
Пример #4
0
def process_timer_notification(
        e: TextMessageEventObject) -> List[HandledMessageEvent]:
    within_secs = min(
        TimerManager.get_notify_within_secs(
            MessageRecordStatisticsManager.get_message_frequency(
                e.channel_oid, Bot.Timer.MessageFrequencyRangeMin)),
        Bot.Timer.MaxNotifyRangeSeconds)

    tmr_ntf = TimerManager.get_notify(e.channel_oid, within_secs)
    tmr_up = TimerManager.get_time_up(e.channel_oid)

    ret = []

    if tmr_up:
        ret.append(_("**{} timer(s) have timed up!**").format(len(tmr_up)))
        ret.append("")

        for tmr in tmr_up:
            ret.append(
                _("- {event} has timed up! (at {time})").format(
                    event=tmr.title,
                    time=localtime(tmr.target_time,
                                   e.user_model.config.tzinfo)))

    if tmr_ntf:
        if ret:
            ret.append("-------------")

        now = now_utc_aware()
        ret.append(
            _("**{} timer(s) will time up in less than {:.0f} minutes!**").
            format(len(tmr_ntf), within_secs / 60))
        ret.append("")

        for tmr in tmr_ntf:
            ret.append(
                _("- {event} will time up after [{diff}]! (at {time})").format(
                    event=tmr.title,
                    diff=t_delta_str(tmr.get_target_time_diff(now)),
                    time=localtime(tmr.target_time,
                                   e.user_model.config.tzinfo)))

    if ret and ret[-1] == "":
        ret = ret[:-1]

    if ret:
        return [HandledMessageEventText(content="\n".join(ret))]
    else:
        return []
Пример #5
0
    def get_recent_messages(
            channel_data: ChannelModel, limit: Optional[int] = None, tz: Optional[tzinfo] = None) \
            -> HandledMessageRecords:
        ret = []
        msgs = list(
            MessageRecordStatisticsManager.get_recent_messages(
                channel_data.id, limit))
        uids = {msg.user_root_oid for msg in msgs}
        uids_handled = IdentitySearcher.get_batch_user_name(uids, channel_data)

        for msg in msgs:
            ret.append(
                HandledMessageRecordEntry(
                    model=msg,
                    user_name=uids_handled[msg.user_root_oid],
                    tz=tz))

        return HandledMessageRecords(data=ret)
Пример #6
0
    def _get_user_msg_ranking_(channel_oids: Union[List[ObjectId], ObjectId],
                               root_oid: ObjectId,
                               *,
                               hours_within: Optional[int] = None,
                               start: Optional[datetime] = None,
                               end: Optional[datetime] = None):
        data = sorted(
            MessageRecordStatisticsManager.get_user_messages_by_category(
                channel_oids, hours_within=hours_within, start=start,
                end=end).data.items(),
            key=lambda _: _[1].total,
            reverse=True)
        for idx, item in enumerate(data, start=1):
            oid, entry = item
            if oid == root_oid:
                return UserMessageRanking(rank=idx, total=len(data))

        return UserMessageRanking(rank=-1, total=len(data))
Пример #7
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
Пример #8
0
    def get_user_channel_message_count_interval(
            channel_data: ChannelModel,
            *,
            hours_within: Optional[int] = None,
            start: Optional[datetime] = None,
            end: Optional[datetime] = None,
            period_count: Optional[int] = None,
            tz: Optional[tzinfo] = None,
            available_only: bool = True) -> UserMessageCountIntervalResult:
        data = MessageRecordStatisticsManager.get_user_messages_total_count(
            channel_data.id,
            hours_within=hours_within,
            start=start,
            end=end,
            period_count=period_count,
            tzinfo_=tz)

        uname_dict = IdentitySearcher.get_batch_user_name(
            ProfileManager.get_channel_member_oids(
                channel_data.id, available_only=available_only), channel_data)

        return UserMessageCountIntervalResult(original_result=data,
                                              uname_dict=uname_dict,
                                              available_only=available_only)
Пример #9
0
def _msg_before_time_(channel_oid, tzinfo, *, hours_within=None, start=None, end=None):
    return KEY_MSG_BEFORE_TIME, MessageRecordStatisticsManager.message_count_before_time(
        channel_oid, tzinfo_=tzinfo, hours_within=hours_within, start=start, end=end)
Пример #10
0
def _msg_mean_(channel_oid, tzinfo, *, hours_within=None, start=None, end=None):
    return KEY_MSG_MEAN, MessageRecordStatisticsManager.mean_message_count(
        channel_oid, tzinfo_=tzinfo, hours_within=hours_within, start=start, end=end, max_mean_days=14)
Пример #11
0
def _msg_daily_(channel_oid, tzinfo, *, hours_within=None, start=None, end=None):
    return KEY_MSG_DAILY, MessageRecordStatisticsManager.daily_message_count(
        channel_oid, tzinfo_=tzinfo, hours_within=hours_within, start=start, end=end)
Пример #12
0
def _msg_intv_flow_(channel_oid, tzinfo, *, hours_within=None, start=None, end=None):
    return KEY_MSG_INTV_FLOW, MessageRecordStatisticsManager.hourly_interval_message_count(
        channel_oid, tzinfo_=tzinfo, hours_within=hours_within, start=start, end=end)
Пример #13
0
    def get_user_daily_message(
            channel_data: ChannelModel, *,
            hours_within: Optional[int] = None, start: Optional[datetime] = None, end: Optional[datetime] = None,
            tz: Optional[tzinfo] = None, available_only: Optional[bool] = True) \
            -> UserDailyMessageResult:
        available_dict = {
            d.user_oid: d.available
            for d in ProfileManager.get_channel_members(
                channel_data.id, available_only=available_only)
        }
        uname_dict = IdentitySearcher.get_batch_user_name(list(
            available_dict.keys()),
                                                          channel_data,
                                                          on_not_found="(N/A)")
        result = MessageRecordStatisticsManager.member_daily_message_count(
            channel_data.id,
            hours_within=hours_within,
            start=start,
            end=end,
            tzinfo_=tz)

        # Array for storing active member count
        actv_mbr = []

        # Create empty arrays for the result
        # Each entry corresponds to daily message count in a specific date
        # Ordered by the date
        proc_count = {}
        proc_rank = {}
        for oid in uname_dict.keys():
            proc_count[oid] = []
            proc_rank[oid] = []

        for date in result.dates:
            # Get the data of the corresponding date
            data_of_date = result.data_count[date]

            # Storing active member count
            actv_mbr.append(len(data_of_date))

            # Get and sort the daily message count
            collated_count = {
                oid: data_of_date.get(oid, 0)
                for oid in proc_count.keys()
            }
            sorted_count = list(
                sorted(collated_count.items(),
                       key=lambda x: x[1],
                       reverse=True))

            # Insert daily count and rank
            for rank, entry in enumerate_ranking(
                    sorted_count, is_tie=lambda cur, prv: cur[1] == prv[1]):
                uid, count = entry
                proc_count[uid].append(count)
                proc_rank[uid].append(rank)

        entries = [
            UserDailyMessageEntry(member_name=name,
                                  count_list=proc_count[oid],
                                  rank_list=proc_rank[oid],
                                  available=available_dict[oid])
            for oid, name in uname_dict.items()
        ]

        return UserDailyMessageResult(label_dates=result.dates,
                                      entries=entries,
                                      active_members=actv_mbr)