Пример #1
0
class Player(object):
    """
    .. code:: python

        def __init__(self, username, wrapper)

    ..

    This class is normally passed as an argument to an event
    callback, but can be also be called using getPlayer(username):

    .. code:: python

        player = self.api.getPlayer(<username>)

    ..

    Player objects contains methods and data of a currently
    logged-in player. This object is destroyed
    upon logging off.  Most features are tied heavily to
    proxy mode implementations and the proxy client instance.

    The player object has a self.__str___ representation that returns the
    player.username.  Therefore, plugins do not need to attempt string
    conversion or do explicit references to player.username in their code
    (str(player) or player.username in plugin code).

    When using events, events in the "proxy" (Group 'Proxy') section are only
    available in proxy mode.  "server" events (Group 'core/mcserver.py')
    are available even without proxy mode, as long as the server is running.


    Supported properties of the player:
    
    .. code:: python

        self.username
        self.loggedIn
        self.mojangUuid
        self.offlineUuid
        self.loginposition
        self.playereid
        self.ipaddress

        # proxy only
        self.serverUuid (proxy only)
        self.clientUuid (proxy only)
        self.clientgameversion
        self.clientboundPackets = Packets_cb(self.clientgameversion)
        self.serverboundPackets = Packets_sb(self.clientgameversion)

        # some player properties associated with abilities (proxy)
        # default is 1.  Should normally be congruent with speed.
        self.field_of_view = float(1)
        # Client set godmode is 0x01
        self.godmode = 0x00
        # Client set creative is 0x08
        self.creative = 0x00
        # default is 1
        self.fly_speed = float(1)

    ..

    """
    def __init__(self, username, wrapper):

        self.wrapper = wrapper
        self.javaserver = wrapper.javaserver
        self.log = wrapper.log

        self.username = username
        self.loggedIn = time.time()

        # TODO - clean this out.  let player objects GC with their client?
        # mcserver will set this to false later to close the thread.
        self.abort = False
        # meanwhile, it still needs to respect wrapper halts
        self.wrapper_signal = self.wrapper.halt

        # these are all MCUUID objects.. I have separated out various
        #  uses of uuid to clarify for later refractoring
        # ---------------
        # Mojang uuid - the bought and paid Mojand UUID.  Never
        # changes- our one constant point of reference per player.
        # offline uuid - created as a MD5 hash of "OfflinePlayer:%s" % username
        # client uuid - what the client stores as the uuid (should be
        # the same as Mojang?) The player.uuid used by old api (and
        # internally here).
        # server uuid = the local server uuid... used to reference
        # the player on the local server.  Could be same as Mojang UUID
        # if server is in online mode or same as offline if server is
        # in offline mode (proxy mode).
        # *******************

        # This can be False if cache (and requests) Fail... bad name or
        # bad Mojang service connection.
        self.mojangUuid = self.wrapper.uuids.getuuidbyusername(username)

        # IF False error carries forward, this is not a valid player,
        # for whatever reason...
        self.clientUuid = self.mojangUuid

        # These two are offline by default.
        self.offlineUuid = self.wrapper.uuids.getuuidfromname(self.username)
        # Start out as the Offline -
        # change it to Mojang if local server is Online
        self.serverUuid = self.offlineUuid

        self.ipaddress = "127.0.0.0"
        self.loginposition = [0, 0, 0]
        self._position = [0, 0, 0, 0, 0]  # internally used for non-proxy mode

        self.client = None
        self.clientgameversion = self.wrapper.servervitals.protocolVersion
        self.clientboundPackets = Packets_cb(self.clientgameversion)
        self.serverboundPackets = Packets_sb(self.clientgameversion)

        self.playereid = None

        # some player properties associated with abilities
        #
        # default is 1.  Should normally be congruent with speed.
        self.field_of_view = float(1)
        # Client set godmode is 0x01
        self.godmode = 0x00
        # Client set creative is 0x08
        self.creative = 0x00
        # default is 1
        self.fly_speed = float(1)

        if self.wrapper.proxy:
            gotclient = False
            for client in self.wrapper.servervitals.clients:
                if client.username == self.username:
                    self.client = client
                    # Both MCUUID objects
                    self.clientUuid = client.uuid
                    self.serverUuid = client.serveruuid

                    self.ipaddress = client.ip

                    # pktSB already set to self.wrapper.servervitals.protocolVersion
                    self.clientboundPackets = self.client.pktCB
                    self.clientgameversion = self.client.clientversion
                    gotclient = True
                    break
            if not gotclient:
                self.log.error("Proxy is on, but this client is not "
                               "listed in wrapper.proxy.clients!")
                self.log.error("The usual cause of this would be that"
                               " someone is connecting directly to"
                               " your server port and not the wrapper"
                               " proxy port!")

        # Process login data
        self.data = Storage(self.clientUuid.string,
                            root="wrapper-data/players")
        if "firstLoggedIn" not in self.data.Data:
            self.data.Data["firstLoggedIn"] = (time.time(), time.tzname)
        if "logins" not in self.data.Data:
            self.data.Data["logins"] = {}
        self.data.Data["lastLoggedIn"] = (self.loggedIn, time.tzname)
        self.data.save()

        # start player logged in time tracking thread
        t = threading.Thread(target=self._track, args=())
        t.daemon = True
        t.start()

    def __str__(self):
        return self.username

    def __del__(self):
        self.data.close()

    @property
    def name(self):
        return self.username

    @property
    def uuid(self):
        return self.mojangUuid

    def _track(self):
        """
        internal tracking that updates a player's server play time.
        Not a part of the public player object API.

        Sample ReST formattings -

        # emphasized notes
        Note: *You do not need to run this function unless you want*
         *certain permission nodes to be granted by default.*
         *i.e., 'essentials.list' should be on by default, so players*
         *can run /list without having any permissions*

        # code samples
            :sample usage:

                .. code:: python

                    < code here >

                ..

        """
        self.data.Data["logins"][int(self.loggedIn)] = time.time()
        while not (self.abort or self.wrapper_signal.halt):
            timeupdate = time.time()
            if timeupdate % 60:  # Just update every 60 seconds
                self.data.Data["logins"][int(self.loggedIn)] = int(time.time())
            # this needs a fast response to ensure the storage closes
            # immediately on player logoff
            time.sleep(.5)
        self.data.close()

    def execute(self, string):
        """
        Run a command as this player. If proxy mode is not enabled,
        it simply falls back to using the 1.8 'execute' command. To 
        be clear, this does NOT work with any Wrapper.py or plugin 
        commands.  The command does not pass through the wrapper.  
        It is only sent to the server console (or the actual server in
        proxy mode).

        :arg string: full command string send on player's behalf to server.

        :returns: Nothing; passes the server or the console as an
         "execute" command.

        """
        try:
            self.client.chat_to_server("/%s" % string)
        except AttributeError:
            if self.wrapper.servervitals.protocolVersion > PROTOCOL_1_7_9:
                self.wrapper.javaserver.console("execute %s ~ ~ ~ %s" %
                                                (self.username, string))
            else:
                self.log.warning("could not run player.execute - wrapper not"
                                 " in proxy mode and minecraft version is less"
                                 " than 1.8 (when /execute was implemented).")

    def sendCommand(self, command, args):
        """
        Sends a command to the wrapper interface as the player instance.
        This would find a nice application with a '\sudo' plugin command.

        :sample usage:

            .. code:: python

                player=getPlayer("username")
                player.sendCommand("perms", ("users", "SurestTexas00", "info"))

            ..

        :Args:
            :command: The wrapper (or plugin) command to execute; no
             slash prefix
            :args: list of arguments (I think it is a list, not a
             tuple or dict!)

        :returns: Nothing; passes command through commands.py function
         'playercommand()'

        """
        pay = {"player": self, "command": command, "args": args}
        self.wrapper.api.callEvent("player.runCommand", pay)

    def say(self, string):
        """
        Send a message as a player.

        :arg string: message/command sent to the server as the player.

        Beware: *in proxy mode, the message string is sent directly to*
        *the server without wrapper filtering,so it could be used to*
        *execute minecraft commands as the player if the string is*
        *prefixed with a slash.*

        """
        try:
            self.client.chat_to_server(string)
        except AttributeError:
            # pre-1.8
            self.wrapper.javaserver.console("say @a <%s> %s" %
                                            (self.username, string))

    def getClient(self):
        """
        Returns the player client context.  Use at your own risk - items
        in client are generally private or subject to change (you are
        working with an undefined API!)... what works in this wrapper
        version may not work in the next.

        :returns: player client object (and possibly sets self.client
         to the matching client).

        """
        if self.client is None:
            for client in self.wrapper.servervitals.clients:
                if client.username == self.username:
                    self.client = client
                    return client
            self.log.warning(
                "getClient could not return a client for:%s"
                " \nThe usual cause of this condition"
                " is that no client instance exists because"
                " proxy is not enabled.", self.username)
            return None
        else:
            return self.client

    def getPosition(self):
        """
        Get the players position
        
        :Note:  The player's position is obtained by parsing client
         packets, which are not sent until the client logs in to 
         the server.  Allow some time after server login to verify 
         the wrapper has had the oppportunity to parse a suitable 
         packet to get the information!
        
        :returns: a tuple of the player's current position x, y, z, 
         and yaw, pitch of head.
        
        """
        if self.wrapper.proxy:
            return self.client.position + self.client.head
        else:
            # Non-proxy mode:
            return self._position

    def getGamemode(self):
        """
        Get the player's current gamemode.
        
        :Note:  The player's Gamemode is obtained by parsing client
         packets, which are not sent until the client logs in to 
         the server.  Allow some time after server login to verify 
         the wrapper has had the oppportunity to parse a suitable 
         packet to get the information!
         
        :returns:  An Integer of the the player's current gamemode.

        """
        try:
            return self.client.gamemode
        except AttributeError:
            # Non-proxy mode:
            return 0

    def getDimension(self):
        """
        Get the player's current dimension.

        :Note:  The player's Dimension is obtained by parsing client
         packets, which are not sent until the client logs in to 
         the server.  Allow some time after server login to verify 
         the wrapper has had the oppportunity to parse a suitable 
         packet to get the information!
         
         :returns: the player's current dimension.

             :Nether: -1
             :Overworld: 0
             :End: 1

        """
        try:
            return self.client.dimension
        except AttributeError:
            # Non-proxy mode:
            return 0

    def setGamemode(self, gamemode=0):
        """
        Sets the user's gamemode.

        :arg gamemode: desired gamemode, as a value 0-3

        """
        if gamemode in (0, 1, 2, 3):
            try:
                self.client.gamemode = gamemode
            except AttributeError:
                # Non-proxy mode:
                pass
            self.wrapper.javaserver.console("gamemode %d %s" %
                                            (gamemode, self.username))

    def setResourcePack(self, url, hashrp=""):
        """
        Sets the player's resource pack to a different URL. If the
        user hasn't already allowed resource packs, the user will
        be prompted to change to the specified resource pack.
        Probably broken right now.

        :Args:
            :url: URL of resource pack
            :hashrp: resource pack hash
        :return: False if not in proxy mode.
        
        """
        try:
            version = self.wrapper.proxy.srv_data.protocolVersion
        except AttributeError:
            # Non proxy mode
            return False
        if version < PROTOCOL_1_8START:
            self.client.packet.sendpkt(self.clientboundPackets.PLUGIN_MESSAGE,
                                       [_STRING, _BYTEARRAY],
                                       ("MC|RPack", url))
        else:
            self.client.packet.sendpkt(
                self.clientboundPackets.RESOURCE_PACK_SEND, [_STRING, _STRING],
                (url, hashrp))

    def isOp(self, strict=False):
        """
        Check if player has Operator status. Accepts player as OP
        based on either the username OR server UUID (unless 'strict'
        is set).

        Note: *If a player has been opped since the last server start,*
        *make sure that you run refreshOpsList() to ensure that*
        *wrapper will acknowlege them as OP.*

        :arg strict: True - use ONLY the UUID as verification

        :returns:  A 1-10 (or more?) op level if the player is currently
         a server operator.

        Can be treated, as before, like a
        boolean - 'if player.isOp():', but now also adds ability
        to granularize with the OP level.  Levels above 4 are
        reserved for wrapper.  10 indicates owner. 5-9 are
        reserved for future minecraft or wrapper levels.  pre-1.8
        servers return 1.  levels above 4 are based on name only
        from the file "superops.txt" in the wrapper folder.
        To assign levels, change the lines of <PlayerName>=<oplevel>
        to your desired names.  Player must be an actual OP before
        the superops.txt will have any effect.  Op level of 10 is
        be required to operate permissions commands.

        """

        if self.wrapper.servervitals.operator_list in (False, None):
            return False  # no ops in file
        # each op item is a dictionary
        for ops in self.wrapper.servervitals.operator_list:
            if ops["uuid"] == self.serverUuid.string:
                return ops["level"]
            if ops["name"] == self.username and not strict:
                return ops["level"]
        return False

    def message(self, message="", position=0):
        """
        Sends a message to the player.

        :Args:
            :message: Can be text, colorcoded text, or json chat
            :position:  an integer 0-2.  2 will place it above XP bar.
             1 or 0 will place it in the chat. Using position 2 will
             only display any text component (or can be used to display
             standard minecraft translates, such as
             "{'translate': 'commands.generic.notFound', 'color': 'red'}" and
             "{'translate': 'tile.bed.noSleep'}"


        :returns: Nothing


        """

        if self.wrapper.proxy:
            self.client.chat_to_client(message, position)
        else:
            self.javaserver.broadcast(message, who=self.username)

    def actionMessage(self, message=""):
        try:
            version = self.wrapper.proxy.srv_data.protocolVersion
        except AttributeError:
            # Non proxy mode
            return False

        if version < PROTOCOL_1_8START:
            parsing = [_STRING, _NULL]
            data = [message]
        else:
            parsing = [_STRING, _BYTE]
            data = (json.dumps({"text": processoldcolorcodes(message)}), 2)

        self.client.packet.sendpkt(
            self.clientboundPackets.CHAT_MESSAGE,
            parsing,  # "string|byte"
            data)

    def setVisualXP(self, progress, level, total):
        """
         Change the XP bar on the client's side only. Does not
         affect actual XP levels.

        :Args:
            :progress:  Float between Between 0 and 1
            :level:  Integer (short in older versions) of EXP level
            :total: Total EXP.

        :returns: Nothing

        """
        try:
            version = self.wrapper.proxy.srv_data.protocolVersion
        except AttributeError:
            # Non proxy mode
            return False

        if version > PROTOCOL_1_8START:
            parsing = [_FLOAT, _VARINT, _VARINT]
        else:
            parsing = [_FLOAT, _SHORT, _SHORT]

        self.client.packet.sendpkt(self.clientboundPackets.SET_EXPERIENCE,
                                   parsing, (progress, level, total))

    def openWindow(self, windowtype, title, slots):
        """
        Opens an inventory window on the client side.  EntityHorse
        is not supported due to further EID requirement.  *1.8*
        *experimental only.*

        :Args:
            :windowtype:  Window Type (text string). See below
             or applicable wiki entry (for version specific info)
            :title: Window title - wiki says chat object (could
             be string too?)
            :slots:

        :returns: None (False if client is less than 1.8 version)


        Valid window names (1.9)

        :minecraft\:chest: Chest, large chest, or minecart with chest

        :minecraft\:crafting_table: Crafting table

        :minecraft\:furnace: Furnace

        :minecraft\:dispenser: Dispenser

        :minecraft\:enchanting_table: Enchantment table

        :minecraft\:brewing_stand: Brewing stand

        :minecraft\:villager: Villager

        :minecraft\:beacon: Beacon

        :minecraft\:anvil: Anvil

        :minecraft\:hopper: Hopper or minecart with hopper

        :minecraft\:dropper: Dropper

        :EntityHorse: Horse, donkey, or mule

        """
        try:
            version = self.wrapper.proxy.srv_data.protocolVersion
        except AttributeError:
            # Non proxy mode
            return False
        client = self.client
        client.windowCounter += 1
        if client.windowCounter > 200:
            client.windowCounter = 2

        # TODO Test what kind of field title is (json or text)

        if not version > PROTOCOL_1_8START:
            return False

        client.packet.sendpkt(self.clientboundPackets.OPEN_WINDOW,
                              [_UBYTE, _STRING, _JSON, _UBYTE],
                              (client.windowCounter, windowtype, {
                                  "text": title
                              }, slots))

        return None  # return a Window object soon

    def setPlayerAbilities(self, fly):
        """
        *based on old playerSetFly (which was an unfinished function)*

        NOTE - You are implementing these abilities on the client
         side only.. if the player is in survival mode, the server
         may think the client is hacking!

        this will set 'is flying' and 'can fly' to true for the player.
        these flags/settings will be set according to the players
        properties, which you can set just prior to calling this
        method:

            :getPlayer().godmode:  Hex or integer (see chart below)

            :getPlayer().creative: Hex or integer (see chart below)

            :getPlayer().field_of_view: Float - default is 1.0

            :getPlayer().fly_speed: Float - default is 1.0

        :arg fly: Boolean

            :True: set fly mode.
            :False: to unset fly mode

        :Bitflags used (for all versions): These can be added to
         produce combination effects.   This function sets
         0x02 and 0x04 together (0x06).

            :Invulnerable: 0x01
            :Flying: 0x02
            :Allow Flying: 0x04
            :Creative Mode: 0x08

        :returns: Nothing

        """
        try:
            sendclient = self.client.packet.sendpkt
            sendserver = self.client.server.packet.sendpkt
        except AttributeError:
            # Non proxy mode
            return False

        # TODO later add and keep track of godmode and creative- code
        # will currently unset them.

        if fly:
            setfly = 0x06  # for set fly
        else:
            setfly = 0x00

        bitfield = self.godmode | self.creative | setfly

        # Note in versions before 1.8, field of view is the
        # walking speed for client (still a float) Server
        # field of view is still walking speed
        sendclient(self.clientboundPackets.PLAYER_ABILITIES,
                   [_BYTE, _FLOAT, _FLOAT],
                   (bitfield, self.fly_speed, self.field_of_view))

        sendserver(self.serverboundPackets.PLAYER_ABILITIES,
                   [_BYTE, _FLOAT, _FLOAT],
                   (bitfield, self.fly_speed, self.field_of_view))

    def sendBlock(self,
                  position,
                  blockid,
                  blockdata,
                  sendblock=True,
                  numparticles=1,
                  partdata=1):
        """
        Used to make phantom blocks visible ONLY to the client.  Sends
        either a particle or a block to the minecraft player's client.
        For blocks iddata is just block id - No need to bitwise the
        blockdata; just pass the additional block data.  The particle
        sender is only a basic version and is not intended to do
        anything more than send something like a barrier particle to
        temporarily highlight something for the player.  Fancy particle
        operations should be custom done by the plugin or someone can
        write a nicer particle-renderer.

        :Args:

            :position: players position as tuple.  The coordinates must
             be in the player's render distance or the block will appear
             at odd places.

            :blockid: usually block id, but could be particle id too.  If
             sending pre-1.8 particles this is a string not a number...
             the valid values are found here

            :blockdata: additional block meta (a number specifying a subtype).

            :sendblock: True for sending a block.

            :numparticles: if particles, their numeric count.

            :partdata: if particles; particle data.  Particles with
             additional ID cannot be used ("Ironcrack").

        :Valid 'blockid' values:
         http://wayback.archive.org/web/20151023030926/https://gist.github.com/thinkofdeath/5110835

        """
        try:
            sendclient = self.client.packet.sendpkt
        except AttributeError:
            # Non proxy
            return False

        posx = position
        x = (position[0])
        y = (position[1])
        z = (position[2])
        if self.clientgameversion > PROTOCOL_1_7_9:
            # 1.8 +
            iddata = blockid << 4 | blockdata

            # [1.8pos/1.7x | 1.7y | 1.7z | 1.7BlockID/1.8iddata | 1.7blockdata]
            # these are whitespaced this way to line up visually
            blockparser = [_POSITION, _NULL, _NULL, _VARINT, _NULL]

            particleparser = [
                _INT, _BOOL, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT,
                _FLOAT, _INT
            ]
        else:
            # 1.7
            posx = x
            iddata = blockid

            blockparser = [_INT, _UBYTE, _INT, _VARINT, _UBYTE]

            particleparser = [
                _STRING, _NULL, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT,
                _FLOAT, _INT
            ]

        if sendblock:
            sendclient(self.clientboundPackets.BLOCK_CHANGE, blockparser,
                       (posx, y, x, iddata, blockdata))
        else:
            sendclient(self.clientboundPackets.PARTICLE, particleparser,
                       (blockid, True, x + .5, y + .5, z + .5, 0, 0, 0,
                        partdata, numparticles))

    # Inventory-related actions.
    def getItemInSlot(self, slot):
        """
        Returns the item object of an item currently being held.

        """
        try:
            return self.client.inventory[slot]
        except AttributeError:
            # Non proxy
            return False

    def getHeldItem(self):
        """
        Returns the item object of an item currently being held.

        """
        try:
            return self.client.inventory[36 + self.client.slot]
        except AttributeError:
            # Non proxy
            return False

    # Permissions-related
    def hasPermission(self,
                      node,
                      another_player=False,
                      group_match=True,
                      find_child_groups=True):
        """
        If the player has the specified permission node (either
        directly, or inherited from a group that the player is in),
        it will return the value (usually True) of the node.
        Otherwise, it returns False.  Using group_match and
        find_child_groups are enabled by default.  Permissions
        can be sped up by disabling child inheritance or even
        group matching entirely (for high speed loops, for
        instance).  Normally, permissions are related to
        commands the player typed, so the 'cost' of child
        inheritance is not a concern.

        :Args:
            :node: Permission node (string)
            :another_player: sending a string name of another player
             will check THAT PLAYER's permission instead! Useful for
             checking a player's permission for someone who is not
             logged in and has no player object.
            :group_match: return a permission for any group the player
             is a member of.  If False, will only return permissions
             player has directly.
            :find_child_groups: If group matching, this will
             additionally locate matches when a group contains
             a permission that is another group's name.  So if group
             'admin' contains a permission called 'moderator', anyone
             with group admin will also have group moderator's
             permissions as well.

        :returns:  Boolean indicating whether player has permission or not.

        """
        uuid_to_check = self.mojangUuid.string
        if another_player:
            # get other player mojang uuid
            uuid_to_check = str(
                self.wrapper.uuids.getuuidbyusername(another_player))
            if not uuid_to_check:
                # probably a bad name provided.. No further check needed.
                return False

        return self.wrapper.perms.has_permission(uuid_to_check, node,
                                                 group_match,
                                                 find_child_groups)

    def setPermission(self, node, value=True):
        """
        Adds the specified permission node and optionally a value
        to the player.

        :Args:
            :node: Permission node (string)
            :value: defaults to True, but can be set to False to
             explicitly revoke a particular permission from the
             player, or to any arbitrary value.

        :returns: Nothing

        """
        self.wrapper.perms.set_permission(self.mojangUuid.string, node, value)

    def removePermission(self, node):
        """
        Completely removes a permission node from the player. They
        will inherit this permission from their groups or from
        plugin defaults.

        If the player does not have the specific permission, an
        IndexError is raised. Note that this method has no effect
        on nodes inherited from groups or plugin defaults.

        :arg node: Permission node (string)

        :returns:  Boolean; True if operation succeeds, False if
         it fails (set debug mode to see/log error).

        """
        return self.wrapper.perms.remove_permission(self.mojangUuid.string,
                                                    node)

    def resetPerms(self, uuid):
        """

        resets all user data (removes all permissions).

        :arg uuid: The online/mojang uuid (string)

        :returns:  nothing

        """
        return self.wrapper.perms.fill_user(uuid)

    def hasGroup(self, group):
        """
        Returns a boolean of whether or not the player is in
        the specified permission group.

        :arg group: Group node (string)

        :returns:  Boolean of whether player has permission or not.

        """
        return self.wrapper.perms.has_group(self.mojangUuid.string, group)

    def getGroups(self):
        """
        Returns a list of permission groups that the player is in.

        :returns:  list of groups

        """
        return self.wrapper.perms.get_groups(self.mojangUuid.string)

    def setGroup(self, group, creategroup=True):
        """
        Adds the player to a specified group.  Returns False if
        the command fails (set debiug to see error).  Failure
        is only normally expected if the group does not exist
        and creategroup is False.

        :Args:
            :group: Group node (string)
            :creategroup: If True (by default), will create the
             group if it does not exist already.  This WILL
             generate a warning log since it is not an expected
             condition.

        :returns:  Boolean; True if operation succeeds, False
         if it fails (set debug mode to see/log error).

        """
        return self.wrapper.perms.set_group(self.mojangUuid.string, group,
                                            creategroup)

    def removeGroup(self, group):
        """
        Removes the player to a specified group.

        :arg group: Group node (string)

        :returns:  (use debug logging to see any errors)

            :True: Group was found and .remove operation performed
             (assume success if no exception raised).
            :None: User not in group
            :False: player uuid not found!

        """
        return self.wrapper.perms.remove_group(self.mojangUuid.string, group)

    # Player Information
    def getFirstLogin(self):
        """
        Returns a tuple containing the timestamp of when the user
        first logged in for the first time, and the timezone (same
        as time.tzname).

        """
        return self.data.Data["firstLoggedIn"]

    # Cross-server commands
    def connect(self, address, port):
        # TODO - WORK IN PROGRESS
        """
        Upon calling, the player object will become defunct and
        the client will be transferred to another server or wrapper
        instance (provided it has online-mode turned off).

        :Args:
            :address: server address (local address)
            :port: server port (local port)

        :returns: Nothing

        """
        self.client.change_servers(address, port)
