Esempio n. 1
0
def delete_auto_reply_module(e: TextMessageEventObject, keyword: str):
    outcome = AutoReplyManager.del_conn(keyword, e.channel_oid,
                                        e.user_model.id)

    if outcome.is_success:
        return [
            HandledMessageEventText(content=_(
                "Auto-Reply Module deleted.\nKeyword: {}").format(keyword))
        ]
    elif outcome == WriteOutcome.X_INSUFFICIENT_PERMISSION:
        return [
            HandledMessageEventText(content=_(
                "Insufficient Permission to delete the auto-reply module."))
        ]
    elif outcome == WriteOutcome.X_NOT_FOUND:
        return [
            HandledMessageEventText(content=_(
                "Active auto-reply module of the keyword `{}` not found.").
                                    format(keyword))
        ]
    else:
        return [
            HandledMessageEventText(
                content=_("Failed to delete the Auto-Reply module.\n"
                          "Code: {}\n"
                          "Visit {} to see the code explanation.").format(
                              outcome.code_str,
                              f"{HostUrl}{reverse('page.doc.code.insert')}"))
        ]
Esempio n. 2
0
def add_timer(e: TextMessageEventObject, keyword: str, title: str, dt: str, countup: str) \
        -> List[HandledMessageEventText]:
    # Parse datetime string
    dt = parse_to_dt(dt, tzinfo_=e.user_model.config.tzinfo)
    if not dt:
        return [
            HandledMessageEventText(content=_(
                "Failed to parse the string of datetime. (`{}`)").format(dt))
        ]

    # Check `countup` flag
    ctup = str_to_bool(countup)

    if ctup == StrBoolResult.UNKNOWN:
        return [
            HandledMessageEventText(content=_(
                "Unknown flag to indicate if the timer will countup once the time is up. (`{}`)"
            ).format(countup))
        ]

    outcome = TimerManager.add_new_timer(e.channel_oid, keyword, title, dt,
                                         ctup.to_bool())

    if outcome.is_success:
        return [HandledMessageEventText(content=_("Timer added."))]
    else:
        return [
            HandledMessageEventText(content=_(
                "Failed to add timer. Outcome code: `{}`").format(outcome))
        ]
Esempio n. 3
0
def remote_control_status(e: TextMessageEventObject):
    current = RemoteControlManager.get_current(e.user_model.id,
                                               e.channel_model_source.id,
                                               update_expiry=False)

    if current:
        cnl = current.target_channel
        if not cnl:
            RemoteControlManager.deactivate(e.user_model.id,
                                            e.channel_model_source.id)
            return [
                HandledMessageEventText(content=_(
                    "Target channel data not found. Terminating remote control.\n"
                    "Target Channel ID: `{}`").format(
                        current.target_channel_oid))
            ]
        else:
            return [
                HandledMessageEventText(
                    content=_("Remote control is activated.\n"
                              "Target Channel ID: `{}`\n"
                              "Target Channel Platform & Name: *{} / {}*\n"
                              "Will be deactivated at `{}`.").format(
                                  cnl.id, cnl.platform.key,
                                  cnl.get_channel_name(e.user_model.id),
                                  current.expiry_str))
            ]
    else:
        return [
            HandledMessageEventText(
                content=_("Remote control is not activated."))
        ]
Esempio n. 4
0
def profile_delete(e: TextMessageEventObject, name: str):
    # --- Check profile name
    prof = ProfileManager.get_profile_name(name)
    if not prof:
        return [
            HandledMessageEventText(content=_(
                "Profile with the name `{}` not found.").format(name))
        ]

    # --- Check permission

    profiles = ProfileManager.get_user_profiles(e.channel_model.id,
                                                e.user_model.id)
    user_permissions = ProfileManager.get_permissions(profiles)

    if ProfilePermission.PRF_CED not in user_permissions:
        return [
            HandledMessageEventText(
                content=_("Insufficient permission to delete profile."))
        ]

    deleted = ProfileManager.delete_profile(e.channel_oid, prof.id,
                                            e.user_model.id)
    if deleted:
        return [HandledMessageEventText(content=_("Profile deleted."))]
    else:
        return [
            HandledMessageEventText(content=_("Failed to delete the profile."))
        ]
