Example #1
0
def handle(bot: Bot, event: events.TextMessage, match: typing.Match):
    if event.uid not in Config.whitelist_admin:
        return

    try:
        json = fetch_api("account", api_key=match.group(1))
        account = models.Account.get_by_api_info(
            bot.session, guid=json.get("id"), name=json.get("name")
        )

        # Account does not exist
        if not account:
            logging.info("User was not registered.")
            bot.send_message(event.id, "account_unknown", account=json.get("name"))
            return

        # Get previous identity
        previous_identity: typing.Optional[
            models.LinkAccountIdentity
        ] = account.valid_identities.one_or_none()

        # Remove previous links
        account.invalidate(bot.session)

        if previous_identity:
            # Get cldbid and sync groups
            try:
                cldbid = bot.exec_(
                    "clientgetdbidfromuid", cluid=previous_identity.identity.guid
                )[0]["cldbid"]

                result = sync_groups(bot, cldbid, account, remove_all=True)

                logging.info(
                    "%s (%s) marked previous links of %s as ignored",
                    event.name,
                    event.uid,
                    account.name,
                )

                bot.send_message(
                    event.id,
                    "groups_revoked",
                    amount="1",
                    groups=result["removed"],
                )
            except ts3.TS3Error:
                # User might not exist in the db
                logging.info("Failed to remove groups from user", exc_info=True)

        else:
            bot.send_message(event.id, "groups_revoked", amount="0", groups=[])
    except InvalidKeyException:
        logging.info("This seems to be an invalid API key.")
        bot.send_message(event.id, "invalid_token")
    except ApiErrBadData:
        bot.send_message(event.id, "error_api")
Example #2
0
def handle(bot: Bot, event: events.TextMessage, _match: typing.Match):
    if event.uid not in Config.whitelist_admin:
        return

    i18n.set("locale", "en")
    message = i18n.t("available_commands")
    for _ in bot.commands:
        if _.USAGE != USAGE:
            message += "\n - {}".format(_.USAGE)

    bot.send_message(event.id, message, is_translation=False)
def handle(bot: Bot, event: events.TextMessage, match: Match) -> None:
    cldbid = bot.exec_("clientgetdbidfromuid", cluid=event.uid)[0]["cldbid"]
    user_groups = bot.exec_("servergroupsbyclientid", cldbid=cldbid)
    allowed = False

    if event.uid in Config.whitelist_admin:
        allowed = True
    else:
        for group in user_groups:
            if group["name"] in Config.whitelist_group_list:
                allowed = True
                break

    # User doesn't have any whitelisted groups
    if not allowed:
        return

    groups = bot.exec_("servergrouplist")
    group = None
    search_group = match.group(1).strip()
    for _ in groups:
        if _["type"] != "1":  # Regular type, neither template nor query
            continue

        if _["name"] == search_group:
            group = _
            break

    # Group not found
    if group is None:
        bot.send_message(event.id, "list_not_found")
        return

    members = bot.exec_("servergroupclientlist", "names", sgid=group["sgid"])

    members = sorted(members,
                     key=lambda x: cast(str, _.get("client_nickname", "")))

    if len(members) >= 50:
        bot.send_message(event.id, "list_50_users")
        return

    text_groups = [""]
    index = 0
    for member in members:
        member_text = (
            f"\n- [URL=client://0/{member['client_unique_identifier']}]"
            f"{member['client_nickname']}[/URL]")
        if len(text_groups[index]) + len(bytes(member_text, "utf-8")) >= 1024:
            index += 1
            text_groups.append("")

        text_groups[index] += member_text

    bot.send_message(event.id,
                     "list_users",
                     amount=len(members),
                     group=group["name"])
    for _ in text_groups:
        bot.send_message(event.id, _, is_translation=False)
