def __init__(self, air):
        ToontownRPCHandlerBase.__init__(self, air)

        self.shardStatus = ShardStatusReceiver(air)
class ToontownRPCHandler(ToontownRPCHandlerBase):
    def __init__(self, air):
        ToontownRPCHandlerBase.__init__(self, air)

        self.shardStatus = ShardStatusReceiver(air)

    # --- TESTS ---

    @rpcmethod(accessLevel=COMMUNITY_MANAGER)
    def rpc_ping(self, data):
        """
        Summary:
            Responds with the provided [data]. This method exists for testing
            purposes.

        Parameters:
            [any data] = The data to be given back in response.

        Example response: 'pong'
        """
        return data

    # --- GENERAL ---

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_queryObject(self, doId):
        """
        Summary:
            Responds with the values of all database fields associated with the
            provided [doId].

        Parameters:
            [int doId] = The ID of the object to query database fields on.

        Example response:
            On success: ['DistributedObject', {'fieldName': ('arg1', ...), ...}]
            On failure: [None, None]
        """
        result = []
        unblocked = threading2.Event()


        def callback(dclass, fields):
            if dclass is not None:
                dclass = dclass.getName()
            result.extend([dclass, fields])
            unblocked.set()


        self.air.dbInterface.queryObject(self.air.dbId, doId, callback)

        # Block until the callback is executed:
        unblocked.wait()

        return result

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_updateObject(self, doId, dclassName, newFields, oldFields=None):
        """
        Summary:
            Update the field(s) of the object associated with the provided
            [doId]. If <oldFields> is provided, then this method will fail if
            the object's current fields don't match.

        Parameters:
            [int doId] = The ID of the object whose fields are to be updated.
            [str dclassName] = The name of the object's DClass.
            [dict newFields] = The new field values.
            <dict oldFields> = The old field values to assert.

        Example response:
            On success: True
            On failure: False
        """
        # Ensure that the provided DClass exists:
        if dclassName not in self.air.dclassesByName:
            dclassName += 'UD'
            if dclassName not in self.air.dclassesByName:
                return False

        dclass = self.air.dclassesByName[dclassName]

        if oldFields is None:
            self.air.dbInterface.updateObject(
                self.air.dbId, doId, dclass, newFields)
            return True

        result = [True]
        unblocked = threading2.Event()


        def callback(fields):
            if fields is not None:
                result[0] = False
            unblocked.set()


        self.air.dbInterface.updateObject(
            self.air.dbId, doId, dclass, newFields, oldFields=oldFields,
            callback=callback)

        # Block until the callback is executed:
        unblocked.wait()

        return result[0]

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_setField(self, doId, dclassName, fieldName, args=[]):
        """
        Summary:
            Set the value of the field named [fieldName] on the suggested
            object.

        Parameters:
            [int doId] = The ID of the object whose field is being modified.
            [str dclassName] = The name of the object's DClass.
            [str fieldName] = The name of the field to be modified.
            [list args] = The new value for the field.

        Example response:
            On success: True
            On failure: False
        """
        # Ensure that the provided DClass exists:
        if dclassName not in self.air.dclassesByName:
            dclassName += 'UD'
            if dclassName not in self.air.dclassesByName:
                return False

        dclass = self.air.dclassesByName[dclassName]

        datagram = dclass.aiFormatUpdate(
            fieldName, doId, doId, self.air.ourChannel, args)
        self.air.send(datagram)

        return True

    # --- MESSAGES ---

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageChannel(self, channel, message):
        """
        Summary:
            Broadcasts a [message] to any client whose Client Agent is
            subscribed to the provided [channel].

        Parameters:
            [int channel] = The channel to direct the message to.
            [str message] = The message to broadcast.
        """
        dclass = self.air.dclassesByName['ClientServicesManagerUD']
        datagram = dclass.aiFormatUpdate(
            'systemMessage', OtpDoGlobals.OTP_DO_ID_CLIENT_SERVICES_MANAGER,
            channel, 1000000, [message])
        self.air.send(datagram)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageAll(self, message):
        """
        Summary: Broadcasts a [message] to all clients.

        Parameters:
            [str message] = The message to broadcast.
        """
        self.rpc_messageChannel(10, message)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageShard(self, shardId, message):
        """
        Summary:
            Broadcasts a [message] to all clients under the provided [shardId].

        Parameters:
            [int shardId] = The ID of the shard to direct the message to.
            [str message] = The message to broadcast.
        """
        # Get the ID of the ToontownDistrict object:
        districtId = shardId + 1

        # Use it to get the uber zone's channel:
        channel = (districtId<<32) | 2

        self.rpc_messageChannel(channel, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageStaff(self, message):
        """
        Summary:
            Broadcasts a [message] to any client whose access level is higher
            than that of a standard user.

        Parameters:
            [str message] = The message to broadcast.
        """
        self.rpc_messageChannel(OtpDoGlobals.OTP_STAFF_CHANNEL, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageUser(self, userId, message):
        """
        Summary:
            Sends a [message] to the client associated with the provided
            [userId].

        Parameters:
            [int/str userId] = The ID of the user to send the message to.
            [str message] = The message to send.
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            self.rpc_messageAccount(accountId, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageAccount(self, accountId, message):
        """
        Summary:
            Sends a [message] to the client associated with the provided
            [accountId].

        Parameters:
            [int accountId] = The ID of the account to send the message to.
            [str message] = The message to send.
        """
        channel = accountId + (1003L<<32)
        self.rpc_messageChannel(channel, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageAvatar(self, avId, message):
        """
        Summary:
            Sends a [message] to the client associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to send the message to.
            [str message] = The message to send.
        """
        channel = avId + (1001L<<32)
        self.rpc_messageChannel(channel, message)

    # --- KICKS ---

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickChannel(self, channel, code, reason):
        """
        Summary:
            Kicks any client whose Client Agent is subscribed to the provided
            [channel].

        Parameters:
            [int channel] = The channel to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        datagram = PyDatagram()
        datagram.addServerHeader(channel, self.air.ourChannel, CLIENTAGENT_EJECT)
        datagram.addUint16(code)
        datagram.addString(reason)
        self.air.send(datagram)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickAll(self, code, reason):
        """
        Summary: Kicks all clients.

        Parameters:
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        self.rpc_kickChannel(10, code, reason)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickShard(self, shardId, code, reason):
        """
        Summary: Kicks all clients under the provided [shardId].

        Parameters:
            [int shardId] = The ID of the shard to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        # Get the ID of the ToontownDistrict object:
        districtId = shardId + 1

        # Use it to get the uber zone's channel:
        channel = (districtId<<32) | 2

        self.rpc_kickChannel(channel, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickUser(self, userId, code, reason):
        """
        Summary: Kicks the client associated with the provided [userId].

        Parameters:
            [int/str userId] = The ID of the user to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            self.rpc_kickAccount(accountId, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickAccount(self, accountId, code, reason):
        """
        Summary: Kicks the client associated with the provided [accountId].

        Parameters:
            [int accountId] = The ID of the account to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        channel = accountId + (1003L<<32)
        self.rpc_kickChannel(channel, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickAvatar(self, avId, code, reason):
        """
        Summary: Kicks the client associated with the provided [avId].

        Parameters:
            [int avId] = The ID of the avatar to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        channel = avId + (1001L<<32)
        self.rpc_kickChannel(channel, code, reason)

    # --- BANS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banUser(self, userId, duration, reason):
        """
        Summary:
            Bans the user associated with the provided [userId] for the
            specified [duration].

        Parameters:
            [int/str userId] = The ID of the user to ban.
            [int duration] = The ban's duration in hours. If this is 0 or less,
                the user will be permanently banned.
            [str reason] = A short description of why this user is being
                banned. This can be one of the following values: 'hacking',
                'language', 'other'.

        Example response:
            On success: True
            On failure: False
        """
        if reason not in ('hacking', 'language', 'other'):
            return False
        self.air.writeServerEvent('ban', userId, duration, reason)
        if duration > 0:
            now = datetime.date.today()
            release = str(now + datetime.timedelta(hours=duration))
        else:
            release = '0000-00-00'  # Permanent ban.
        executeHttpRequest('accounts/ban/', Id=userId, Release=release,
                           Reason=reason)
        self.rpc_kickUser(userId, 152, reason)
        return True

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banAccount(self, accountId, duration, reason):
        """
        Summary:
            Bans the user associated with the provided [accountId] for the
            specified [duration].

        Parameters:
            [int accountId] = The ID of the account associated with the user to
                ban.
            [int duration] = The ban's duration in hours. If this is 0 or less,
                the user will be permanently banned.
            [str reason] = A short description of why this user is being
                banned. This can be one of the following values: 'hacking',
                'language', 'other'.

        Example response:
            On success: True
            On failure: False
        """
        userId = self.rpc_getAccountUserId(accountId)
        return self.rpc_banUser(userId, duration, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banAvatar(self, avId, duration, reason):
        """
        Summary:
            Bans the user associated with the provided [avId] for the specified
            [duration].

        Parameters:
            [int/str avId] = The ID of the avatar associated with the user to
                be banned.
            [int duration] = The ban's duration in hours. If this is 0 or less,
                the user will be permanently banned.
            [str reason] = A short description of why this user is being
                banned. This can be one of the following values: 'hacking',
                'language', 'other'.

        Example response:
            On success: True
            On failure: False
        """
        userId = self.rpc_getAvatarUserId(avId)
        return self.rpc_banUser(userId, duration, reason)

    # --- USERS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserAccountId(self, userId):
        """
        Summary:
            Responds with the ID of the account associated with the provided
            [userId].

        Parameters:
            [int/str userId] = The ID of the user to query the account ID on.

        Example response:
            On success: 100000000
            On failure: None
        """
        if str(userId) in self.air.csm.accountDB.dbm:
            return int(self.air.csm.accountDB.dbm[str(userId)])

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserAvatars(self, userId):
        """
        Summary:
            Responds with a list of avatar IDs associated with the provided
            [userId].

        Parameters:
            [int/str userId] = The ID of the user to query the avatars on.

        Example response:
            On success: [0, 100000001, 0, 0, 0, 0]
            On failure: None
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            return self.rpc_getAccountAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserDeletedAvatars(self, userId):
        """
        Summary:
            Responds with a list of deleted avatar IDs associated with the
            provided [userId], along with the time at which each avatar was
            deleted.

        Parameters:
            [int/str userId] = The ID of the user to query the deleted avatars
                on.

        Example response:
            On success: [[100000001, 1409665000], ...]
            On failure: None
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            return self.rpc_getAccountDeletedAvatars(accountId)

    # --- ACCOUNTS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountUserId(self, accountId):
        """
        Summary:
            Responds with the ID of the user associated with the provided
            [accountId].

        Parameters:
            [int accountId] = The ID of the account to query the user ID on.

        Example response:
            On success: 1
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_ID']

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountAvatars(self, accountId):
        """
        Summary:
            Responds with a list of avatar IDs associated with the provided
            [accountId].

        Parameters:
            [int accountId] = The ID of the account to query the avatar IDs on.

        Example response:
            On success: [0, 100000001, 0, 0, 0, 0]
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_AV_SET']

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountDeletedAvatars(self, accountId):
        """
        Summary:
            Responds with a list of deleted avatar IDs associated with the
            provided [accountId], along with the time at which each avatar was
            deleted.

        Parameters:
            [int accountId] = The ID of the account to query the deleted
                avatars on.

        Example response:
            On success: [[100000001, 1409665000], ...]
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_AV_SET_DEL']

    # --- AVATARS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarUserId(self, avId):
        """
        Summary:
            Responds with the ID of the user associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to query the user ID on.

        Example response:
            On success: 1
            On failure: None
        """
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountUserId(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarAccountId(self, avId):
        """
        Summary:
            Responds with the ID of the account associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to query the account ID on.

        Example response:
            On success: 100000000
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(avId)
        if dclassName == 'DistributedToon':
            return fields['setDISLid'][0]

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarAvatars(self, avId):
        """
        Summary:
            Responds with a list of avatar IDs associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to query the avatar IDs on.

        Example response:
            On success: [0, 100000001, 0, 0, 0, 0]
            On failure: None
        """
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarDeletedAvatars(self, avId):
        """
        Summary:
            Responds with a list of deleted avatar IDs associated with the
            provided [avId], along with the time at which each avatar was
            deleted.

        Parameters:
            [int avId] = The ID of the avatar to query the deleted avatars on.

        Example response:
            On success: [[100000001, 1409665000], ...]
            On failure: None
        """
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountDeletedAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarDetails(self, avId):
        """
        Summary:
            Responds with basic details on the avatar associated with the
            provided [avId].

        Parameters:
            [int avId] = The ID of the avatar to query basic details on.

        Example response:
            On success:
                {
                   'name': 'Toon Name',
                   'species': 'cat',
                   'head-color': 'Red',
                   'max-hp': 15,
                   'online': True
                }
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(avId)
        if dclassName == 'DistributedToon':
            result = {}

            result['name'] = fields['setName'][0]

            dna = ToonDNA.ToonDNA()
            dna.makeFromNetString(fields['setDNAString'][0])
            result['species'] = ToonDNA.getSpeciesName(dna.head)

            result['head-color'] = TTLocalizer.NumToColor[dna.headColor]
            result['max-hp'] = fields['setMaxHp'][0]
            result['online'] = (avId in self.air.friendsManager.onlineToons)

            return result

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_findAvatarsByName(self, needle):
        """
        Summary:
            Responds with the IDs of each avatar whose name matches, or
            contains the provided [needle].

        Parameters:
            [str needle] = The string to filter avatars by name with. This is
                case insensitive.

        Example response: [100000001, ...]
        """
        if not config.GetBool('want-mongo-client', False):
            return []
        if not needle:
            return []
        self.air.mongodb.astron.objects.ensure_index('fields.setName')
        exp = re.compile('.*%s.*' % needle, re.IGNORECASE)
        result = self.air.mongodb.astron.objects.find({'fields.setName._0': exp})
        return [avatar['_id'] for avatar in result]

    # --- SHARDS ---

    @rpcmethod(accessLevel=USER)
    def rpc_listShards(self):
        """
        Summary:
            Responds with the current status of each shard that has ever been
            created in the lifetime of the UberDOG.

        Example response:
            {
               401000000: {
                  'name': 'District Name'
                  'available': True,
                  'created': 1409665000,
                  'population': 150,
                  'invasion': {
                     'type': 'Flunky',
                     'flags': 0,
                     'remaining': 1000,
                     'total': 1000,
                     'start': 1409665000
                  }
               },
               ...
            }
        """
        return self.shardStatus.getShards()

    # --- INVASIONS ---

    @rpcmethod(accessLevel=ADMINISTRATOR)
    def rpc_startInvasion(self, shardId, suitDeptIndex=None, suitTypeIndex=None,
                          flags=[0,0,0,0,0], type=INVASION_TYPE_NORMAL):
        """
        Summary:
            Starts an invasion under the provided [shardId] with the specified
            configuration.

        Parameters:
            [int shardId] = The ID of the shard to start the invasion in.
            <int/NoneType suitDeptIndex> = The invading Cog's department index.
            <int/NoneType suitTypeIndex> = The invading Cog's type index.
            <int flags> = Extra invasion flags.
            <int type> = The invasion type.
        """
        if flags == 0:
            flags = [0, 0, 0, 0, 0]

        self.air.netMessenger.send(
            'startInvasion',
            [shardId, suitDeptIndex, suitTypeIndex, flags, type])

    @rpcmethod(accessLevel=ADMINISTRATOR)
    def rpc_stopInvasion(self, shardId):
        """
        Summary:
            Stops any invasion currently running under the provided [shardId].

        Parameters:
            [int shardId] = The ID of the shard that is running the invasion to
                be terminated.
        """
        self.air.netMessenger.send('stopInvasion', [shardId])

    # --- NAME APPROVAL ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_approveName(self, avId):
        """
        Summary:
            Approves the pending name of the avatar associated with the
            provided [avId].

        Parameters:
            [int avId] = The ID of the avatar whose pending name is to be
                approved.

        Example response:
            On success: True
            On failure: False
        """
        newFields = {'WishNameState': 'APPROVED'}
        oldFields = {'WishNameState': 'PENDING'}
        return self.rpc_updateObject(
            avId, 'DistributedToonUD', newFields, oldFields=oldFields)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_rejectName(self, avId):
        """
        Summary:
            Rejects the pending name of the avatar associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar whose pending name is to be
                rejected.

        Example response:
            On success: True
            On failure: False
        """
        newFields = {'WishNameState': 'REJECTED'}
        oldFields = {'WishNameState': 'PENDING'}
        return self.rpc_updateObject(
            avId, 'DistributedToonUD', newFields, oldFields=oldFields)
Example #3
0
class ToontownRPCHandler(ToontownRPCHandlerBase):
    def __init__(self, air):
        ToontownRPCHandlerBase.__init__(self, air)

        self.shardStatus = ShardStatusReceiver(air)

    # --- TESTS ---

    @rpcmethod(accessLevel=COMMUNITY_MANAGER)
    def rpc_ping(self, data):
        """
        Summary:
            Responds with the provided [data]. This method exists for testing
            purposes.

        Parameters:
            [any data] = The data to be given back in response.

        Example response: 'pong'
        """
        return data

    # --- GENERAL ---

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_queryObject(self, doId):
        """
        Summary:
            Responds with the values of all database fields associated with the
            provided [doId].

        Parameters:
            [int doId] = The ID of the object to query database fields on.

        Example response:
            On success: ['DistributedObject', {'fieldName': ('arg1', ...), ...}]
            On failure: [None, None]
        """
        result = []
        unblocked = threading2.Event()

        def callback(dclass, fields):
            if dclass is not None:
                dclass = dclass.getName()
            result.extend([dclass, fields])
            unblocked.set()

        self.air.dbInterface.queryObject(self.air.dbId, doId, callback)

        # Block until the callback is executed:
        unblocked.wait()

        return result

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_updateObject(self, doId, dclassName, newFields, oldFields=None):
        """
        Summary:
            Update the field(s) of the object associated with the provided
            [doId]. If <oldFields> is provided, then this method will fail if
            the object's current fields don't match.

        Parameters:
            [int doId] = The ID of the object whose fields are to be updated.
            [str dclassName] = The name of the object's DClass.
            [dict newFields] = The new field values.
            <dict oldFields> = The old field values to assert.

        Example response:
            On success: True
            On failure: False
        """
        # Ensure that the provided DClass exists:
        if dclassName not in self.air.dclassesByName:
            dclassName += 'UD'
            if dclassName not in self.air.dclassesByName:
                return False

        dclass = self.air.dclassesByName[dclassName]

        if oldFields is None:
            self.air.dbInterface.updateObject(self.air.dbId, doId, dclass,
                                              newFields)
            return True

        result = [True]
        unblocked = threading2.Event()

        def callback(fields):
            if fields is not None:
                result[0] = False
            unblocked.set()

        self.air.dbInterface.updateObject(self.air.dbId,
                                          doId,
                                          dclass,
                                          newFields,
                                          oldFields=oldFields,
                                          callback=callback)

        # Block until the callback is executed:
        unblocked.wait()

        return result[0]

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_setField(self, doId, dclassName, fieldName, args=[]):
        """
        Summary:
            Set the value of the field named [fieldName] on the suggested
            object.

        Parameters:
            [int doId] = The ID of the object whose field is being modified.
            [str dclassName] = The name of the object's DClass.
            [str fieldName] = The name of the field to be modified.
            [list args] = The new value for the field.

        Example response:
            On success: True
            On failure: False
        """
        # Ensure that the provided DClass exists:
        if dclassName not in self.air.dclassesByName:
            dclassName += 'UD'
            if dclassName not in self.air.dclassesByName:
                return False

        dclass = self.air.dclassesByName[dclassName]

        datagram = dclass.aiFormatUpdate(fieldName, doId, doId,
                                         self.air.ourChannel, args)
        self.air.send(datagram)

        return True

    # --- MESSAGES ---

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageChannel(self, channel, message):
        """
        Summary:
            Broadcasts a [message] to any client whose Client Agent is
            subscribed to the provided [channel].

        Parameters:
            [int channel] = The channel to direct the message to.
            [str message] = The message to broadcast.
        """
        dclass = self.air.dclassesByName['ClientServicesManagerUD']
        datagram = dclass.aiFormatUpdate(
            'systemMessage', OtpDoGlobals.OTP_DO_ID_CLIENT_SERVICES_MANAGER,
            channel, 1000000, [message])
        self.air.send(datagram)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageAll(self, message):
        """
        Summary: Broadcasts a [message] to all clients.

        Parameters:
            [str message] = The message to broadcast.
        """
        self.rpc_messageChannel(10, message)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageShard(self, shardId, message):
        """
        Summary:
            Broadcasts a [message] to all clients under the provided [shardId].

        Parameters:
            [int shardId] = The ID of the shard to direct the message to.
            [str message] = The message to broadcast.
        """
        # Get the ID of the ToontownDistrict object:
        districtId = shardId + 1

        # Use it to get the uber zone's channel:
        channel = (districtId << 32) | 2

        self.rpc_messageChannel(channel, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageStaff(self, message):
        """
        Summary:
            Broadcasts a [message] to any client whose access level is higher
            than that of a standard user.

        Parameters:
            [str message] = The message to broadcast.
        """
        self.rpc_messageChannel(OtpDoGlobals.OTP_STAFF_CHANNEL, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageUser(self, userId, message):
        """
        Summary:
            Sends a [message] to the client associated with the provided
            [userId].

        Parameters:
            [int/str userId] = The ID of the user to send the message to.
            [str message] = The message to send.
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            self.rpc_messageAccount(accountId, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageAccount(self, accountId, message):
        """
        Summary:
            Sends a [message] to the client associated with the provided
            [accountId].

        Parameters:
            [int accountId] = The ID of the account to send the message to.
            [str message] = The message to send.
        """
        channel = accountId + (1003L << 32)
        self.rpc_messageChannel(channel, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageAvatar(self, avId, message):
        """
        Summary:
            Sends a [message] to the client associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to send the message to.
            [str message] = The message to send.
        """
        channel = avId + (1001L << 32)
        self.rpc_messageChannel(channel, message)

    # --- KICKS ---

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickChannel(self, channel, code, reason):
        """
        Summary:
            Kicks any client whose Client Agent is subscribed to the provided
            [channel].

        Parameters:
            [int channel] = The channel to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        datagram = PyDatagram()
        datagram.addServerHeader(channel, self.air.ourChannel,
                                 CLIENTAGENT_EJECT)
        datagram.addUint16(code)
        datagram.addString(reason)
        self.air.send(datagram)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickAll(self, code, reason):
        """
        Summary: Kicks all clients.

        Parameters:
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        self.rpc_kickChannel(10, code, reason)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickShard(self, shardId, code, reason):
        """
        Summary: Kicks all clients under the provided [shardId].

        Parameters:
            [int shardId] = The ID of the shard to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        # Get the ID of the ToontownDistrict object:
        districtId = shardId + 1

        # Use it to get the uber zone's channel:
        channel = (districtId << 32) | 2

        self.rpc_kickChannel(channel, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickUser(self, userId, code, reason):
        """
        Summary: Kicks the client associated with the provided [userId].

        Parameters:
            [int/str userId] = The ID of the user to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            self.rpc_kickAccount(accountId, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickAccount(self, accountId, code, reason):
        """
        Summary: Kicks the client associated with the provided [accountId].

        Parameters:
            [int accountId] = The ID of the account to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        channel = accountId + (1003L << 32)
        self.rpc_kickChannel(channel, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickAvatar(self, avId, code, reason):
        """
        Summary: Kicks the client associated with the provided [avId].

        Parameters:
            [int avId] = The ID of the avatar to kick.
            [int code] = The code for the kick.
            [str reason] = The reason for the kick.
        """
        channel = avId + (1001L << 32)
        self.rpc_kickChannel(channel, code, reason)

    # --- BANS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banUser(self, userId, duration, reason):
        """
        Summary:
            Bans the user associated with the provided [userId] for the
            specified [duration].

        Parameters:
            [int/str userId] = The ID of the user to ban.
            [int duration] = The ban's duration in hours. If this is 0 or less,
                the user will be permanently banned.
            [str reason] = A short description of why this user is being
                banned. This can be one of the following values: 'hacking',
                'language', 'other'.

        Example response:
            On success: True
            On failure: False
        """
        if reason not in ('hacking', 'language', 'other'):
            return False
        self.air.writeServerEvent('ban', userId, duration, reason)
        if duration > 0:
            now = datetime.date.today()
            release = str(now + datetime.timedelta(hours=duration))
        else:
            release = '0000-00-00'  # Permanent ban.
        executeHttpRequest('accounts/ban/',
                           Id=userId,
                           Release=release,
                           Reason=reason)
        self.rpc_kickUser(userId, 152, reason)
        return True

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banAccount(self, accountId, duration, reason):
        """
        Summary:
            Bans the user associated with the provided [accountId] for the
            specified [duration].

        Parameters:
            [int accountId] = The ID of the account associated with the user to
                ban.
            [int duration] = The ban's duration in hours. If this is 0 or less,
                the user will be permanently banned.
            [str reason] = A short description of why this user is being
                banned. This can be one of the following values: 'hacking',
                'language', 'other'.

        Example response:
            On success: True
            On failure: False
        """
        userId = self.rpc_getAccountUserId(accountId)
        return self.rpc_banUser(userId, duration, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banAvatar(self, avId, duration, reason):
        """
        Summary:
            Bans the user associated with the provided [avId] for the specified
            [duration].

        Parameters:
            [int/str avId] = The ID of the avatar associated with the user to
                be banned.
            [int duration] = The ban's duration in hours. If this is 0 or less,
                the user will be permanently banned.
            [str reason] = A short description of why this user is being
                banned. This can be one of the following values: 'hacking',
                'language', 'other'.

        Example response:
            On success: True
            On failure: False
        """
        userId = self.rpc_getAvatarUserId(avId)
        return self.rpc_banUser(userId, duration, reason)

    # --- USERS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserAccountId(self, userId):
        """
        Summary:
            Responds with the ID of the account associated with the provided
            [userId].

        Parameters:
            [int/str userId] = The ID of the user to query the account ID on.

        Example response:
            On success: 100000000
            On failure: None
        """
        if str(userId) in self.air.csm.accountDB.dbm:
            return int(self.air.csm.accountDB.dbm[str(userId)])

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserAvatars(self, userId):
        """
        Summary:
            Responds with a list of avatar IDs associated with the provided
            [userId].

        Parameters:
            [int/str userId] = The ID of the user to query the avatars on.

        Example response:
            On success: [0, 100000001, 0, 0, 0, 0]
            On failure: None
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            return self.rpc_getAccountAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserDeletedAvatars(self, userId):
        """
        Summary:
            Responds with a list of deleted avatar IDs associated with the
            provided [userId], along with the time at which each avatar was
            deleted.

        Parameters:
            [int/str userId] = The ID of the user to query the deleted avatars
                on.

        Example response:
            On success: [[100000001, 1409665000], ...]
            On failure: None
        """
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            return self.rpc_getAccountDeletedAvatars(accountId)

    # --- ACCOUNTS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountUserId(self, accountId):
        """
        Summary:
            Responds with the ID of the user associated with the provided
            [accountId].

        Parameters:
            [int accountId] = The ID of the account to query the user ID on.

        Example response:
            On success: 1
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_ID']

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountAvatars(self, accountId):
        """
        Summary:
            Responds with a list of avatar IDs associated with the provided
            [accountId].

        Parameters:
            [int accountId] = The ID of the account to query the avatar IDs on.

        Example response:
            On success: [0, 100000001, 0, 0, 0, 0]
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_AV_SET']

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountDeletedAvatars(self, accountId):
        """
        Summary:
            Responds with a list of deleted avatar IDs associated with the
            provided [accountId], along with the time at which each avatar was
            deleted.

        Parameters:
            [int accountId] = The ID of the account to query the deleted
                avatars on.

        Example response:
            On success: [[100000001, 1409665000], ...]
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_AV_SET_DEL']

    # --- AVATARS ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarUserId(self, avId):
        """
        Summary:
            Responds with the ID of the user associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to query the user ID on.

        Example response:
            On success: 1
            On failure: None
        """
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountUserId(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarAccountId(self, avId):
        """
        Summary:
            Responds with the ID of the account associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to query the account ID on.

        Example response:
            On success: 100000000
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(avId)
        if dclassName == 'DistributedToon':
            return fields['setDISLid'][0]

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarAvatars(self, avId):
        """
        Summary:
            Responds with a list of avatar IDs associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar to query the avatar IDs on.

        Example response:
            On success: [0, 100000001, 0, 0, 0, 0]
            On failure: None
        """
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarDeletedAvatars(self, avId):
        """
        Summary:
            Responds with a list of deleted avatar IDs associated with the
            provided [avId], along with the time at which each avatar was
            deleted.

        Parameters:
            [int avId] = The ID of the avatar to query the deleted avatars on.

        Example response:
            On success: [[100000001, 1409665000], ...]
            On failure: None
        """
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountDeletedAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarDetails(self, avId):
        """
        Summary:
            Responds with basic details on the avatar associated with the
            provided [avId].

        Parameters:
            [int avId] = The ID of the avatar to query basic details on.

        Example response:
            On success:
                {
                   'name': 'Toon Name',
                   'species': 'cat',
                   'head-color': 'Red',
                   'max-hp': 15,
                   'online': True
                }
            On failure: None
        """
        dclassName, fields = self.rpc_queryObject(avId)
        if dclassName == 'DistributedToon':
            result = {}

            result['name'] = fields['setName'][0]

            dna = ToonDNA.ToonDNA()
            dna.makeFromNetString(fields['setDNAString'][0])
            result['species'] = ToonDNA.getSpeciesName(dna.head)

            result['head-color'] = TTLocalizer.NumToColor[dna.headColor]
            result['max-hp'] = fields['setMaxHp'][0]
            result['online'] = (avId in self.air.friendsManager.onlineToons)

            return result

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_findAvatarsByName(self, needle):
        """
        Summary:
            Responds with the IDs of each avatar whose name matches, or
            contains the provided [needle].

        Parameters:
            [str needle] = The string to filter avatars by name with. This is
                case insensitive.

        Example response: [100000001, ...]
        """
        if not config.GetBool('want-mongo-client', False):
            return []
        if not needle:
            return []
        self.air.mongodb.astron.objects.ensure_index('fields.setName')
        exp = re.compile('.*%s.*' % needle, re.IGNORECASE)
        result = self.air.mongodb.astron.objects.find(
            {'fields.setName._0': exp})
        return [avatar['_id'] for avatar in result]

    # --- SHARDS ---

    @rpcmethod(accessLevel=USER)
    def rpc_listShards(self):
        """
        Summary:
            Responds with the current status of each shard that has ever been
            created in the lifetime of the UberDOG.

        Example response:
            {
               401000000: {
                  'name': 'District Name'
                  'available': True,
                  'created': 1409665000,
                  'population': 150,
                  'invasion': {
                     'type': 'Flunky',
                     'flags': 0,
                     'remaining': 1000,
                     'total': 1000,
                     'start': 1409665000
                  }
               },
               ...
            }
        """
        return self.shardStatus.getShards()

    # --- INVASIONS ---

    @rpcmethod(accessLevel=ADMINISTRATOR)
    def rpc_startInvasion(self,
                          shardId,
                          suitDeptIndex=None,
                          suitTypeIndex=None,
                          flags=[0, 0, 0, 0, 0],
                          type=INVASION_TYPE_NORMAL):
        """
        Summary:
            Starts an invasion under the provided [shardId] with the specified
            configuration.

        Parameters:
            [int shardId] = The ID of the shard to start the invasion in.
            <int/NoneType suitDeptIndex> = The invading Cog's department index.
            <int/NoneType suitTypeIndex> = The invading Cog's type index.
            <int flags> = Extra invasion flags.
            <int type> = The invasion type.
        """
        if flags == 0:
            flags = [0, 0, 0, 0, 0]

        self.air.netMessenger.send(
            'startInvasion',
            [shardId, suitDeptIndex, suitTypeIndex, flags, type])

    @rpcmethod(accessLevel=ADMINISTRATOR)
    def rpc_stopInvasion(self, shardId):
        """
        Summary:
            Stops any invasion currently running under the provided [shardId].

        Parameters:
            [int shardId] = The ID of the shard that is running the invasion to
                be terminated.
        """
        self.air.netMessenger.send('stopInvasion', [shardId])

    # --- NAME APPROVAL ---

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_approveName(self, avId):
        """
        Summary:
            Approves the pending name of the avatar associated with the
            provided [avId].

        Parameters:
            [int avId] = The ID of the avatar whose pending name is to be
                approved.

        Example response:
            On success: True
            On failure: False
        """
        newFields = {'WishNameState': 'APPROVED'}
        oldFields = {'WishNameState': 'PENDING'}
        return self.rpc_updateObject(avId,
                                     'DistributedToonUD',
                                     newFields,
                                     oldFields=oldFields)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_rejectName(self, avId):
        """
        Summary:
            Rejects the pending name of the avatar associated with the provided
            [avId].

        Parameters:
            [int avId] = The ID of the avatar whose pending name is to be
                rejected.

        Example response:
            On success: True
            On failure: False
        """
        newFields = {'WishNameState': 'REJECTED'}
        oldFields = {'WishNameState': 'PENDING'}
        return self.rpc_updateObject(avId,
                                     'DistributedToonUD',
                                     newFields,
                                     oldFields=oldFields)
Example #4
0
    def __init__(self, air):
        ToontownRPCHandlerBase.__init__(self, air)

        self.shardStatus = ShardStatusReceiver(air)
class ToontownRPCHandler(ToontownRPCHandlerBase):

    def __init__(self, air):
        ToontownRPCHandlerBase.__init__(self, air)
        self.shardStatus = ShardStatusReceiver(air)

    @rpcmethod(accessLevel=COMMUNITY_MANAGER)
    def rpc_ping(self, data):
        return data

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_queryObject(self, doId):
        result = []
        unblocked = threading2.Event()

        def callback(dclass, fields):
            if dclass is not None:
                dclass = dclass.getName()
            result.extend([dclass, fields])
            unblocked.set()

        self.air.dbInterface.queryObject(self.air.dbId, doId, callback)
        unblocked.wait()
        return result

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_updateObject(self, doId, dclassName, newFields, oldFields = None):
        if dclassName not in self.air.dclassesByName:
            dclassName += 'UD'
            if dclassName not in self.air.dclassesByName:
                return False
        dclass = self.air.dclassesByName[dclassName]
        if oldFields is None:
            self.air.dbInterface.updateObject(self.air.dbId, doId, dclass, newFields)
            return True
        result = [True]
        unblocked = threading2.Event()

        def callback(fields):
            if fields is not None:
                result[0] = False
            unblocked.set()

        self.air.dbInterface.updateObject(self.air.dbId, doId, dclass, newFields, oldFields=oldFields, callback=callback)
        unblocked.wait()
        return result[0]

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_setField(self, doId, dclassName, fieldName, args = []):
        if dclassName not in self.air.dclassesByName:
            dclassName += 'UD'
            if dclassName not in self.air.dclassesByName:
                return False
        dclass = self.air.dclassesByName[dclassName]
        datagram = dclass.aiFormatUpdate(fieldName, doId, doId, self.air.ourChannel, args)
        self.air.send(datagram)
        return True

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageChannel(self, channel, message):
        dclass = self.air.dclassesByName['ClientServicesManagerUD']
        datagram = dclass.aiFormatUpdate('systemMessage', OtpDoGlobals.OTP_DO_ID_CLIENT_SERVICES_MANAGER, channel, 1000000, [message])
        self.air.send(datagram)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageAll(self, message):
        self.rpc_messageChannel(10, message)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_messageShard(self, shardId, message):
        districtId = shardId + 1
        channel = districtId << 32 | 2
        self.rpc_messageChannel(channel, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageStaff(self, message):
        self.rpc_messageChannel(OtpDoGlobals.OTP_STAFF_CHANNEL, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageUser(self, userId, message):
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            self.rpc_messageAccount(accountId, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageAccount(self, accountId, message):
        channel = accountId + 4307852197888L
        self.rpc_messageChannel(channel, message)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_messageAvatar(self, avId, message):
        channel = avId + 4299262263296L
        self.rpc_messageChannel(channel, message)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickChannel(self, channel, code, reason):
        datagram = PyDatagram()
        datagram.addServerHeader(channel, self.air.ourChannel, CLIENTAGENT_EJECT)
        datagram.addUint16(code)
        datagram.addString(reason)
        self.air.send(datagram)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickAll(self, code, reason):
        self.rpc_kickChannel(10, code, reason)

    @rpcmethod(accessLevel=SYSTEM_ADMINISTRATOR)
    def rpc_kickShard(self, shardId, code, reason):
        districtId = shardId + 1
        channel = districtId << 32 | 2
        self.rpc_kickChannel(channel, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickUser(self, userId, code, reason):
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            self.rpc_kickAccount(accountId, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickAccount(self, accountId, code, reason):
        channel = accountId + 4307852197888L
        self.rpc_kickChannel(channel, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_kickAvatar(self, avId, code, reason):
        channel = avId + 4299262263296L
        self.rpc_kickChannel(channel, code, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banUser(self, userId, duration, reason):
        if reason not in ('hacking', 'language', 'other'):
            return False
        self.air.writeServerEvent('ban', userId, duration, reason)
        if duration > 0:
            now = datetime.date.today()
            release = str(now + datetime.timedelta(hours=duration))
        else:
            release = '0000-00-00'
        executeHttpRequest('accounts/ban/', Id=userId, Release=release, Reason=reason)
        self.rpc_kickUser(userId, 152, reason)
        return True

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banAccount(self, accountId, duration, reason):
        userId = self.rpc_getAccountUserId(accountId)
        return self.rpc_banUser(userId, duration, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_banAvatar(self, avId, duration, reason):
        userId = self.rpc_getAvatarUserId(avId)
        return self.rpc_banUser(userId, duration, reason)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserAccountId(self, userId):
        if str(userId) in self.air.csm.accountDB.dbm:
            return int(self.air.csm.accountDB.dbm[str(userId)])

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserAvatars(self, userId):
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            return self.rpc_getAccountAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getUserDeletedAvatars(self, userId):
        accountId = self.rpc_getUserAccountId(userId)
        if accountId is not None:
            return self.rpc_getAccountDeletedAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountUserId(self, accountId):
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_ID']

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountAvatars(self, accountId):
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_AV_SET']

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAccountDeletedAvatars(self, accountId):
        dclassName, fields = self.rpc_queryObject(accountId)
        if dclassName == 'Account':
            return fields['ACCOUNT_AV_SET_DEL']

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarUserId(self, avId):
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountUserId(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarAccountId(self, avId):
        dclassName, fields = self.rpc_queryObject(avId)
        if dclassName == 'DistributedToon':
            return fields['setDISLid'][0]

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarAvatars(self, avId):
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarDeletedAvatars(self, avId):
        accountId = self.rpc_getAvatarAccountId(avId)
        if accountId is not None:
            return self.rpc_getAccountDeletedAvatars(accountId)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_getAvatarDetails(self, avId):
        dclassName, fields = self.rpc_queryObject(avId)
        if dclassName == 'DistributedToon':
            result = {}
            result['name'] = fields['setName'][0]
            dna = ToonDNA.ToonDNA()
            dna.makeFromNetString(fields['setDNAString'][0])
            result['species'] = ToonDNA.getSpeciesName(dna.head)
            result['head-color'] = dna.headColor
            result['max-hp'] = fields['setMaxHp'][0]
            result['online'] = avId in self.air.friendsManager.onlineToons
            return result

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_findAvatarsByName(self, needle):
        if not config.GetBool('want-mongo-client', False):
            return []
        if not needle:
            return []
        self.air.mongodb.astron.objects.ensure_index('fields.setName')
        exp = re.compile('.*%s.*' % needle, re.IGNORECASE)
        result = self.air.mongodb.astron.objects.find({'fields.setName._0': exp})
        return [ avatar['_id'] for avatar in result ]

    @rpcmethod(accessLevel=USER)
    def rpc_listShards(self):
        return self.shardStatus.getShards()

    @rpcmethod(accessLevel=ADMINISTRATOR)
    def rpc_startInvasion(self, shardId, suitDeptIndex = None, suitTypeIndex = None, flags = 0, type = INVASION_TYPE_NORMAL):
        self.air.netMessenger.send('startInvasion', [shardId,
         suitDeptIndex,
         suitTypeIndex,
         flags,
         type])

    @rpcmethod(accessLevel=ADMINISTRATOR)
    def rpc_stopInvasion(self, shardId):
        self.air.netMessenger.send('stopInvasion', [shardId])

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_approveName(self, avId):
        newFields = {'WishNameState': 'APPROVED'}
        oldFields = {'WishNameState': 'PENDING'}
        return self.rpc_updateObject(avId, 'DistributedToonUD', newFields, oldFields=oldFields)

    @rpcmethod(accessLevel=MODERATOR)
    def rpc_rejectName(self, avId):
        newFields = {'WishNameState': 'REJECTED'}
        oldFields = {'WishNameState': 'PENDING'}
        return self.rpc_updateObject(avId, 'DistributedToonUD', newFields, oldFields=oldFields)