Esempio n. 5
0
def random_multi(e: TextMessageEventObject, elements: str, times: int):
    ret = []

    if times > Bot.RandomChoiceCountLimit:
        return [HandledMessageEventText(content=pick_overlimit_txt)]

    option_list = OptionList(elements)

    if len(option_list) > Bot.RandomChoiceOptionLimit:
        return [HandledMessageEventText(content=option_overlimit_txt)]

    results = sorted(option_list.pick_multi(times).items(),
                     key=lambda kv: kv[1],
                     reverse=True)

    if option_list.elem_ignored:
        ret.append(
            _("*There are element(s) ignored possibly because the options are misformatted.*"
              ))

    ret.append("\n".join([
        f"{option}: {count} ({count / times:.2%})" for option, count in results
    ]))

    return [HandledMessageEventText(content=ctnt) for ctnt in ret]
Esempio n. 6
0
def profile_list(e: TextMessageEventObject):
    result = ProfileHelper.get_channel_profiles(e.channel_oid)

    if not result:
        return [
            HandledMessageEventText(content=_("No profile in this channel."))
        ]

    return [HandledMessageEventText(content=_output_profile_txt_(result))]
Esempio n. 7
0
def _output_detach_outcome_(outcome: OperationOutcome):
    if outcome.is_success:
        return [HandledMessageEventText(content=_("Profile detached."))]
    else:
        return [
            HandledMessageEventText(
                content=_("Failed to detach the profile.\nError: `{}` - {}"
                          ).format(outcome.code_str, outcome.description))
        ]
Esempio n. 8
0
def replace_newline(e: TextMessageEventObject, original_url: str) -> List[HandledMessageEventText]:
    result = ShortUrlDataManager.create_record(original_url, e.user_model.id)
    if result.success:
        return [HandledMessageEventText(content=_("URL Shortened.")),
                HandledMessageEventText(content=result.model.short_url)]
    else:
        return [
            HandledMessageEventText(content=_("Failed to shorten the URL. Code: {}").format(result.outcome.code_str))
        ]
Esempio n. 9
0
def profile_query(e: TextMessageEventObject, keyword: str):
    result = ProfileHelper.get_channel_profiles(e.channel_oid, keyword)

    if not result:
        return [
            HandledMessageEventText(content=_(
                "No profile with the keyword `{}` was found.").format(keyword))
        ]

    return [HandledMessageEventText(content=_output_profile_txt_(result))]
Esempio n. 10
0
    def _handle_fn_(self,
                    e: TextMessageEventObject,
                    cmd_fn: callable,
                    args: List[str] = None):
        ret: List[HandledMessageEventText]

        if not e.remote_activated and not cmd_fn.scope.is_in_scope(
                e.channel_type):
            # Out of scope

            ret = [
                HandledMessageEventText(
                    content=CommandSpecialResponse.out_of_scope(
                        e.channel_type, cmd_fn.scope.available_ctypes))
            ]
        elif not cmd_fn.can_be_called(e.channel_oid):
            # On cooldown

            ret = [
                HandledMessageEventText(content=_(
                    "Command is in cooldown. Call the command again after {:.2f} secs."
                ).format(cmd_fn.cd_sec_left(e.channel_oid)))
            ]
        else:
            BotFeatureUsageDataManager.record_usage(cmd_fn.cmd_feature,
                                                    e.channel_oid,
                                                    e.user_model.id)
            try:
                ret = arg_type_ensure(fn=cmd_fn.fn,
                                      converter=NonSafeDataTypeConverter)(
                                          e, *args)
                cmd_fn.record_called(e.channel_oid)
            except TypeCastingFailed as e:
                ret = [
                    HandledMessageEventText(
                        content=_("Incorrect type of data: `{}`.\n"
                                  "Expected type: `{}` / Actual type: `{}`").
                        format(e.data, type_translation(e.expected_type),
                               type_translation(e.actual_type)))
                ]

        if isinstance(ret, str):
            ret = [HandledMessageEventText(content=ret)]
        elif isinstance(ret, list):
            ret = [
                HandledMessageEventText(
                    content=txt) if isinstance(txt, str) else txt
                for txt in ret
            ]

        return ret
Esempio n. 11
0
def list_usable_auto_reply_module(e: TextMessageEventObject):
    conn_list = AutoReplyManager.get_conn_list(e.channel_oid)

    if not conn_list.empty:
        ctnt = _("Usable Keywords ({}):").format(len(conn_list)) + \
            "\n\n" + \
            "<div class=\"ar-content\">" + "".join(get_list_of_keyword_html(conn_list)) + "</div>"

        return [HandledMessageEventText(content=ctnt, force_extra=True)]
    else:
        return [
            HandledMessageEventText(
                content=_("No usable auto-reply module in this channel."))
        ]
