Beispiel #1
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")
Beispiel #2
0
 def create(account_info: dict, api_key: str) -> "Account":
     """
     Returns an instance based on given information
     """
     return Account(
         guid=account_info.get("id", ""),
         name=account_info.get("name", ""),
         world=enums.World(account_info.get("world")),
         api_key=api_key,
     )
Beispiel #3
0
    def __init__(
        self,
        session: Session,
        verify_all: bool,
        verify_linked_worlds: bool,
        verify_ts3: bool,
        verify_world: typing.Optional[int] = None,
    ):
        self.bot = Bot(session, is_cycle=True)
        self.session = session
        self.verify_all = verify_all
        self.verify_linked_worlds = verify_linked_worlds
        self.verify_ts3 = verify_ts3

        if verify_world:
            self.verify_world: typing.Optional[enums.World] = enums.World(verify_world)
        else:
            self.verify_world = None

        self.verify_begin = datetime.datetime.today()
Beispiel #4
0
    def update(self, session: Session) -> AccountUpdateDict:
        """
        Updates and saves an accounts's detail
        :raises InvalidKeyException
        :raises RateLimitException
        :raises RequestException
        """
        LOG.info("Updating account record for %s", self.name)

        result: AccountUpdateDict = AccountUpdateDict(transfer=[],
                                                      guilds=([], []))

        try:
            account_info = ts3bot.limit_fetch_api("account",
                                                  api_key=self.api_key)

            # TODO: Remove after GUID migration is done
            if not self.guid:
                self.guid = account_info.get("id", "UNKNOWN")

            # Update world
            new_world = cast(enums.World,
                             enums.World(account_info.get("world")))
            if new_world != self.world:
                result["transfer"] = [self.world, new_world]

                LOG.info(
                    "%s transferred from %s to %s",
                    self.name,
                    self.world.proper_name,
                    new_world.proper_name,
                )
                self.world = new_world

            # Update name, changes rarely
            new_name = account_info.get("name")
            if new_name != self.name:
                LOG.info("%s got renamed to %s", self.name, new_name)

            # Update guilds
            account_guilds = account_info.get("guilds", [])
            guids_joined: List[str] = []
            links_left: List[int] = []
            guilds_left: List[str] = []
            old_guilds: List[str] = []

            # Collect left guilds
            link_guild: LinkAccountGuild
            for link_guild in cast(AppenderQuery, self.guilds):
                old_guilds.append(link_guild.guild.guid)

                if link_guild.guild.guid not in account_guilds:
                    links_left.append(link_guild.id)
                    guilds_left.append(link_guild.guild.name)

            # Collect new guilds
            guild_guid: str
            for guild_guid in account_guilds:
                if guild_guid not in old_guilds:
                    guids_joined.append(guild_guid)

            # Process guild leaves
            if len(links_left) > 0:
                session.query(LinkAccountGuild).filter(
                    LinkAccountGuild.id.in_(links_left)).delete(
                        synchronize_session="fetch")

            # Process guild joins
            guilds_joined = []
            for guild_guid in guids_joined:
                guild = Guild.get_or_create(session, guild_guid)
                guilds_joined.append(guild.name)
                is_leader = guild.guid in account_info.get("guild_leader", [])
                LinkAccountGuild.get_or_create(session, self, guild, is_leader)

            # Process all current guilds for leader status
            for link_guild in self.guilds:
                # Skip new guilds
                if link_guild.guild.guid in guids_joined:
                    continue

                # Update leader status
                link_guild.is_leader = link_guild.guild.guid in account_info.get(
                    "guild_leader", [])

            result["guilds"] = (guilds_joined, guilds_left)

            if len(guilds_joined) > 0:
                LOG.info("%s joined new guilds: %s", self.name, guilds_joined)
            if len(guilds_left) > 0:
                LOG.info("%s left guilds: %s", self.name, guilds_left)

            self.last_check = datetime.datetime.now()
            self.is_valid = True
            if self.retries > 0:
                LOG.info("%s was valid again after %s retries.", self.name,
                         self.retries)
                self.retries = 0
        except ts3bot.InvalidKeyException:
            if self.retries >= 3:
                self.is_valid = False
                LOG.info("%s was invalid after 3 retries, marking as invalid.",
                         self.name)
                raise
            else:
                LOG.info(
                    "%s was invalid in this attempt, increasing counter to %s",
                    self.name,
                    self.retries + 1,
                )
                self.retries += 1
        finally:
            session.commit()

        return result
Beispiel #5
0
def migrate_database(session: Session, source_database: str):
    engine = create_engine(source_database, echo=False)
    source_session: Session = sessionmaker(bind=engine)()

    # Verify login for source database is valid
    try:
        source_session.execute("SELECT VERSION();")
    except OperationalError:
        logging.warning("Failed to connect to source database.")
        raise

    # Check if target DB is empty
    if session.query(Account).count() > 0 or session.query(
            Identity).count() > 0:
        raise Exception("Target database is not empty!")

    logging.info("--- Migrating database ---")

    known_identities = []
    num_accounts = 0
    rows = source_session.execute("""
        SELECT
            `name`,
            `world`,
            SUBSTRING_INDEX( GROUP_CONCAT( DISTINCT `apikey` ORDER BY `timestamp` DESC SEPARATOR ',' ), ',', 1 ),
            `tsuid`,
            `timestamp`,
            `last_check`,
            `guilds`
        FROM
            `users` 
        WHERE
            `ignored` = FALSE 
        GROUP BY
            `name`
        """)
    for idx, row in enumerate(rows):
        logging.info("%s: Copying %s", idx + 1, row[0])

        if row[3] in known_identities:
            identity = session.query(Identity).filter_by(
                guid=row[3]).one_or_none()
        else:
            identity = Identity(guid=row[3], created_at=row[4])
            known_identities.append(row[3])

        account = Account(
            name=row[0],
            world=enums.World(row[1]),
            api_key=row[2],
            last_check=row[5],
            created_at=row[4],
        )
        num_accounts += 1

        session.add(identity)
        session.add(account)
        session.add(
            LinkAccountIdentity(account=account,
                                identity=identity,
                                created_at=row[4]))

        # Create guilds
        _guilds = json.loads(row[6])
        for guild in _guilds:
            try:
                Guild.get_or_create(session, guild)
            except requests.RequestException:
                logging.warning("%s: Failed to create guild %s",
                                idx + 1,
                                guild,
                                exc_info=True)

        session.commit()

    # Verify accounts and update guild links
    update_accounts(session)

    # Migrate known guilds
    migrate_known_guilds(session)

    # Associate identity with currently selected guild
    fetch_identity_guilds(session)

    logging.info("--- Migration done ---")
Beispiel #6
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")