Example #4
0
def handle(bot: Bot, event: events.TextMessage, match: typing.Match):
    try:
        account = fetch_api("account", api_key=match.group(1))
        server = enums.World(account.get("world"))

        guilds = (
            bot.session.query(models.Guild)
            .filter(models.Guild.guid.in_(account.get("guilds", [])))
            .filter(models.Guild.group_id.isnot(None))
            .options(load_only(models.Guild.name))
        )

        bot.send_message(
            event.id,
            "info_world",
            user=account.get("name"),
            world=server.proper_name,
            guilds=", ".join([_.name for _ in guilds]),
        )
    except InvalidKeyException:
        logging.info("This seems to be an invalid API key.")
        bot.send_message(event.id, "invalid_token")
    except (requests.RequestException, RateLimitException, ApiErrBadData):
        logging.exception("Error during API call")
        bot.send_message(event.id, "error_api")
Example #5
0
def handle(bot: Bot, event: events.TextMessage, match: typing.Match):
    if event.uid not in Config.whitelist_admin:
        return

    # Grab client_uid
    try:
        user = bot.exec_("clientgetnamefromdbid", cldbid=match.group(1))
        client_uid = user[0]["cluid"]
    except ts3.query.TS3QueryError:
        bot.send_message(event.id, "user_not_found")
        return

    try:
        json = fetch_api("account", api_key=match.group(2))
        account = models.Account.get_or_create(bot.session, json,
                                               match.group(2))
        identity: models.Identity = models.Identity.get_or_create(
            bot.session, client_uid)

        # Save api key in account
        account.api_key = match.group(2)
        account.is_valid = True
        bot.session.commit()

        transfer_registration(
            bot,
            account,
            event,
            is_admin=True,
            target_identity=identity,
            target_dbid=match.group(1),
        )
    except InvalidKeyException:
        logging.info("This seems to be an invalid API key.")
        bot.send_message(event.id, "invalid_token")
        return
    except (RateLimitException, RequestException, ApiErrBadData):
        bot.send_message(event.id, "error_api")
Example #6
0
def handle(bot: Bot, event: events.TextMessage, match: typing.Match):
    cldbid = bot.exec_("clientgetdbidfromuid", cluid=event.uid)[0]["cldbid"]

    # Grab user's account
    account = models.Account.get_by_identity(bot.session, event.uid)

    if not account or not account.is_valid:
        bot.send_message(event.id, "missing_token")
        return

    on_join_hours_timeout = Config.getfloat("verify", "on_join_hours")

    # Saved account is older than x hours or has no guilds
    if (
        timedelta_hours(datetime.datetime.today() - account.last_check)
        >= on_join_hours_timeout
        or account.guilds.count() == 0
    ):
        bot.send_message(event.id, "account_updating")

        try:
            account.update(bot.session)

            # Sync groups in case the user has left a guild or similar changes
            sync_groups(bot, cldbid, account)
        except InvalidKeyException:
            # Invalidate link
            account.invalidate(bot.session)
            sync_groups(bot, cldbid, account, remove_all=True)

            logging.info("Revoked user's permissions.")
            bot.send_message(event.id, "invalid_token_admin")
            return
        except (requests.RequestException, RateLimitException, ApiErrBadData):
            logging.exception("Error during API call")
            bot.send_message(event.id, "error_api")

    # User requested guild removal
    if match.group(1) and match.group(1).lower() == "remove":
        # Get active guilds
        has_active_guilds: int = (
            account.guilds.join(models.Guild)
            .filter(models.Guild.group_id.isnot(None))
            .filter(models.LinkAccountGuild.is_active.is_(True))
            .count()
        )

        # There are no active guilds, no need to remove anything
        if not has_active_guilds:
            bot.send_message(event.id, "guild_already_removed")
            return

        # Remove guilds
        account.guilds.filter(models.LinkAccountGuild.is_active.is_(True)).update(
            {"is_active": False}
        )
        bot.session.commit()

        # Sync groups
        changes = sync_groups(bot, cldbid, account)
        if len(changes["removed"]) > 0:
            bot.send_message(event.id, "guild_removed")
        else:
            bot.send_message(event.id, "guild_error")

        return

    available_guilds = account.guilds.join(models.Guild).filter(
        models.Guild.group_id.isnot(None)
    )

    # No guild specified
    if not match.group(1):
        available_guilds = available_guilds.all()
        if len(available_guilds) > 0:
            bot.send_message(
                event.id,
                "guild_selection",
                guilds="\n- ".join([_.guild.tag for _ in available_guilds]),
            )
        else:
            bot.send_message(event.id, "guild_unknown")
    else:
        guild = match.group(1).lower()

        selected_guild: typing.Optional[
            models.LinkAccountGuild
        ] = available_guilds.filter(models.Guild.tag.ilike(guild)).one_or_none()

        # Guild not found or user not in guild
        if not selected_guild:
            bot.send_message(
                event.id, "guild_invalid_selection", timeout=on_join_hours_timeout
            )
            return

        # Toggle guild
        if selected_guild.is_active:
            selected_guild.is_active = False
        else:
            selected_guild.is_active = True

            # Remove other guilds if only one is allowed
            if not Config.getboolean("guild", "allow_multiple_guilds"):
                account.guilds.filter(
                    models.LinkAccountGuild.id != selected_guild.id
                ).update({"is_active": False})

        bot.session.commit()

        # Sync groups
        changes = sync_groups(bot, cldbid, account)
        if selected_guild.is_active and len(changes["added"]):
            bot.send_message(event.id, "guild_set", guild=selected_guild.guild.name)
        elif not selected_guild.is_active and len(changes["removed"]):
            bot.send_message(
                event.id, "guild_removed_one", guild=selected_guild.guild.name
            )
        else:
            bot.send_message(event.id, "guild_error")