Esempio n. 12
0
def auto_reply_ranking(e: TextMessageEventObject):
    ret = [_("Auto-Reply TOP{} ranking").format(Bot.AutoReply.RankingMaxCount)]

    # Attach module stats section
    module_stats = list(
        AutoReplyManager.get_module_count_stats(e.channel_oid,
                                                Bot.AutoReply.RankingMaxCount))
    if module_stats:
        ret.append("")
        ret.append(_("# Module usage ranking"))

        for rank, module in module_stats:
            reduced_kw = str_reduce_length(
                str(module.keyword).replace("\n", "\\n"),
                Bot.AutoReply.RankingMaxContentLength)
            reduced_rs1 = str_reduce_length(
                str(module.responses[0]).replace("\n", "\\n"),
                Bot.AutoReply.RankingMaxContentLength)

            ret.append(f"#{rank} - {'' if module.active else '[X] '}"
                       f"{reduced_kw} → {reduced_rs1} ({module.called_count})")

    # Attach unique keyword stats section
    unique_kw = AutoReplyManager.get_unique_keyword_count_stats(
        e.channel_oid, Bot.AutoReply.RankingMaxCount)
    if unique_kw.data:
        ret.append("")
        ret.append(_("# Unique keyword ranking"))

        for data in unique_kw.data:
            ret.append(f"#{data.rank} - {data.word_str} ({data.count_usage})")

    # Attach external url section
    ret.append("")
    ret.append(
        _("For more ranking data, please visit {}{}.").format(
            HostUrl,
            reverse("page.ar.ranking.channel",
                    kwargs={"channel_oid": e.channel_oid})))

    if len(ret) > 1:
        return [
            HandledMessageEventText(content="\n".join([str(s) for s in ret]))
        ]
    else:
        return [
            HandledMessageEventText(
                content=_("No ranking data available for now."))
        ]
Esempio n. 13
0
def random_once(e: TextMessageEventObject, elements: str):
    option_list = OptionList(elements)

    if len(option_list) > Bot.RandomChoiceOptionLimit:
        return [HandledMessageEventText(content=option_overlimit_txt)]

    ctnt = [_("Option Picked: {}").format(option_list.pick_one())]

    if option_list.elem_ignored:
        ctnt.append("")
        ctnt.append(
            _("*There are element(s) ignored possibly because the options are misformatted.*"
              ))

    return [HandledMessageEventText(content="\n".join([str(c) for c in ctnt]))]
Esempio n. 14
0
def auto_reply_module_detail(e: TextMessageEventObject, keyword: str):
    conn_list = AutoReplyManager.get_conn_list(e.channel_oid, keyword)

    if not conn_list.empty:
        result = ExtraContentManager.record_content(
            ExtraContentType.AUTO_REPLY_SEARCH,
            [module.id for module in conn_list],
            _("Auto-Reply module with keyword {} in {}").format(
                keyword, e.channel_model.get_channel_name(e.user_model.id)),
            channel_oid=e.channel_oid)

        if result.success:
            content = _("Visit {} to see the result.").format(result.url)
        else:
            content = _("Failed to record the result. ({})").format(
                result.outcome.code_str)
    else:
        content = _(
            "Cannot find any auto-reply module including the substring `{}` in their keyword."
        ).format(keyword)

    content += "\n"
    content += _(
        "Visit {}{} for more completed module searching functionality. Login required."
    ).format(
        HostUrl,
        reverse("page.ar.search.channel",
                kwargs={"channel_oid": e.channel_oid}))

    return [HandledMessageEventText(content=content)]
Esempio n. 15
0
def list_usable_auto_reply_module_keyword(e: TextMessageEventObject,
                                          keyword: str):
    conn_list = AutoReplyManager.get_conn_list(e.channel_oid, keyword)

    if not conn_list.empty:
        ctnt = _("Usable Keywords ({}):").format(len(conn_list)) \
            + "\n\n" \
            + "<div class=\"ar-content\">" + "".join(get_list_of_keyword_html(conn_list)) + "</div>"

        return [HandledMessageEventText(content=ctnt, force_extra=True)]
    else:
        return [
            HandledMessageEventText(content=_(
                "Cannot find any auto-reply module including the substring `{}` in their keyword."
            ).format(keyword))
        ]