Пример #2
0
class Web(object):
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.api = wrapper.api
        self.log = logging.getLogger('Web')
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.pass_handler = self.wrapper.cipher
        self.socket = False
        self.storage = Storage("web", pickle=False)
        self.data = self.storage.Data
        self.xplayer = ConsolePlayer(self.wrapper, self.console_output)

        self.adminname = "Web Admin"
        self.xplayer.username = self.adminname

        self.onlyusesafe_ips = self.config["Web"]["safe-ips-use"]
        self.safe_ips = self.config["Web"]["safe-ips"]

        if "keys" not in self.data:
            self.data["keys"] = []

        # Register events
        self.api.registerEvent("server.consoleMessage", self.on_server_console)
        self.api.registerEvent("player.message", self.on_player_message)
        self.api.registerEvent("player.login", self.on_player_join)
        self.api.registerEvent("player.logout", self.on_player_leave)
        self.api.registerEvent("irc.message", self.on_channel_message)

        self.consoleScrollback = []
        self.chatScrollback = []
        self.memoryGraph = []
        self.loginAttempts = 0
        self.lastAttempt = 0
        self.disableLogins = 0
        self.props = ""
        self.propsCount = 0
        # t = threading.Thread(target=self.update_graph, args=())
        # t.daemon = True
        # t.start()

    # ================ Start  and Run code section ================
    # ordered by the time they are referenced in the code.

    # def update_graph(self):
    #     while not self.wrapper.haltsig.halt:
    #         while len(self.memoryGraph) > 200:
    #             del self.memoryGraph[0]
    #         if self.wrapper.javaserver.getmemoryusage():
    #             self.memoryGraph.append(
    #                 [time.time(), self.wrapper.javaserver.getmemoryusage()])
    #        time.sleep(1)

    def wrap(self):
        """ Wrapper starts excution here (via a thread). """
        if not pkg_resources:
            self.log.error("`pkg_resources` is not installed.  It is usually "
                           "distributed with setuptools. Check https://stackov"
                           "erflow.com/questions/7446187/no-module-named-pkg-r"
                           "esources for possible solutions")
            return

        while not self.wrapper.haltsig.halt:
            try:
                if self.bind():
                    # cProfile.run("self.listen()", "cProfile-debug")
                    self.listen()
                else:
                    self.log.error(
                        "Could not bind web to %s:%d - retrying in 5"
                        " seconds" % (self.config["Web"]["web-bind"],
                                      self.config["Web"]["web-port"]))
            except:
                for line in traceback.format_exc().split("\n"):
                    self.log.error(line)
            time.sleep(5)
        # closing also calls storage.save().
        self.storage.close()

    def bind(self):
        """ Started by self.wrap() to bind socket. """
        if self.socket is not False:
            self.socket.close()
        try:
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((self.config["Web"]["web-bind"],
                              self.config["Web"]["web-port"]))
            self.socket.listen(5)
            return True
        except:
            return False

    def listen(self):
        """ Excuted by self.wrap() to listen for client(s). """
        self.log.info(
            "Web Interface bound to %s:%d" %
            (self.config["Web"]["web-bind"], self.config["Web"]["web-port"]))
        while not self.wrapper.haltsig.halt:
            # noinspection PyUnresolvedReferences
            sock, addr = self.socket.accept()
            if self.onlyusesafe_ips:
                if addr[0] not in self.safe_ips:
                    sock.close()
                    self.log.info(
                        "Sorry charlie (an unathorized IP %s attempted "
                        "connection)", addr[0])
                    continue
            client = Client(self.wrapper, sock, addr, self)

            t = threading.Thread(target=client.wrap, args=())
            t.daemon = True
            t.start()
        self.storage.save()

    def console_output(self, message):
        display = str(message)
        if type(message) is dict:
            if "text" in message:
                display = message["text"]
        self.on_server_console({"message": display})

    # ========== EVENTS SECTION ==========================

    def on_server_console(self, payload):
        while len(self.consoleScrollback) > 1000:
            try:
                self.consoleScrollback.pop()
            except:
                break
        self.consoleScrollback.append((time.time(), payload["message"]))

    def on_player_message(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                self.chatScrollback.pop()
            except:
                break
        self.chatScrollback.append([
            time.time(), {
                "type": "player",
                "payload": {
                    "player": payload["player"].username,
                    "message": payload["message"]
                }
            }
        ])

    def on_player_join(self, payload):
        # abrupt disconnections can cause player on-join although player is
        # not on...
        if not payload["player"]:
            return
        while len(self.chatScrollback) > 200:
            self.chatScrollback.pop()
        self.chatScrollback.append([
            time.time(), {
                "type": "playerJoin",
                "payload": {
                    "player": payload["player"].username
                }
            }
        ])

    def on_player_leave(self, payload):
        while len(self.chatScrollback) > 200:
            self.chatScrollback.pop()
        self.chatScrollback.append([
            time.time(), {
                "type": "playerLeave",
                "payload": {
                    "player": payload["player"].username
                }
            }
        ])

    def on_channel_message(self, payload):
        while len(self.chatScrollback) > 200:
            self.chatScrollback.pop()
        self.chatScrollback.append(
            [time.time(), {
                "type": "irc",
                "payload": payload
            }])

    # ========== Externally-called Methods section ==========================

    def check_login(self, password):
        """
        Returns True or False to indicate login success.
         - Called by client.run_action, action="login"
        """

        # Threshold for logins
        if time.time() - self.disableLogins < 60:
            self.loginAttempts = 0
            return None

        # check password validity
        if self.pass_handler.check_pw(
                password, self.config["Web"]["web-password"]):  # noqa
            return True

        # unsuccessful password attempt
        self.loginAttempts += 1
        if self.loginAttempts > 4 and time.time() - self.lastAttempt < 60:
            self.disableLogins = time.time()
            self.log.warning("Disabled login attempts for one minute")
        self.lastAttempt = time.time()
        return False

    def make_key(self, remember_me, username):
        a = ""
        z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
        for i in range(64):
            a += z[random.randrange(0, len(z))]
        # a += chr(random.randrange(97, 122))
        self.data["keys"].append([a, time.time(), remember_me, username])
        return a

    def validate_key(self, key):
        # day = 86400
        week = 604800
        curr_time = int(time.time())
        old_keys = []
        success = False

        for i in self.data["keys"]:
            if len(i) > 2:
                expire_time = int(i[1])
                remembered = i[2]
                user = getargs(i, 3)
                if user != "":
                    self.adminname = user

                # even "remembereds" should expire after a week after last use
                if remembered:
                    expire_time += week

                if curr_time - expire_time > week:  # or day or whatever
                    # remove expired keys
                    old_keys.append(i[0])
                else:
                    if i[0] == key:  # Validate key
                        if remembered:
                            # remembereds are reset at each successful login:
                            self.update_key(i, 1, curr_time)
                        self.loginAttempts = 0
                        success = True
            else:
                # remove bad malformed keys
                old_keys.append(i[0])

        for oldkey in old_keys:
            self.remove_key(oldkey)
        self.storage.save()
        return success

    def update_key(self, key, field_number, data):
        for i, v in enumerate(self.data["keys"]):
            if v[0] == key:
                self.data["keys"][i][field_number] = data

    def remove_key(self, key):
        for i, v in enumerate(self.data["keys"]):
            if v[0] == key:
                del self.data["keys"][i]

    def getdisk_usage(self):
        """only works on Python 3.  returns 0 for Python 2"""
        if disk_usage:
            # noinspection PyCallingNonCallable
            spaces = disk_usage(self.serverpath)
            frsp = spaces.free
            return frsp
        return int(0)
Пример #3
0
class Web(object):
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.api = wrapper.api
        self.log = logging.getLogger('Web')
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.pass_handler = self.wrapper.cipher
        self.socket = False
        self.storage = Storage("web", pickle=False)
        self.data = self.storage.Data
        self.xplayer = ConsolePlayer(self.wrapper, self.console_output)

        self.adminname = "Web Admin"
        self.xplayer.username = self.adminname

        self.onlyusesafe_ips = self.config["Web"]["safe-ips-use"]
        self.safe_ips = self.config["Web"]["safe-ips"]

        if "keys" not in self.data:
            self.data["keys"] = []

        # Register events
        self.api.registerEvent("server.consoleMessage", self.on_server_console)
        self.api.registerEvent("player.message", self.on_player_message)
        self.api.registerEvent("player.login", self.on_player_join)
        self.api.registerEvent("player.logout", self.on_player_leave)
        self.api.registerEvent("irc.message", self.on_channel_message)

        self.consoleScrollback = []
        self.chatScrollback = []
        self.memoryGraph = []
        self.loginAttempts = 0
        self.lastAttempt = 0
        self.disableLogins = 0
        self.props = ""
        self.propsCount = 0
        # t = threading.Thread(target=self.update_graph, args=())
        # t.daemon = True
        # t.start()

    # ================ Start  and Run code section ================
    # ordered by the time they are referenced in the code.

    # def update_graph(self):
    #     while not self.wrapper.haltsig.halt:
    #         while len(self.memoryGraph) > 200:
    #             del self.memoryGraph[0]
    #         if self.wrapper.javaserver.getmemoryusage():
    #             self.memoryGraph.append(
    #                 [time.time(), self.wrapper.javaserver.getmemoryusage()])
    #        time.sleep(1)

    def wrap(self):
        """ Wrapper starts excution here (via a thread). """
        if not pkg_resources:
            self.log.error("`pkg_resources` is not installed.  It is usually "
                           "distributed with setuptools. Check https://stackov"
                           "erflow.com/questions/7446187/no-module-named-pkg-r"
                           "esources for possible solutions")
            return 
        
        while not self.wrapper.haltsig.halt:
            try:
                if self.bind():
                    # cProfile.run("self.listen()", "cProfile-debug")
                    self.listen()
                else:
                    self.log.error(
                        "Could not bind web to %s:%d - retrying in 5"
                        " seconds" % (
                            self.config["Web"]["web-bind"],
                            self.config["Web"]["web-port"]
                        )
                    )
            except:
                for line in traceback.format_exc().split("\n"):
                    self.log.error(line)
            time.sleep(5)
        # closing also calls storage.save().
        self.storage.close()

    def bind(self):
        """ Started by self.wrap() to bind socket. """
        if self.socket is not False:
            self.socket.close()
        try:
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((self.config["Web"]["web-bind"],
                              self.config["Web"]["web-port"]))
            self.socket.listen(5)
            return True
        except:
            return False

    def listen(self):
        """ Excuted by self.wrap() to listen for client(s). """
        self.log.info("Web Interface bound to %s:%d" % (
            self.config["Web"]["web-bind"], self.config["Web"]["web-port"]))
        while not self.wrapper.haltsig.halt:
            # noinspection PyUnresolvedReferences
            sock, addr = self.socket.accept()
            if self.onlyusesafe_ips:
                if addr[0] not in self.safe_ips:
                    sock.close()
                    self.log.info(
                        "Sorry charlie (an unathorized IP %s attempted "
                        "connection)", addr[0]
                    )
                    continue
            client = Client(self.wrapper, sock, addr, self)

            t = threading.Thread(target=client.wrap, args=())
            t.daemon = True
            t.start()
        self.storage.save()

    def console_output(self, message):
        display = str(message)
        if type(message) is dict:
            if "text" in message:
                display = message["text"]
        self.on_server_console({"message": display})

    # ========== EVENTS SECTION ==========================

    def on_server_console(self, payload):
        while len(self.consoleScrollback) > 1000:
            try:
                self.consoleScrollback.pop()
            except:
                break
        self.consoleScrollback.append((time.time(), payload["message"]))

    def on_player_message(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                self.chatScrollback.pop()
            except:
                break
        self.chatScrollback.append(
            [time.time(), {"type": "player",
                           "payload": {"player": payload["player"].username,
                                       "message": payload["message"]}}])

    def on_player_join(self, payload):
        # abrupt disconnections can cause player on-join although player is
        # not on...
        if not payload["player"]:
            return
        while len(self.chatScrollback) > 200:
            self.chatScrollback.pop()
        self.chatScrollback.append([
            time.time(), {"type": "playerJoin",
                          "payload": {"player": payload["player"].username}}])

    def on_player_leave(self, payload):
        while len(self.chatScrollback) > 200:
            self.chatScrollback.pop()
        self.chatScrollback.append([
            time.time(), {"type": "playerLeave",
                          "payload": {"player": payload["player"].username}}])

    def on_channel_message(self, payload):
        while len(self.chatScrollback) > 200:
            self.chatScrollback.pop()
        self.chatScrollback.append([
            time.time(), {"type": "irc", "payload": payload}])

    # ========== Externally-called Methods section ==========================

    def check_login(self, password):
        """
        Returns True or False to indicate login success.
         - Called by client.run_action, action="login"
        """

        # Threshold for logins
        if time.time() - self.disableLogins < 60:
            self.loginAttempts = 0
            return None

        # check password validity
        if self.pass_handler.check_pw(password, self.config["Web"]["web-password"]):  # noqa
            return True

        # unsuccessful password attempt
        self.loginAttempts += 1
        if self.loginAttempts > 4 and time.time() - self.lastAttempt < 60:
            self.disableLogins = time.time()
            self.log.warning("Disabled login attempts for one minute")
        self.lastAttempt = time.time()
        return False

    def make_key(self, remember_me, username):
        a = ""
        z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
        for i in range(64):
            a += z[random.randrange(0, len(z))]
        # a += chr(random.randrange(97, 122))
        self.data["keys"].append([a, time.time(), remember_me, username])
        return a

    def validate_key(self, key):
        # day = 86400
        week = 604800
        curr_time = int(time.time())
        old_keys = []
        success = False

        for i in self.data["keys"]:
            if len(i) > 2:
                expire_time = int(i[1])
                remembered = i[2]
                user = getargs(i, 3)
                if user != "":
                    self.adminname = user

                # even "remembereds" should expire after a week after last use
                if remembered:
                    expire_time += week

                if curr_time - expire_time > week:  # or day or whatever
                    # remove expired keys
                    old_keys.append(i[0])
                else:
                    if i[0] == key:  # Validate key
                        if remembered:
                            # remembereds are reset at each successful login:
                            self.update_key(i, 1, curr_time)
                        self.loginAttempts = 0
                        success = True
            else:
                # remove bad malformed keys
                old_keys.append(i[0])

        for oldkey in old_keys:
            self.remove_key(oldkey)
        self.storage.save()
        return success

    def update_key(self, key, field_number, data):
        for i, v in enumerate(self.data["keys"]):
            if v[0] == key:
                self.data["keys"][i][field_number] = data

    def remove_key(self, key):
        for i, v in enumerate(self.data["keys"]):
            if v[0] == key:
                del self.data["keys"][i]

    def getdisk_usage(self):
        """only works on Python 3.  returns 0 for Python 2"""
        if disk_usage:
            # noinspection PyCallingNonCallable
            spaces = disk_usage(self.serverpath)
            frsp = spaces.free
            return frsp
        return int(0)