Example #7
0
def handle(bot: Bot, event: events.TextMessage, match: Match) -> None:
    sheet_channel_id = Config.get("teamspeak", "sheet_channel_id")
    if sheet_channel_id == 0:
        return

    current_state: CommandingDict = {
        "EBG": [],
        "Red": [],
        "Green": [],
        "Blue": []
    }

    if match.group(1) == "help" and event.uid in Config.whitelist_admin:
        bot.send_message(
            event.id,
            "!sheet <ebg,red,green,blue,remove,reset>\n!sheet set <ebg,red,green,blue,remove> <name>",
            is_translation=False,
        )
        return

    if match.group(1) == "reset" and event.uid in Config.whitelist_admin:
        pass  # Don't load the current file, just use the defaults
    elif match.group(1) == "set" and event.uid in Config.whitelist_admin:
        # Force-set an entry
        _match = re.match(
            "!sheet set (ebg|red|green|blue|r|g|b|remove) (.*)",
            event.message.strip(),
        )
        if not _match:
            bot.send_message(event.id, "invalid_input")
            return

        if STATE_FILE.exists():
            current_state = cast(CommandingDict,
                                 json.loads(STATE_FILE.read_text()))

        if _match.group(1) == "remove":
            current_state = _remove_lead(current_state,
                                         name_field=_match.group(2))
        else:
            # Add new entry
            new_state = _add_lead(
                current_state,
                wvw_map=_match.group(1),
                note="",
                name=_match.group(2),
            )
            if not new_state:
                bot.send_message(event.id, "sheet_map_full")
                return
            current_state = new_state

    elif match.group(1) in [
            "ebg", "red", "green", "blue", "r", "g", "b", "remove"
    ]:
        if STATE_FILE.exists():
            current_state = json.loads(STATE_FILE.read_text())

        if match.group(1) == "remove":
            current_state = _remove_lead(current_state, uid=event.uid)
        else:
            new_state = _add_lead(
                current_state,
                wvw_map=match.group(1),
                note=match.group(2),
                uid=event.uid,
                name=event.name,
            )
            if not new_state:
                bot.send_message(event.id, "sheet_map_full")
                return
            current_state = new_state
    else:
        bot.send_message(event.id, "invalid_input")
        return

    # Build new table
    desc = "[table][tr][td] | Map | [/td][td] | Lead | [/td][td] | Note | [/td][td] | Date | [/td][/tr]"
    for _map, leads in cast(IterType, current_state.items()):
        if len(leads) == 0:
            desc += f"[tr][td]{_map}[/td][td]-[/td][td]-[/td][td]-[/td][/tr]"
            continue

        for lead in leads:
            desc += (
                f"[tr][td]{_map}[/td][td]{lead['lead']}[/td][td]{_encode(lead['note'])}[/td]"
                f"[td]{lead['date']}[/td][/tr]")

    desc += (
        f"[/table]\n[hr]Last change: {_tidy_date()}\n\n"
        f"Link to bot: [URL=client://0/{bot.own_uid}]{Config.get('bot_login', 'nickname')}[/URL]\n"  # Add link to self
        "Usage:\n"
        "- !sheet red/green/blue (note)\t—\tRegister your lead with an optional note (20 characters).\n"
        "- !sheet remove\t—\tRemove the lead")
    bot.exec_("channeledit", cid=sheet_channel_id, channel_description=desc)
    bot.send_message(event.id, "sheet_changed")

    STATE_FILE.write_text(json.dumps(current_state))