Esempio n. 16
0
def add_auto_reply_module(e: TextMessageEventObject, keyword: str,
                          response: str) -> List[HandledMessageEventText]:
    ret = []
    kw_type = AutoReplyContentType.determine(keyword)
    # Issue #124
    if not AutoReplyValidator.is_valid_content(
            kw_type, keyword, online_check=True):
        kw_type = AutoReplyContentType.TEXT
        ret.append(
            HandledMessageEventText(content=_(
                "The type of the keyword has been automatically set to `TEXT` "
                "because the validation was failed.")))

    resp_type = AutoReplyContentType.determine(response)
    # Issue #124
    if not AutoReplyValidator.is_valid_content(
            resp_type, response, online_check=True):
        resp_type = AutoReplyContentType.TEXT
        ret.append(
            HandledMessageEventText(content=_(
                "The type of the response has been automatically set to `TEXT` "
                "because the validation was failed.")))

    add_result = AutoReplyManager.add_conn_complete(
        keyword, kw_type,
        [AutoReplyContentModel(Content=response, ContentType=resp_type)],
        e.user_model.id, e.channel_oid, Bot.AutoReply.DefaultPinned,
        Bot.AutoReply.DefaultPrivate, Bot.AutoReply.DefaultTags,
        Bot.AutoReply.DefaultCooldownSecs)

    if add_result.outcome.is_success:
        ret.append(
            HandledMessageEventText(content=_(
                "Auto-Reply module successfully registered.\n"
                "Keyword Type: {}\n"
                "Response Type: {}").format(kw_type.key, resp_type.key),
                                    bypass_multiline_check=True))
    else:
        ret.append(
            HandledMessageEventText(
                content=_("Failed to register the Auto-Reply module.\n"
                          "Code: `{}`\n"
                          "Visit {} to see the code explanation.").format(
                              add_result.outcome.code_str,
                              f"{HostUrl}{reverse('page.doc.code.insert')}")))

    return ret
Esempio n. 17
0
def remote_control_activate(e: TextMessageEventObject,
                            target_channel: ObjectId):
    entry = RemoteControlManager.activate(
        e.user_model.id, e.channel_model_source.id, target_channel,
        e.user_model.config.get_locale_code())

    if entry.target_channel:
        return [
            HandledMessageEventText(content=_("Remote control activated."))
        ]
    else:
        RemoteControlManager.deactivate(e.user_model.id,
                                        e.channel_model_source.id)
        return [
            HandledMessageEventText(content=_(
                "Target channel not found. Failed to activate remote control.")
                                    )
        ]
Esempio n. 18
0
def list_all_timer(e: TextMessageEventObject) \
        -> List[HandledMessageEventText]:
    tmrs = TimerManager.list_all_timer(e.channel_oid)

    if tmrs.has_data:
        return [
            HandledMessageEventText(
                content=_("All Timers in this Channel:\n") +
                tmrs.to_string(e.user_model))
        ]
Esempio n. 19
0
def remote_control_deactivate(e: TextMessageEventObject):
    if RemoteControlManager.deactivate(e.user_model.id,
                                       e.channel_model_source.id):
        return [
            HandledMessageEventText(content=_("Remote control deactivated."))
        ]
    else:
        if RemoteControlManager.get_current(e.user_model.id,
                                            e.channel_model_source.id,
                                            update_expiry=False):
            return [
                HandledMessageEventText(
                    content=_("Remote control failed to delete."))
            ]
        else:
            return [
                HandledMessageEventText(
                    content=_("Remote control not activated."))
            ]
Esempio n. 20
0
def _link_recent_msgs_(e: TextMessageEventObject, limit: int):
    qd = QueryDict("", mutable=True)
    qd.update({"limit": limit})

    return HandledMessageEventText(content=_(
        "Visit {}{}?{} to see the most recent {} messages. Login required."
    ).format(
        HostUrl,
        reverse("info.channel.recent.message",
                kwargs={"channel_oid": e.channel_oid}), qd.urlencode(), limit))
Esempio n. 21
0
def help_text(e: TextMessageEventObject):
    # For some reason, translation needs to be casted to `str` explicitly, or:
    #   - LINE cannot respond anything
    #   - Discord will not change the language
    return [
        HandledMessageEventText(content=str(
            _("LINE: http://rnnx.cc/Line\n"
              "Discord: http://rnnx.cc/Discord\n"
              "Website: http://rnnx.cc/Website")),
                                bypass_multiline_check=True)
    ]
Esempio n. 22
0
def process_timer_get(e: TextMessageEventObject) -> List[HandledMessageEvent]:
    tmrs = TimerManager.get_timers(e.channel_oid, e.content)

    if tmrs.has_data:
        BotFeatureUsageDataManager.record_usage_async(BotFeature.TXT_TMR_GET,
                                                      e.channel_oid,
                                                      e.user_model.id)

        return [HandledMessageEventText(content=tmrs.to_string(e.user_model))]

    return []
