Beispiel #1
0
    async def requestInventory(self):
        """|coro|
		Send a request to the server to get the bot's inventory."""
        await self.main.send(Packet.new(31, 1))
Beispiel #2
0
    async def requestShopList(self):
        """|coro|
		Send a request to the server to get the shop list."""
        await self.main.send(Packet.new(8, 20))
Beispiel #3
0
    async def handle_packet(self, connection: Connection, packet: Packet):
        """|coro|
		Handles the known packets and dispatches events.
		Subclasses should handle only the unhandled packets from this method.

		Example: ::
			class Bot(aiotfm.Client):
				async def handle_packet(self, conn, packet):
					handled = await super().handle_packet(conn, packet.copy())

					if not handled:
						# Handle here the unhandled packets.
						pass

		:param connection: :class:`aiotfm.connection.Connection` the connection that received the packet.
		:param packet: :class:`aiotfm.Packet` the packet.
		:return: True if the packet got handled, False otherwise.
		"""
        CCC = packet.readCode()
        if CCC == (1, 1):  # Old packets
            data = packet.readBytes().split(b'\x01')
            oldCCC = tuple(data.pop(0)[:2])
            self.dispatch('old_packet', connection, oldCCC, data)
            return await self.handle_old_packet(connection, oldCCC, data)

        elif CCC == (5, 21):  # Joined room
            room = self.room = Room(private=not packet.readBool(),
                                    name=packet.readUTF())
            self.dispatch('joined_room', room)

        elif CCC == (6, 6):  # Room message
            player_id = packet.read32()
            username = packet.readUTF()
            commu = packet.read8()
            message = packet.readUTF()
            self.dispatch(
                'room_message',
                Message(Player(username, pid=player_id), message, commu, self))

        elif CCC == (6, 20):  # Server message
            packet.readBool(
            )  # if False then the message will appear in the #Server channel
            t_key = packet.readUTF()
            t_args = [packet.readUTF() for i in range(packet.read8())]
            self.dispatch('server_message', self.locale[t_key], *t_args)

        elif CCC == (8, 5):  # Show emoji
            player = self.room.get_player(pid=packet.read32())
            emoji = packet.read8()
            self.dispatch('emoji', player, emoji)

        elif CCC == (8, 16):  # Profile
            self.dispatch('profile', Profile(packet))

        elif CCC == (8, 20):  # Shop
            self.dispatch('shop', Shop(packet))

        elif CCC == (8, 22):  # Skills
            skills = {}
            for i in range(packet.read8()):
                key, value = packet.read8(), packet.read8()
                skills[key] = value
            self.dispatch('skills', skills)

        elif CCC == (16, 2):  # Tribe invitation received
            author = packet.readUTF()
            tribe = packet.readUTF()
            self.dispatch('tribe_inv', author, tribe)

        elif CCC == (26, 2):  # Logged in successfully
            player_id = packet.read32()
            self.username = username = packet.readUTF()
            played_time = packet.read32()
            community = packet.read8()
            pid = packet.read32()
            self.dispatch('logged', player_id, username, played_time,
                          community, pid)

        elif CCC == (26, 3):  # Handshake OK
            online_players = packet.read32()  # online players
            connection.fingerprint = packet.read8()
            community = packet.readUTF()  # community
            country = packet.readUTF()  # country
            self.authkey = packet.read32()

            self.loop.create_task(self._heartbeat_loop())

            await connection.send(
                Packet.new(8, 2).write8(self.community).write8(0))

            os_info = Packet.new(28, 17).writeString('en').writeString('Linux')
            os_info.writeString('LNX 29,0,0,140').write8(0)

            await connection.send(os_info)
            self.dispatch('login_ready', online_players, community, country)

        elif CCC == (26, 12):  # Login result
            self.dispatch('login_result', packet.read8(), packet.readUTF(),
                          packet.readUTF())

        elif CCC == (26, 25):  # Ping
            self.dispatch('ping')

        elif CCC == (28, 6):  # Server ping
            await connection.send(Packet.new(28, 6).write8(packet.read8()))

        elif CCC == (29, 6):  # Lua logs
            self.dispatch('lua_log', packet.readUTF())

        elif CCC == (31, 1):  # Inventory data
            self.inventory = Inventory.from_packet(packet)
            self.inventory.client = self
            self.dispatch('inventory_update', self.inventory)

        elif CCC == (31, 2):  # Update inventory item
            id = packet.read16()
            quantity = packet.read8()

            if id in self.inventory.items:
                item = self.inventory.items[id]
                previous = item.quantity
                item.quantity = quantity
                self.dispatch('item_update', item, previous)

            else:
                item = InventoryItem(id=id, quantity=quantity)
                self.inventory.items[item.id] = item
                self.dispatch('new_item', item)

        elif CCC == (31, 5):  # Trade invite
            player = self.room.get_player(pid=packet.read32())
            trade = Trade(player, self)
            self.trades.append(trade)
            trade.alive = True
            trade.on_invite = True
            self.dispatch('trade_invite', trade)

        elif CCC == (31, 6):  # Trade error
            name = packet.readUTF()
            error = packet.read8()

            if name == "":
                if self.trade._other.username == name:
                    self.trade._close()
                    self.dispatch('trade_error', self.trade, error)
                    self.dispatch('trade_close', self.trade)

            else:
                for trade in self.trades:
                    if trade._other.username == name:
                        trade._close()
                        self.dispatch('trade_error', trade, error)
                        self.dispatch('trade_close', trade)
                        break

        elif CCC == (31, 7):  # Trade start
            player = self.room.get_player(pid=packet.read32())
            player.trade.on_invite = False
            player.trade.alive = True

            if self.trade is not None:
                trade = self.trade
                self.trade._close()
                self.dispatch('trade_close', trade)
            self.trade = player.trade
            self.dispatch('trade_start', self.trade)

        elif CCC == (31, 8):  # Trade items
            me = packet.readBool()
            id = packet.read16()
            adding = packet.readBool()
            quantity = packet.read8()
            quantity = (1 if adding else -1) * quantity

            items = self.trade.items_me if me else self.trade.items_other
            if id in items:
                items[id] += quantity
            else:
                items[id] = quantity
            if items[id] == 0:
                del items[id]

            self.trade.locked_me = False
            self.trade.locked_other = False

            self.dispatch('trade_item_change', self.trade,
                          self if me else self.trade._other, id, quantity,
                          items[id] if id in items else 0)

        elif CCC == (31, 9):  # Trade lock
            if packet.readBool():
                self.trade.locked_me = packet.readBool()
                self.dispatch('trade_lock', self.trade, self,
                              self.trade.locked_me)
            else:
                self.trade.locked_other = packet.readBool()
                self.dispatch('trade_lock', self.trade, self.trade._other,
                              self.trade.locked_other)

        elif CCC == (31, 10):  # Trade complete
            trade = self.trade
            self.trade._close()
            self.dispatch('trade_complete', trade)

        elif CCC == (44, 1):  # Bulle switching
            bulle_id = packet.read32()
            bulle_ip = packet.readString().decode()

            if self.bulle is not None:
                self.bulle.close()

            self.bulle = Connection('bulle', self, self.loop)
            await self.bulle.connect(bulle_ip, self.main.address[1])
            await self.bulle.send(Packet.new(*CCC).write32(bulle_id))

        elif CCC == (44, 22):  # Fingerprint offset changed
            connection.fingerprint = packet.read8()

        elif CCC == (60, 3):  # Community platform
            TC = packet.read16()
            self.dispatch('raw_cp', TC, packet.copy(True))

            if TC == 3:  # Connected to the community platform
                self.dispatch('ready')

            elif TC == 55:  # Channel join result
                result = packet.read8()
                self.dispatch('channel_joined_result', result)

            elif TC == 57:  # Channel leave result
                result = packet.read8()
                self.dispatch('channel_leaved_result', result)

            elif TC == 59:  # Channel /who result
                idSequence = packet.read32()
                result = packet.read8()
                players = [
                    Player(packet.readUTF()) for _ in range(packet.read16())
                ]
                self.dispatch('channel_who', idSequence, players)

            elif TC == 62:  # Joined a channel
                name = packet.readUTF()

                if name in self._channels:
                    channel = [c for c in self._channels if c == name][0]
                else:
                    channel = Channel(name, self)
                    self._channels.append(channel)

                self.dispatch('channel_joined', channel)

            elif TC == 63:  # Quit a channel
                name = packet.readUTF()
                if name in self._channels:
                    self._channels.remove(name)

                self.dispatch('channel_closed', name)

            elif TC == 64:  # Channel message
                author, community = packet.readUTF(), packet.read32()
                channel_name, message = packet.readUTF(), packet.readUTF()
                channel = self.get_channel(channel_name)

                if channel is None:
                    channel = Channel(channel_name, self)
                    self._channels.append(channel)

                self.dispatch(
                    'channel_message',
                    ChannelMessage(author, community, message, channel))

            elif TC == 65:  # Tribe message
                author, message = packet.readUTF(), packet.readUTF()
                self.dispatch('tribe_message', author, message)

            elif TC == 66:  # Whisper
                author, commu, receiver, message = Player(
                    packet.readUTF()), packet.read32(), Player(
                        packet.readUTF()), packet.readUTF()
                self.dispatch('whisper',
                              Whisper(author, commu, receiver, message, self))

            elif TC == 88:  # tribe member connected
                self.dispatch('member_connected', packet.readUTF())

            elif TC == 90:  # tribe member disconnected
                self.dispatch('member_disconnected', packet.readUTF())

            else:
                if self.LOG_UNHANDLED_PACKETS:
                    print(CCC, TC, bytes(packet.buffer)[4:])
                return False

        elif CCC == (100, 67):  # New inventory item
            slot = packet.read8()
            id = packet.read16()
            quantity = packet.read8()

            item = InventoryItem(id=id,
                                 quantity=quantity,
                                 slot=None if slot == 0 else slot)
            self.inventory[id] = item
            self.dispatch('new_item', item)

        elif CCC == (144, 1):  # Set player list
            before = self.room.players
            self.room.players = []

            for player in range(packet.read16()):
                self.room.players.append(Player.from_packet(packet))

            for player in before:
                if player.trade is not None:
                    after = self.room.get_player(pid=player.pid)

                    if after is not None:
                        player.trade._update_player(after)
                    else:
                        trade = player.trade
                        player.trade._close()
                        self.dispatch('trade_close', trade)

            self.dispatch('bulk_player_update', before, self.room.players)

        elif CCC == (144, 2):  # Add a player
            after = Player.from_packet(packet)
            before = self.room.get_player(pid=after.pid)

            self.room.players.append(after)
            if before is None:
                self.dispatch('player_join', after)
            else:
                self.room.players.remove(before)
                if before.trade is not None:
                    before.trade._update_player(after)
                self.dispatch('player_update', before, after)

        else:
            if self.LOG_UNHANDLED_PACKETS:
                print(CCC, bytes(packet.buffer)[2:])
            return False

        return True