Example #8
0
def handle(bot: Bot, event: events.TextMessage, match: Match) -> None:
    if event.uid not in Config.whitelist_admin:
        return

    # Grab cluid
    try:
        if match.group(1).isdigit():  # DB id
            user = bot.exec_("clientgetnamefromdbid", cldbid=match.group(1))
            cldbid = match.group(1)
            cluid = user[0]["cluid"]
        else:
            user = bot.exec_("clientgetnamefromuid", cluid=match.group(1))
            cldbid = user[0]["cldbid"]
            cluid = match.group(1)
    except ts3.query.TS3QueryError:
        bot.send_message(event.id, "user_not_found")
        return

    # Grab user's account
    account = models.Account.get_by_identity(bot.session, cluid)

    if not account:
        bot.send_message(event.id, "verify_no_token")
        return

    try:
        bot.send_message(event.id, "account_updating")
        result = account.update(bot.session)
        if result["transfer"]:
            old_world: enums.World = result["transfer"][0]
            new_world: enums.World = result["transfer"][1]

            bot.send_message(
                event.id,
                "verify_transferred",
                old_world=old_world.proper_name,
                new_world=new_world.proper_name,
            )
        guilds_joined, guilds_left = result["guilds"]

        if len(guilds_joined) > 0 or len(guilds_left) > 0:
            bot.send_message(
                event.id,
                "verify_guild_change",
                guilds_joined=guilds_joined,
                guilds_left=guilds_left,
            )

        # Sync user's groups
        sync_groups(bot, cldbid, account)

        bot.send_message(
            event.id,
            "verify_valid_world",
            user=account.name,
            world=account.world.proper_name,
        )
    except InvalidKeyException:
        bot.send_message(event.id, "invalid_token")

        # Invalidate link
        account.invalidate(bot.session)
        changes = sync_groups(bot, cldbid, account)

        bot.send_message(event.id, "groups_removed", groups=str(changes["removed"]))
    except (requests.RequestException, ApiErrBadData):
        bot.send_message(event.id, "error_api")