Esempio n. 23
0
def _content_recent_msgs_(e: TextMessageEventObject, limit: int):
    limit = min(Bot.RecentActivity.DefaultLimitCountDirect, limit)

    ctnt = []

    recent_messages = MessageStatsDataProcessor.get_recent_messages(
        e.channel_model, limit, e.user_model.config.tzinfo)

    for data in recent_messages.data:
        ctnt.append(f"{data.timestamp} - {data.model.message_content}")

    return HandledMessageEventText(content="\n".join(ctnt))
Esempio n. 24
0
def process_imgur_upload(
        e: ImageMessageEventObject) -> List[HandledMessageEvent]:
    if e.channel_type == ChannelType.PRIVATE_TEXT:
        BotFeatureUsageDataManager.record_usage(BotFeature.IMG_IMGUR_UPLOAD,
                                                e.channel_oid, e.user_model.id)

        exec_result = exec_timing_result(ImgurClient.upload_image,
                                         e.content.content,
                                         e.content.content_type)
        upload_result = exec_result.return_

        return [
            HandledMessageEventText(content=_(
                "Uploaded to imgur.com\n\n"
                "Time consumed: *{:.2f} ms*\n"
                "Link to the image below.").format(exec_result.execution_ms),
                                    bypass_multiline_check=True),
            HandledMessageEventText(content=upload_result.link,
                                    bypass_multiline_check=True)
        ]
    else:
        return []
Esempio n. 25
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 []
Esempio n. 26
0
def delete_timer(e: TextMessageEventObject, keyword: str, index: int):
    tmrs = TimerManager.get_timers(e.channel_oid, keyword)

    if not tmrs.has_data:
        return [
            HandledMessageEventText(content=_(
                "There are no timer(s) using the keyword {}.").format(keyword))
        ]

    tmr = tmrs.get_item(index)
    if tmr:
        del_ok = TimerManager.del_timer(tmr.id)
        if del_ok:
            return [HandledMessageEventText(content=_("Timer deleted."))]
        else:
            return [
                HandledMessageEventText(
                    content=_("Failed to delete the timer."))
            ]
    else:
        return [
            HandledMessageEventText(
                content=_("Timer not found using the given index."))
        ]
Esempio n. 27
0
def process_display_info(
        e: LineStickerMessageEventObject) -> List[HandledMessageEvent]:
    if e.channel_type == ChannelType.PRIVATE_TEXT:
        sticker_info = e.content

        BotFeatureUsageDataManager.record_usage(BotFeature.STK_LINE_GET_INFO,
                                                e.channel_oid, e.user_model.id)

        return [
            HandledMessageEventText(
                content=_("Sticker ID: `{}`\nPackage ID: `{}`").format(
                    sticker_info.sticker_id, sticker_info.package_id),
                bypass_multiline_check=True)
        ]
    else:
        return []
Esempio n. 28
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."))]
Esempio n. 29
0
def check_channel_member(e: TextMessageEventObject):
    ret = []

    user_oids = ProfileManager.get_channel_member_oids(e.channel_oid,
                                                       available_only=True)
    user_name_dict = IdentitySearcher.get_batch_user_name(user_oids,
                                                          e.channel_model,
                                                          on_not_found="(N/A)")

    for user_oid, user_name in sorted(user_name_dict.items(),
                                      key=lambda x: x[1]):
        ret.append(f"<li>`{user_oid}` - {user_name}</li>")

    ret = '\n'.join(ret)
    return [
        HandledMessageEventText(content=f"<ul>{ret}</ul>", force_extra=True)
    ]
Esempio n. 30
0
def handle_no_user_token(e: MessageEventObject) -> List[HandledMessageEvent]:
    if e.channel_oid not in _sent_cache_:
        _sent_cache_[e.channel_oid] = True

        return [
            HandledMessageEventText(content=_(
                "Bot Features cannot be used as the Bot cannot get your user token.\n"
                "If you are using LINE, please ensure that you have added Jelly Bot as friend.\n"
                "Contact the developer via the website ({}) if this issue persists.\n"
                "\n"
                "This message will be sent only once in {} seconds per channel when someone without user "
                "token attempt to use any bot features.").format(
                    HostUrl, System.NoUserTokenNotificationSeconds,
                    System.NoUserTokenNotificationSeconds),
                                    bypass_multiline_check=True)
        ]

    return []