def __init__(self, community=Community.en, auto_restart=False, loop=None): self.loop = loop or asyncio.get_event_loop() self.main = Connection('main', self, self.loop) self.bulle = None self._waiters = {} self.room = None self.trade = None self.trades = {} self.inventory = None self.username = None self.locale = Locale() self.community = Community(community) self.cp_fingerprint = 0 self.keys = None self.authkey = 0 self.auto_restart = auto_restart self.api_tfmid = None self.api_token = None self._channels = []
async def commu(ctx, commu): if str(ctx.author) in client_admins: commu = str(commu) try: try: client.community = Community(int(commu)) except ValueError: client.community = Community[commu] except KeyError: client.community = Community.br loop.create_task(client.restart())
async def joinRoom(self, room_name, password=None, community=None, auto=False): """|coro| Join a room. The event 'on_joined_room' is dispatched when the client has successfully joined the room. :param password: :class:`str` if given the client will ignore `community` and `auto` parameters and will connect to the room with the given password. :param room_name: :class:`str` the room's name. :param community: Optional[:class:`int`] the room's community. :param auto: Optional[:class:`bool`] joins a random room (I think). """ if password is not None: packet = Packet.new(5, 39).writeString(password).writeString(room_name) else: packet = Packet.new(5, 38).write8(Community(community or self.community).value) packet.writeString(room_name).writeBool(auto) await self.main.send(packet)
async def on_whisper(self, _message): author = str(_message.author) commu = ChatCommunity(_message.community).name message = _message.content args = message.split() print(f"[Whisper][{commu}][{author}] {message}") # Whispers commands if author in client_admins: level = client_admins[author] # Level 0 Admins only if level == 0: if args[0] == "!ban": if len(args) > 1: await self.sendCommand('ban ' + args[1]) return elif args[0] == "!target": if len(args) > 1: self.elim_target = args[1] return elif args[0] == '!press6': await self.sendSmiley(5) return elif args[0] == '!press3': await self.sendSmiley(2) return elif args[0] == '!dance': await self.playEmote(0) return elif args[0] == "!callback": self.can_press_callback = not self.can_press_callback return elif args[0] == "!room": if len(args) > 1: room = ' '.join(args[1:]) return await self.joinRoom(room) return await _message.reply(self._room.name) elif args[0] == "!pwroom": if len(args) == 1: return return await self.joinRoom(" ".join(args[2:]), args[1]) elif args[0] == "!canplay": self.can_play, self.is_playing = not self.can_play, False return self.cancel_mov_task() return await self._mort() elif args[0] == "!candie": self.can_die = not self.can_die return elif args[0] == "!cheese": return await self._cheese() elif args[0] == "!win": return await self._win() elif args[0] == "!del": code = str(self._room.map.code) if len(args) > 1: code = args[1] return await self.del_map(author, code) elif args[0] == "!inv": self.can_accept_inv = not self.can_accept_inv return # Level 0+ Admins if level >= 0: if args[0] == "!lua": if len(args) > 1: if "pastebin" not in args[1]: return await _message.reply("[Error] Code must be hosted on Pastebin") await self.run_code(args[1]) return elif args[0] in ["!come", "!follow", "!seg"]: target = author if len(args) > 1: target = args[1] tribe = await self.getTribe(disconnected=False) if tribe is not None: member = tribe.get_member(target) if member is not None and member.room is not None: return await self.joinRoom(member.room) if self.friends is not None: friend = self.friends.get_friend(target) if friend is not None and friend.room is not None: return await self.joinRoom(friend.room) return await _message.reply("Player not found") elif args[0] == "!commu": commu = 1 if len(args) > 1: try: commu = Community(int(args[1])) except: commu = Community[args[1]] self.community = commu return loop.create_task(self.restart()) elif args[0] == "!say": return await self.sendRoomMessage(" ".join(args[1:])) elif args[0] in ["!th", "!house"]: return await self.enterTribe() elif args[0] == "!lua": if len(args) == 1: return return await self.run_code(args[1]) elif args[0] == "!pm": if len(args) == 1: return return await self.whisper(args[1], " ".join(args[2:])) elif args[0] == "!tm": if len(args) == 1: return return await self.sendTribeMessage(" ".join(args[1:])) elif args[0] == "!mort": return await self._mort()
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 player_id = packet.read32() username = packet.readUTF() commu = packet.read8() message = packet.readUTF() player = self.room.get_player(pid=player_id) if player is None: player = Player(username, pid=player_id) # :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, 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())] # :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, 5): # Show emoji player = self.room.get_player(pid=packet.read32()) emoji = packet.read8() # :desc: Called 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 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() connection.fingerprint = packet.read8() community = Community[packet.readUTF()] country = packet.readUTF() self.authkey = packet.read32() self.loop.create_task(self._heartbeat_loop()) await connection.send(Packet.new(8, 2).write8(self.community.value).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) # :desc: Called when the client can login through the game. # :param online_players: :class:`int` the number of player connected to the game. # :param community: :class:`aiotfm.enums.Community` the community the server suggest. # :param country: :class:`str` the country detected from your ip. self.dispatch('login_ready', online_players, community, country) elif CCC == (26, 12): # Login result # :desc: Called when the client failed logging. # :param code: :class:`int` the error code. # :param code: :class:`str` error messages. # :param code: :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 == (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() player_id = 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(*CCC).write32(timestamp).write32(player_id)) 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 result = packet.read8() # :desc: Called when the client receives the result of joining a channel. # :param result: :class:`int` result code. self.dispatch('channel_joined_result', result) elif TC == 57: # Channel leave result result = packet.read8() # :desc: Called when the client receives the result of leaving a channel. # :param result: :class:`int` result code. 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())] # :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 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) 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