Example #9
0
def handle(bot: Bot, event: events.TextMessage, match: Match) -> None:
    key = match.group(1)

    # Check with ArenaNet's API
    try:
        account_info = fetch_api("account", api_key=key)

        # Check if one of the guilds is in the alliance
        is_part_of_alliance = (bot.session.query(models.Guild).filter(
            models.Guild.guid.in_(account_info.get("guilds", []))).filter(
                models.Guild.is_part_of_alliance.is_(True)).count() > 0)

        # One of the guilds is in the alliance
        if is_part_of_alliance:
            account: models.Account = models.Account.get_or_create(
                bot.session, account_info, key)
            identity: models.Identity = models.Identity.get_or_create(
                bot.session, event.uid)

            # Check if account is registered to anyone
            linked_identity: Optional[
                models.
                LinkAccountIdentity] = account.valid_identities.one_or_none()

            # Account is already linked
            if linked_identity:
                # Account is linked to another guid
                if linked_identity.identity.guid != event.uid:
                    try:
                        # Get user's DB id
                        cldbid: str = bot.exec_("clientgetdbidfromuid",
                                                cluid=event.uid)[0]["cldbid"]
                    except ts3.TS3Error:
                        LOG.error("Failed to get user's dbid", exc_info=True)
                        bot.send_message(event.id, "error_critical")
                        return

                    force_key_name = f"ts3bot-{cldbid}"

                    # Fetch token info
                    token_info = fetch_api("tokeninfo", api_key=key)

                    # Override registration, same as !register
                    if token_info.get("name", "") == force_key_name:
                        ts3bot.transfer_registration(bot, account, event)

                        LOG.info(
                            "%s (%s) transferred permissions of %s onto themselves.",
                            event.name,
                            event.uid,
                            account_info.get("name"),
                        )
                        return

                    LOG.warning(
                        "%s (%s) tried to use an already registered API key/account. (%s)",
                        event.name,
                        event.uid,
                        account_info.get("name"),
                    )
                    bot.send_message(event.id,
                                     "token_in_use",
                                     api_name=force_key_name)
                else:  # Account is linked to current guid
                    LOG.info(
                        "User %s (%s) tried to register a second time for whatever reason using %s",
                        event.name,
                        event.uid,
                        account_info.get("name", "Unknown account"),
                    )

                    # Save new API key
                    if account.api_key != key:
                        account.api_key = key
                        account.is_valid = True
                        bot.session.commit()
                        bot.send_message(event.id, "registration_exists")
                        return

                    # Same API key supplied, last check was over 12 minutes ago
                    if (ts3bot.timedelta_hours(datetime.datetime.today() -
                                               account.last_check) >= 0.2):
                        # Update saved account info if same API key was posted again with a reasonable time frame
                        account.update(bot.session)
                        try:
                            # Get user's DB id
                            cldbid = bot.exec_("clientgetdbidfromuid",
                                               cluid=event.uid)[0]["cldbid"]

                            # Sync groups
                            ts3bot.sync_groups(bot, cldbid, account)
                            bot.send_message(event.id,
                                             "registration_details_updated")
                        except ts3.TS3Error:
                            # User might not exist in the db
                            LOG.error("Failed to sync user", exc_info=True)
                    else:
                        # Too early
                        bot.send_message(event.id, "registration_too_early")

            else:
                # Otherwise account is not yet linked and can be used

                # Save API key
                account.api_key = key
                account.is_valid = True
                bot.session.commit()

                # Get user's DB id
                cldbid = bot.exec_("clientgetdbidfromuid",
                                   cluid=event.uid)[0]["cldbid"]

                # Unlink previous account from identity
                current_account = models.Account.get_by_identity(
                    bot.session, event.uid)
                if current_account:
                    LOG.info("Delinking %s from cldbid:%s", current_account,
                             cldbid)
                    current_account.invalidate(bot.session)

                # Register link between models
                bot.session.add(
                    models.LinkAccountIdentity(account=account,
                                               identity=identity))
                bot.session.commit()

                # Add all known guilds to user if enabled
                if Config.getboolean(
                        "guild", "assign_on_register") and Config.getboolean(
                            "guild", "allow_multiple_guilds"):
                    cast(AppenderQuery, account.guilds).filter(
                        models.LinkAccountGuild.id.in_(
                            bot.session.query(models.LinkAccountGuild.id).join(
                                models.Guild).filter(
                                    models.Guild.group_id.isnot(
                                        None)).subquery())).update(
                                            {"is_active": True},
                                            synchronize_session="fetch")
                    bot.session.commit()

                # Sync groups
                sync_groups(bot, cldbid, account)

                LOG.info(
                    "Assigned alliance permissions to %s (%s) using %s",
                    event.name,
                    event.uid,
                    account_info.get("name", "Unknown account"),
                )

                # Was registered with other account previously
                if current_account:
                    bot.send_message(event.id,
                                     "registration_update",
                                     account=account.name)
                else:
                    bot.send_message(event.id, "welcome_registered")

                    # Tell user about !guild if it's enabled
                    if Config.getboolean("commands", "guild"):
                        if Config.getboolean(
                                "guild",
                                "assign_on_register") and Config.getboolean(
                                    "guild", "allow_multiple_guilds"):
                            bot.send_message(event.id, "welcome_registered_3")
                        else:
                            bot.send_message(event.id, "welcome_registered_2")

        else:
            bot.send_message(
                event.id,
                "invalid_world",
                world=enums.World(account_info.get("world")).proper_name,
            )

    except InvalidKeyException:
        LOG.info("This seems to be an invalid API key.")
        bot.send_message(event.id, "invalid_token_retry")
    except (RateLimitException, RequestException, ApiErrBadData):
        bot.send_message(event.id, "error_api")