Beispiel #4
0
    async def enterTribe(self):
        """|coro|
		Enter the tribe house
		"""
        await self.main.send(Packet.new(16, 1))
Beispiel #5
0
    async def handle_packet(self, connection: Connection, packet: Packet):
        """|coro|
		Handles the known packets and dispatches events.
		Subclasses should handle only the unhandled packets from this method.

		Example: ::
			class Bot(aiotfm.Client):
				async def handle_packet(self, conn, packet):
					handled = await super().handle_packet(conn, packet.copy())

					if not handled:
						# Handle here the unhandled packets.
						pass

		:param connection: :class:`aiotfm.Connection` the connection that received
			the packet.
		:param packet: :class:`aiotfm.Packet` the packet.
		:return: True if the packet got handled, False otherwise.
		"""
        CCC = packet.readCode()
        if CCC == (1, 1):  # Old packets
            oldCCC, *data = packet.readString().split(b'\x01')
            data = list(map(bytes.decode, data))
            oldCCC = tuple(oldCCC[:2])

            # :desc: Called when an old packet is received. Does not interfere
            # with :meth:`Client.handle_old_packet`.
            # :param connection: :class:`aiotfm.Connection` the connection that received
            # the packet.
            # :param oldCCC: :class:`tuple` the packet identifiers on the old protocol.
            # :param data: :class:`list` the packet data.
            self.dispatch('old_packet', connection, oldCCC, data)
            return await self.handle_old_packet(connection, oldCCC, data)

        if CCC == (5, 21):  # Joined room
            self.room = Room(official=packet.readBool(), name=packet.readUTF())

            # :desc: Called when the client has joined a room.
            # :param room: :class:`aiotfm.room.Room` the room the client has entered.
            self.dispatch('joined_room', self.room)

        elif CCC == (5, 39):  # Password required for the room

            # :desc: Called when a password is required to enter a room
            # :param room: :class:`aiotfm.room.Room` the room the server is asking for a password.
            self.dispatch('room_password', Room(packet.readUTF()))

        elif CCC == (6, 6):  # Room message
            username = packet.readUTF()
            message = packet.readUTF()
            player = self.room.get_player(username=username)

            if player is None:
                player = Player(username)

            # :desc: Called when the client receives a message from the room.
            # :param message: :class:`aiotfm.message.Message` the message.
            self.dispatch('room_message', Message(player, message, self))

        elif CCC == (6, 20):  # Server message
            packet.readBool(
            )  # if False then the message will appear in the #Server channel
            t_key = packet.readUTF()
            t_args = [packet.readUTF() for i in range(packet.read8())]

            # :desc: Called when the client receives a message from the server that needs to be translated.
            # :param message: :class:`aiotfm.locale.Translation` the message translated with the
            # current locale.
            # :param *args: a list of string used as replacement inside the message.
            self.dispatch('server_message', self.locale[t_key], *t_args)

        elif CCC == (8, 1):  # Play emote
            player = self.room.get_player(pid=packet.read32())
            emote = packet.read8()
            flag = packet.readUTF() if emote == 10 else ''

            # :desc: Called when a player plays an emote.
            # :param player: :class:`aiotfm.Player` the player.
            # :param emote: :class:`int` the emote's id.
            # :param flag: :class:`str` the flag's id.
            self.dispatch('emote', player, emote, flag)

        elif CCC == (8, 5):  # Show emoji
            player = self.room.get_player(pid=packet.read32())
            emoji = packet.read8()

            # :desc: Called when a player is showing an emoji above its head.
            # :param player: :class:`aiotfm.Player` the player.
            # :param emoji: :class:`int` the emoji's id.
            self.dispatch('emoji', player, emoji)

        elif CCC == (8, 6):  # Player won
            packet.read8()
            player = self.room.get_player(pid=packet.read32())
            player.score = packet.read16()
            order = packet.read8()
            player_time = packet.read16() / 100

            # :desc: Called when a player get the cheese to the hole.
            # :param player: :class:`aiotfm.Player` the player.
            # :param order: :class:`int` the order of the player in the hole.
            # :param player_time: :class:`float` player's time in the hole in seconds.
            self.dispatch('player_won', player, order, player_time)

        elif CCC == (8, 16):  # Profile
            # :desc: Called when the client receives the result of a /profile command.
            # :param profile: :class:`aiotfm.player.Profile` the profile.
            self.dispatch('profile', Profile(packet))

        elif CCC == (8, 20):  # Shop
            # :desc: Called when the client receives the content of the shop.
            # :param shop: :class:`aiotfm.shop.Shop` the shop.
            self.dispatch('shop', Shop(packet))

        elif CCC == (8, 22):  # Skills
            skills = {}
            for _ in range(packet.read8()):
                key, value = packet.read8(), packet.read8()
                skills[key] = value

            # :desc: Called when the client receives its skill tree.
            # :param skills: :class:`dict` the skills.
            self.dispatch('skills', skills)

        elif CCC == (16, 2):  # Tribe invitation received
            author = packet.readUTF()
            tribe = packet.readUTF()

            # :desc: Called when the client receives an invitation to a tribe. (/inv)
            # :param author: :class:`str` the player that invited you.
            # :param tribe: :class:`str` the tribe.
            self.dispatch('tribe_inv', author, tribe)

        elif CCC == (26, 2):  # Logged in successfully
            player_id = packet.read32()
            self.username = username = packet.readUTF()
            played_time = packet.read32()
            community = Community(packet.read8())
            pid = packet.read32()

            # :desc: Called when the client successfully logged in.
            # :param uid: :class:`int` the client's unique id.
            # :param username: :class:`str` the client's username.
            # :param played_time: :class:`int` the total number of minutes the client has played.
            # :param community: :class:`aiotfm.enums.Community` the community the client has connected to.
            # :param pid: :class:`int` the client's player id.
            self.dispatch('logged', player_id, username, played_time,
                          community, pid)

        elif CCC == (26, 3):  # Handshake OK
            online_players = packet.read32()
            language = packet.readUTF()
            country = packet.readUTF()
            self.authkey = packet.read32()

            self.loop.create_task(self._heartbeat_loop())

            await connection.send(Packet.new(176, 2).writeUTF(language))

            os_info = Packet.new(28, 17).writeString('en').writeString('Linux')
            os_info.writeString('LNX 29,0,0,140').write8(0)

            await connection.send(os_info)

            # :desc: Called when the client can login through the game.
            # :param online_players: :class:`int` the number of player connected to the game.
            # :param language: :class:`str` the language the server is suggesting.
            # :param country: :class:`str` the country detected from your ip.
            self.dispatch('login_ready', online_players, language, country)

        elif CCC == (26, 12):  # Login result
            # :desc: Called when the client failed logging.
            # :param code: :class:`int` the error code.
            # :param error1: :class:`str` error messages.
            # :param error2: :class:`str` error messages.
            self.dispatch('login_result', packet.read8(), packet.readUTF(),
                          packet.readUTF())

        elif CCC == (26, 25):  # Ping
            # :desc: Called when the client receives the ping response from the server.
            self.dispatch('ping')

        elif CCC == (26, 35):  # Room list
            roomlist = RoomList.from_packet(packet)
            # :desc: Dispatched when the client receives the room list
            self.dispatch('room_list', roomlist)

        elif CCC == (28, 6):  # Server ping
            await connection.send(Packet.new(28, 6).write8(packet.read8()))

        elif CCC == (29, 6):  # Lua logs
            # :desc: Called when the client receives lua logs from #Lua.
            # :param log: :class:`str` a log message.
            self.dispatch('lua_log', packet.readUTF())

        elif CCC == (31, 1):  # Inventory data
            self.inventory = Inventory.from_packet(packet)
            self.inventory.client = self

            # :desc: Called when the client receives its inventory's content.
            # :param inventory: :class:`aiotfm.inventory.Inventory` the client's inventory.
            self.dispatch('inventory_update', self.inventory)

        elif CCC == (31, 2):  # Update inventory item
            item_id = packet.read16()
            quantity = packet.read8()

            if item_id in self.inventory.items:
                item = self.inventory.items[item_id]
                previous = item.quantity
                item.quantity = quantity

                # :desc: Called when the quantity of an item has been updated.
                # :param item: :class:`aiotfm.inventory.InventoryItem` the new item.
                # :param previous: :class:`aiotfm.inventory.InventoryItem` the previous item.
                self.dispatch('item_update', item, previous)

            else:
                item = InventoryItem(item_id=item_id, quantity=quantity)
                self.inventory.items[item.id] = item

                # :desc: Called when the client receives a new item in its inventory.
                # :param item: :class:`aiotfm.inventory.InventoryItem` the new item.
                self.dispatch('new_item', item)

        elif CCC == (31, 5):  # Trade invite
            pid = packet.read32()

            self.trades[pid] = Trade(self, self.room.get_player(pid=pid))

            # :desc: Called when received an invitation to trade.
            # :param trade: :class:`aiotfm.inventory.Trade` the trade object.
            self.dispatch('trade_invite', self.trades[pid])

        elif CCC == (31, 6):  # Trade error
            name = packet.readUTF().lower()
            error = packet.read8()

            if name == self.username.lower():
                trade = self.trade
            else:
                for t in self.trades.values():
                    if t.trader.lower() == name:
                        trade = t
                        break

            # :desc: Called when an error occurred with a trade.
            # :param trade: :class:`aiotfm.inventory.Trade` the trade that failed.
            # :param error: :class:`aiotfm.enums.TradeError` the error.
            self.dispatch('trade_error', trade, TradeError[error])
            trade._close()

        elif CCC == (31, 7):  # Trade start
            pid = packet.read32()
            trade = self.trades.get(pid)

            if trade is None:
                raise AiotfmException(f'Cannot find the trade from pid {pid}.')

            trade._start()
            self.trade = trade

            # :desc: Called when a trade starts. You can access the trade object with `Client.trade`.
            self.dispatch('trade_start')

        elif CCC == (31, 8):  # Trade items
            export = packet.readBool()
            id_ = packet.read16()
            quantity = (1 if packet.readBool() else -1) * packet.read8()

            items = self.trade.exports if export else self.trade.imports
            items.add(id_, quantity)

            trader = self if export else self.trade.trader
            self.trade.locked = [False, False]

            # :desc: Called when an item has been added/removed from the current trade.
            # :param trader: :class:`aiotfm.Player` the player that triggered the event.
            # :param id: :class:`int` the item's id.
            # :param quantity: :class:`int` the quantity added/removed. Can be negative.
            # :param item: :class:`aiotfm.inventory.InventoryItem` the item after the change.
            self.dispatch('trade_item_change', trader, id_, quantity,
                          items.get(id_))

        elif CCC == (31, 9):  # Trade lock
            index = packet.read8()
            locked = packet.readBool()
            if index > 1:
                self.trade.locked = [locked, locked]
                who = "both"
            else:
                self.trade.locked[index] = locked
                who = self.trade.trader if index == 0 else self

            # :desc: Called when the trade got (un)locked.
            # :param who: :class:`aiotfm.Player` the player that triggered the event.
            # :param locked: :class:`bool` either the trade got locked or unlocked.
            self.dispatch('trade_lock', who, locked)

        elif CCC == (31, 10):  # Trade complete
            trade = self.trade
            self.trade._close(succeed=True)

        elif CCC == (44, 1):  # Bulle switching
            timestamp = packet.read32()
            uid = packet.read32()
            pid = packet.read32()
            bulle_ip = packet.readUTF()
            ports = packet.readUTF().split('-')

            if self.bulle is not None:
                self.bulle.close()

            self.bulle = Connection('bulle', self, self.loop)
            await self.bulle.connect(bulle_ip, random.choice(ports))
            await self.bulle.send(
                Packet.new(44, 1).write32(timestamp).write32(uid).write32(pid))

        elif CCC == (44, 22):  # Fingerprint offset changed
            connection.fingerprint = packet.read8()

        elif CCC == (60, 3):  # Community platform
            TC = packet.read16()

            # :desc: Called when the client receives a packet from the community platform.
            # :param TC: :class:`int` the packet's code.
            # :param packet: :class:`aiotfm.Packet` the packet.
            self.dispatch('raw_cp', TC, packet.copy(copy_pos=True))

            if TC == 3:  # Connected to the community platform
                # :desc: Called when the client is successfully connected to the community platform.
                self.dispatch('ready')

            elif TC == 55:  # Channel join result
                sequenceId = packet.read32()
                result = packet.read8()

                # :desc: Called when the client receives the result of joining a channel.
                # :param sequenceId: :class:`int` identifier returned by :meth:`Client.sendCP`.
                # :param result: :class:`int` result code.
                self.dispatch('channel_joined_result', sequenceId, result)

            elif TC == 57:  # Channel leave result
                sequenceId = packet.read32()
                result = packet.read8()

                # :desc: Called when the client receives the result of leaving a channel.
                # :param sequenceId: :class:`int` identifier returned by :meth:`Client.sendCP`.
                # :param result: :class:`int` result code.
                self.dispatch('channel_left_result', sequenceId, result)

            elif TC == 59:  # Channel /who result
                idSequence = packet.read32()
                result = packet.read8()
                players = [
                    Player(packet.readUTF()) for _ in range(packet.read16())
                ]

                # :desc: Called when the client receives the result of the /who command in a channel.
                # :param idSequence: :class:`int` the reference to the packet that performed the request.
                # :param players: List[:class:`aiotfm.Player`] the list of players inside the channel.
                self.dispatch('channel_who', idSequence, players)

            elif TC == 62:  # Joined a channel
                name = packet.readUTF()

                if name in self._channels:
                    channel = [c for c in self._channels if c == name][0]
                else:
                    channel = Channel(name, self)
                    self._channels.append(channel)

                # :desc: Called when the client joined a channel.
                # :param channel: :class:`aiotfm.message.Channel` the channel.
                self.dispatch('channel_joined', channel)

            elif TC == 63:  # Quit a channel
                name = packet.readUTF()
                if name in self._channels:
                    self._channels.remove(name)

                # :desc: Called when the client leaves a channel.
                # :param name: :class:`str` the channel's name.
                self.dispatch('channel_closed', name)

            elif TC == 64:  # Channel message
                username, community = packet.readUTF(), packet.read32()
                channel_name, message = packet.readUTF(), packet.readUTF()
                channel = self.get_channel(channel_name)
                author = self.room.get_player(username=username)

                if author is None:
                    author = Player(username)

                if channel is None:
                    channel = Channel(channel_name, self)
                    self._channels.append(channel)

                channel_message = ChannelMessage(author, community, message,
                                                 channel)

                # :desc: Called when the client receives a message from a channel.
                # :param message: :class:`aiotfm.message.ChannelMessage` the message.
                self.dispatch('channel_message', channel_message)

            elif TC == 65:  # Tribe message
                author, message = packet.readUTF(), packet.readUTF()

                # :desc: Called when the client receives a message from the tribe.
                # :param author: :class:`str` the message's author.
                # :param message: :class:`str` the message's content.
                self.dispatch('tribe_message', author, message)

            elif TC == 66:  # Whisper
                author = Player(packet.readUTF())
                commu = packet.read32()
                receiver = Player(packet.readUTF())
                message = packet.readUTF()

                author = self.room.get_player(name=author, default=author)
                receiver = self.room.get_player(name=receiver,
                                                default=receiver)

                # :desc: Called when the client receives a whisper.
                # :param message: :class:`aiotfm.message.Whisper` the message.
                self.dispatch('whisper',
                              Whisper(author, commu, receiver, message, self))

            elif TC == 88:  # tribe member connected
                # :desc: Called when a tribe member connected.
                # :param name: :class:`str` the member's name.
                self.dispatch('member_connected', packet.readUTF())

            elif TC == 90:  # tribe member disconnected
                # :desc: Called when a tribe member disconnected.
                # :param name: :class:`str` the member's name.
                self.dispatch('member_disconnected', packet.readUTF())

            else:
                if self.LOG_UNHANDLED_PACKETS:
                    print(CCC, TC, bytes(packet.buffer)[4:])
                return False

        elif CCC == (100, 67):  # New inventory item
            slot = packet.read8()
            slot = None if slot == 0 else slot
            item_id = packet.read16()
            quantity = packet.read8()

            item = InventoryItem(item_id=item_id, quantity=quantity, slot=slot)
            self.inventory[item_id] = item
            self.dispatch('new_item', item)

        elif CCC == (144, 1):  # Set player list
            before = self.room.players
            self.room.players = {}

            for _ in range(packet.read16()):
                player = Player.from_packet(packet)
                self.room.players[player.pid] = player

            # :desc: Called when the client receives an update of all player in the room.
            # :param before: Dict[:class:`aiotfm.Player`] the list of player before the update.
            # :param players: Dict[:class:`aiotfm.Player`] the list of player updated.
            self.dispatch('bulk_player_update', before, self.room.players)

        elif CCC == (144, 2):  # Add a player
            after = Player.from_packet(packet)
            before = self.room.players.pop(after.pid, None)

            self.room.players[after.pid] = after
            if before is None:
                # :desc: Called when a player joined the room.
                # :param player: :class:`aiotfm.Player` the player.
                self.dispatch('player_join', after)
            else:
                # :desc: Called when a player's data on the room has been updated.
                # :param before: :class:`aiotfm.Player` the player before the update.
                # :param player: :class:`aiotfm.Player` the player updated.
                self.dispatch('player_update', before, after)

        else:
            if self.LOG_UNHANDLED_PACKETS:
                print(CCC, bytes(packet.buffer)[2:])
            return False

        return True