Example #10
0
def transfer_registration(
    bot: ts3_bot.Bot,
    account: models.Account,
    event: events.TextMessage,
    is_admin: bool = False,
    target_identity: typing.Optional[models.Identity] = None,
    target_dbid: typing.Optional[str] = None,
):
    """
    Transfers a registration and server/guild groups to the sender of the event or the target_guid
    :param bot: The current bot instance
    :param account: The account that should be re-registered for the target user
    :param event: The sender of the text message, usually the one who gets permissions
    :param is_admin: Whether the sender is an admin
    :param target_identity: To override the user who gets the permissions
    :param target_dbid: The target's database id, usually sourced from the event
    :return:
    """

    # Get identity from event if necessary
    if not target_identity:
        target_identity: models.Identity = models.Identity.get_or_create(
            bot.session, event.uid)

    # Get database id if necessary
    if not target_dbid:
        try:
            target_dbid: str = bot.exec_("clientgetdbidfromuid",
                                         cluid=event.uid)[0]["cldbid"]
        except ts3.TS3Error:
            # User might not exist in the db
            logging.exception("Failed to get database id from event's user")
            bot.send_message(event.id, "error_critical")
            return

    # Get current guild groups to save them for later use
    guild_groups = account.guild_groups()

    # Get previous identity
    previous_identity: typing.Optional[
        models.LinkAccountIdentity] = account.valid_identities.one_or_none()

    # Remove previous identities, also removes guild groups
    account.invalidate(bot.session)

    # Account is currently registered, sync groups with old identity
    if previous_identity:
        # Get cldbid and sync groups
        try:
            cldbid = bot.exec_(
                "clientgetdbidfromuid",
                cluid=previous_identity.identity.guid)[0]["cldbid"]

            result = sync_groups(bot, cldbid, account, remove_all=True)

            logging.info(
                "Removed previous links of %s as ignored during transfer to %s",
                account.name,
                target_identity.guid,
            )

            if is_admin:
                bot.send_message(event.id,
                                 "groups_revoked",
                                 amount="1",
                                 groups=result["removed"])
        except ts3.TS3Error:
            # User might not exist in the db
            logging.info("Failed to remove groups from user", exc_info=True)

    # Invalidate target identity's link, if it exists
    other_account = models.Account.get_by_identity(bot.session,
                                                   target_identity.guid)
    if other_account:
        other_account.invalidate(bot.session)

    # Transfer roles to new identity
    bot.session.add(
        models.LinkAccountIdentity(account=account, identity=target_identity))

    # Add guild group
    if guild_groups:
        bot.session.query(models.LinkAccountGuild).filter(
            models.LinkAccountGuild.id.in_([g.id for g in guild_groups
                                            ])).update({"is_active": True})

    bot.session.commit()

    # Sync group
    sync_groups(bot, target_dbid, account)

    logging.info("Transferred groups of %s to cldbid:%s", account.name,
                 target_dbid)

    bot.send_message(
        event.id,
        "registration_transferred",
        account=account.name,
    )