def main(): options = get_options() auth_token = authentication.AuthenticationToken() try: auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() connection = Connection(options.address, options.port, auth_token) connection.connect() print("Logged in as " + auth_token.username) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("", 9001)) s.listen(5) connection.register_packet_listener(print_chat, ChatMessageClientboundPacket) #send_message("GET http://www.google.com/ HTTP/1.1\r\nhost: www.google.com\r\n\r\n", "address", connection) #GET http://www.uga.edu/ HTTP/1.1\r\nHost: www.uga.edu\r\n #time.sleep(100) #sys.exit() while True: try: (conn, addr) = s.accept() print("got packet") data = conn.recv(8192) send_message(data, addr, connection) except KeyboardInterrupt: sys.exit() s.close()
def __init__(self, username, password=None, server=None, versions=("1.12.2", "1.12.2"), auto_connect=False): self.username = username self.password = password self.server = server self.versions = versions self.event = lambda x: print(x) self.auth_token = authentication.AuthenticationToken( username=self.username, access_token=self.password) print("authenticated: %s" % self.auth_token.authenticate( self.username, self.password, invalidate_previous=False)) self.connection = Connection(self.server, auth_token=self.auth_token, allowed_versions=self.versions) self.connection.register_packet_listener(lambda x: self.print_chat(x), ChatMessagePacket) print(self.auth_token) print(self.connection) if auto_connect: self._loop_reconect()
def connect(self): self.connection = Connection(self.ip, self.port, auth_token=auth_token, handle_exception=self.exception_handler) print("connecting to", self.ip) self.connection.register_packet_listener( self.handle_join_game, clientbound.play.JoinGamePacket) self.connection.register_packet_listener( self.on_login_disconnect_packet, clientbound.play.DisconnectPacket, early=True) # self.connection.register_packet_listener(self.print_packet, Packet, early=True) self.connection.register_packet_listener( self.on_login_disconnect_packet, clientbound.login.DisconnectPacket, early=True) with timeout(2): self.connection.connect() start = time.time() while not self.done: time.sleep(0.01) now = time.time() diff = now - start if diff > 2: self.done = True
def run(self): self.logger.debug( "Checking if the server {} is online before connecting.") if not self.config.mc_online: self.logger.info("Connecting in offline mode...") while not self.is_server_online(): self.logger.info( 'Not connecting to server because it appears to be offline.' ) time.sleep(15) self.bot_username = self.config.mc_username self.connection = Connection( self.config.mc_server, self.config.mc_port, username=self.config.mc_username, handle_exception=self.minecraft_handle_exception) else: self.auth_token = authentication.AuthenticationToken() try: self.auth_token.authenticate(self.config.mc_username, self.config.mc_password) except YggdrasilError as ex: self.logger.info(ex) sys.exit(os.EX_TEMPFAIL) self.bot_username = self.auth_token.profile.name self.logger.info("Logged in as %s...", self.auth_token.profile.name) while not self.is_server_online(): self.logger.info( 'Not connecting to server because it appears to be offline.' ) time.sleep(15) self.connection = Connection( self.config.mc_server, self.config.mc_port, auth_token=self.auth_token, handle_exception=self.minecraft_handle_exception) self.register_handlers(self.connection) self.connection_retries += 1 self.reactor_thread.start() self.connection.connect() try: self.aioloop.run_until_complete( self.discord_bot.start(self.config.discord_token)) except (KeyboardInterrupt, SystemExit): # log out of discord self.aioloop.run_until_complete(self.discord_bot.logout()) # log out of minecraft self.connection.disconnect() # shut down auth server from twisted.internet import reactor reactor.callFromThread(reactor.stop) # clean up auth server thread self.reactor_thread.join() finally: # close the asyncio event loop discord uses self.aioloop.close() return self.return_code
def main(): username = '******' # connection = Connection(address, port, username='******'.format(random.randint(0, 1000))) connection = Connection(address, port, username=username) bot = Bot(connection, username) def print_outgoing(packet): if type(packet) is not my_svbnd_play.DiggingPacket: print('<-- %s' % packet, file=sys.stderr) connection.register_packet_listener( bot.process_packet, Packet) connection.register_packet_listener( print_outgoing, Packet, outgoing=True) def handle_join_game(join_game_packet, early=True): print('Connected.') connection.register_packet_listener( handle_join_game, clientbound.play.JoinGamePacket) connection.connect() while True: # making a loop try: # used try so that if user pressed other than the given key error will not be shown a = input() except Exception as e: # print(e) pass #
def SetServer(self, ip, port=25565, handler=None): """ Sets the server, ready for connection Parameters ---------- ip : str The server to connect to port : int, optional The port to connect on handler : Function pointer, optional Points to the function used to handle Clientbound chat packets """ handler = handler or self.ReceiveChat self.ip = ip self.port = port self.connection = Connection( ip, port, auth_token=self.auth_token, handle_exception=print ) self.connection.register_packet_listener( handler, clientbound.play.ChatMessagePacket ) self.connection.exception_handler(print)
def create_conn(address, port, version, token): mc_conn = Connection( address=address, port=port, auth_token=token, initial_version=version, allowed_versions=[version], ) mc_conn.connect() return mc_conn
def __init__(self, username, password, bot_ign, reply_rate=20, whitelist=False): self.username = username self.password = password self.bot_ign = bot_ign self.debug = False self.whitelist = whitelist self.reply_rate = int(reply_rate) self.auth_token = authentication.AuthenticationToken() try: self.auth_token.authenticate(self.username, self.password) except YggdrasilError as error: print(error) exit() print("Logged in as %s." % self.auth_token.username) self.connection = Connection("mc.hypixel.net", 25565, auth_token=self.auth_token) self.command_delay = 0 self.msgQueue = [] self.partyQueue = [] self.commandQueue = [] self.msgCurrentChannel = "" self.party = {"inP": False, "from": "", "timestamp": 0} self.partyConfig = {} self.playercooldown = {} self.cooldownTimer = time.time() self.heartbeat = time.time() + 120 self.heartbeatCooldown = time.time() + 120 self.msgformat = msgformat.formats(self.bot_ign, 24) self.bots = {x: 0 for x in msgformat.bots if x != self.bot_ign} self.current_load = 0 self.inQueue = False self.inQueueTime = 0 self.muted = False self.muteDuration = 3600 self.unmutetime = 0 self.muteheartbeat = 0 self.leaderBuffer = [] self.mods = [] self.whitelisted = [] try: with open("whitelisted.txt", "r") as file: self.whitelisted = [x for x in file.read().split("\n")] except Exception: self.whitelisted = [] print("whitelisted loaded", len(self.whitelisted))
def __init__(self, username, server, port, commands): self.username = username self.server = server self.port = port self.commands = commands self.bot = Connection(server, port, username=username, allowed_versions=[47]) self.bot.register_packet_listener(self.handle_join_game, clientbound.play.JoinGamePacket) log("INFO", "Trying to connect {0} to {1}:{2}.".format(username, server, port)) self.bot.connect() threading.Thread(target=self.execute_go, args=["/go"], daemon=True).start()
def main(): options = get_options() auth_token = authentication.AuthenticationToken() try: auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() print("Logged in as " + auth_token.username) connection = Connection(options.address, options.port, auth_token) connection.connect() def print_chat(chat_packet): print("Position: " + str(chat_packet.position)) print("Data: " + chat_packet.json_data) connection.register_packet_listener(print_chat, ChatMessagePacket) while True: try: text = input() packet = ChatPacket() packet.message = text connection.write_packet(packet) except KeyboardInterrupt: print("Bye!") sys.exit()
class MinecraftBot(): def __init__(self, username, server, port, commands): self.username = username self.server = server self.port = port self.commands = commands self.bot = Connection(server, port, username=username, allowed_versions=[47]) self.bot.register_packet_listener(self.handle_join_game, clientbound.play.JoinGamePacket) log("INFO", "Trying to connect {0} to {1}:{2}.".format(username, server, port)) self.bot.connect() threading.Thread(target=self.execute_go, args=["/go"], daemon=True).start() def execute_go(self, command): time.sleep(15) self.execute_command(command) def handle_join_game(self, join_game_packet): log("INFO", "{0} is connected to {1}:{2}.".format(self.username, self.server, self.port)) time.sleep(3) self.execute_command(self.commands[0]) def execute_command(self, command): log("INFO", "{0} is doing command {1}".format(self.username, command)) packet = serverbound.play.ChatPacket() packet.message = command self.bot.write_packet(packet) def disconnect(self): log("INFO", "Disconnecting {0} from {1}:{2}.".format(self.username, self.server, self.port)) self.bot.disconnect() log("INFO", "{0} is disconnected of {1}:{2}.".format(self.username, self.server, self.port))
def main(): options = get_options() if options.offline: print("Connecting in offline mode") connection = Connection( options.address, options.port, username=options.username) else: auth_token = authentication.AuthenticationToken() try: auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() print("Logged in as " + auth_token.username) connection = Connection( options.address, options.port, auth_token=auth_token) connection.connect() def print_chat(chat_packet): print("Position: " + str(chat_packet.position)) print("Data: " + chat_packet.json_data) connection.register_packet_listener(print_chat, ChatMessagePacket) while True: try: text = input() packet = ChatPacket() packet.message = text connection.write_packet(packet) except KeyboardInterrupt: print("Bye!") sys.exit()
def __init__(self, terminal: Terminal, options: object): """ Parameters ---------- terminal : Terminal class representing and managing the terminal and it's subwindows options : object options parsed by the argument parser """ self._terminal = terminal auth_token = authentication.AuthenticationToken() if options.auth_type == 'Mojang': try: auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() # TODO Use correct status and not default to 0 as it's a failure self._client = Client(auth_token.profile.name, auth_token.profile.id_) self._terminal.info.update(self._client.name, util.format_uuid(self._client.uuid)) self._terminal.console.log('Successfully authenticated') self._address = options.address self._port = options.port self._connection = Connection(self._address, self._port, auth_token=auth_token) if hasattr(options, 'Bot'): self._bot = options.Bot(self._terminal, self._connection, self._client) else: self._bot = None self._register_listeners() self._connection.connect() while (True): key = self._terminal.stdscr.getkey() if (key == 'q'): sys.exit(0) if (key == 'KEY_UP'): self._terminal.console.scroll_up() elif (key == 'KEY_DOWN'): self._terminal.console.scroll_down() else: if hasattr(self._bot, 'keys') and key in self._bot.keys and inspect.ismethod(self._bot.keys[key]): self._bot.keys[key]()
def startChat(update, context): usr = "******" + str(update.message.from_user.first_name) connection = Connection("ioya.de", username=usr) context.user_data["connection"] = connection id = update.effective_chat.id def handle_join_game(join_game_packet): print("handle_join_Game") context.bot.send_message(chat_id=id, text='Connected to Corona Land.') connection.register_packet_listener(handle_join_game, clientbound.play.JoinGamePacket) def print_chat(chat_packet): msg = json.loads(chat_packet.json_data) translate = msg["translate"] if translate == "chat.type.text": name = msg["with"][0]["text"] content = msg["with"][1]["text"] if name != usr: context.bot.send_message(chat_id=update.effective_chat.id, text="%s: %s" % (name, content)) connection.register_packet_listener(print_chat, clientbound.play.ChatMessagePacket) connection.connect() return SENDMESSAGE
def main(): config = read_config() # split port and host match = re.match( r"((?P<host>[^\[\]:]+)|\[(?P<addr>[^\[\]]+)\])" r"(:(?P<port>\d+))?$", config["server"]) if match is None: raise ValueError(f"Invalid server address: '{config['server']}'") address = match.group("host") or match.group("addr") port = int(match.group("port") or 25565) auth_token = authentication.AuthenticationToken() try: auth_token.authenticate(config["username"], config["password"]) except YggdrasilError as e: print(e) sys.exit() print(f"Authenticated successfully as {auth_token.username}") connection = Connection(address, port, auth_token=auth_token) def handle_goodbye(signum, frame): print("Signing out!") payload = { 'username': config["username"], 'password': config["password"] } try: authentication._make_request(authentication.AUTH_SERVER, "signout", payload) except: print("Failed to sign out with Yggdrasil") finally: sys.exit() def handle_disconnect(): print("Disconnected from server") if config["reconnect"] == True: connection.connect() else: sys.exit() connection.register_packet_listener( lambda packet: print(f"Connected to {address}!"), clientbound.play.JoinGamePacket) connection.register_packet_listener(handle_disconnect, clientbound.login.DisconnectPacket) try: connection.connect() except Exception as err: print(err) print("Failed to connect to specified server") sys.exit() signal.signal(signal.SIGINT, handle_goodbye) while True: time.sleep(1)
def connect(self): global viewing if viewing is None: viewing = self.email Connect.connections[str(self.authtoken.username)] = Connection(self.ip, self.port, auth_token=self.authtoken) connection = Connect.connections[str(self.authtoken.username)] def onjoin(packet): print(f"{Colors.colors['green']}[+] Connected as {self.authtoken.username}") time.sleep(Config.onjoin_wait_time) p = serverbound.play.ChatPacket() p.message = Config.onjoin_command connection.write_packet(p) connection.register_packet_listener(onjoin, clientbound.play.JoinGamePacket) c = Chat(self.email) connection.register_packet_listener(c.handleChatMessage, clientbound.play.ChatMessagePacket) if Config.autoreconnect_on: d = Disconnect(self.email, self.passw) connection.register_packet_listener(d.handleDisconnect, clientbound.play.DisconnectPacket) connection.connect()
def main(): options = get_options() auth_token = authentication.AuthenticationToken() try: # Uncomment line to either authenticate to mojang, or create a fake auth token for offline servers. auth_token.authenticate(options.username, options.password) # auth_token.fake_authenticate(options.username) except YggdrasilError as e: print(e) sys.exit() print("Logged in as " + auth_token.username) connection = Connection(options.address, options.port, auth_token) connection.connect() def print_chat(chat_packet): print("Position: " + str(chat_packet.position)) print("Data: " + chat_packet.json_data) connection.register_packet_listener(print_chat, ChatMessagePacket) while True: try: text = input() packet = ChatPacket() packet.message = text connection.write_packet(packet) except KeyboardInterrupt: print("Bye!") sys.exit()
def __init__(self, account: str, password: str, server_address: str, port: int, version: int, auto_reconnect: bool, auto_respawn: bool, lang: Lang): self.__email = account self.__password = base64.b64encode(password.encode()) self.__lang = lang self.__logger = logging.getLogger("Auth") logging.basicConfig(level=logging.INFO) tokens = self.__get_tokens() self.__auth = authentication.AuthenticationToken( username=self.__email, access_token=tokens["access"], client_token=tokens["client"]) self.auth() self.__auto_reconnect = auto_reconnect self.__auto_respawn = auto_respawn self.__connection = Connection(address=server_address, port=port, initial_version=version, auth_token=self.__auth) if not self.__auth.authenticated: return self.username = self.__auth.profile.name self.__logger = logging.getLogger(self.username) self.__connection.register_packet_listener( self.handle_join_game, clientbound.play.JoinGamePacket) self.__connection.register_packet_listener( self.print_chat, clientbound.play.ChatMessagePacket) self.__connection.register_packet_listener( self.handle_disconnect, clientbound.play.DisconnectPacket) self.__connection.register_packet_listener( self.handle_health_change, clientbound.play.UpdateHealthPacket) self.__connection.register_exception_handler(self.handle_exception) try: self.__connection.connect() except Exception as e: self.__logger.error(str(e)) self.__retry()
def __init__(self, options): self.auth_token = authentication.AuthenticationToken() try: self.auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() print("Logged in as " + self.auth_token.username) self.network = Connection(options.address, options.port, self.auth_token) self.network.connect() self.register_listeners() #sys.stdout = Speaker(self) while not self.network.playing: pass self.respawn() while True: try: self.tick() except KeyboardInterrupt: print("Bye!") sys.exit()
def ServerWakeUp(): connection = Connection("ioya.de", username="******") def handle_join_game(join_game_packet): print('Connected.') connection.register_packet_listener(handle_join_game, clientbound.play.JoinGamePacket) def print_chat(chat_packet): print("Message (%s): %s" % (chat_packet.field_string('position'), chat_packet.json_data)) connection.register_packet_listener(print_chat, clientbound.play.ChatMessagePacket) connection.connect() sleep(2)
def make_connection(*args, **kwds): kwds['initial_version'] = self.earliest_version return Connection(*args, **kwds)
webhook_url = config['webhook'] ACCOUNT = config["mcAcc"].split(":") #ACCOUNT = custom_answers = config["custom"] auth_token = authentication.AuthenticationToken() #auth_token.authenticate(ACCOUNT[0], ACCOUNT[1]) while 1: try: auth_token.authenticate(ACCOUNT[0], ACCOUNT[1]) break except: print("waiting for mojang cooldown") time.sleep(10) print("Logged in as %s..." % auth_token.username) connection = Connection("gommehd.net", auth_token=auth_token) #connection = Connection("localhost", 62139, username="******") ''' def print_incoming(packet): if type(packet) is Packet: # This is a direct instance of the base Packet type, meaning # that it is a packet of unknown type, so we do not print it. return print('--> %s' % packet, file=sys.stderr) def print_outgoing(packet): print('<-- %s' % packet, file=sys.stderr)
def main(): options = get_options() if options.offline: print("Connecting in offline mode...") connection = Connection( options.address, options.port, username=options.username) else: auth_token = authentication.AuthenticationToken() try: auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() print("Logged in as %s..." % auth_token.username) connection = Connection( options.address, options.port, auth_token=auth_token) if options.dump_packets: def print_incoming(packet): if type(packet) is Packet: # This is a direct instance of the base Packet type, meaning # that it is a packet of unknown type, so we do not print it # unless explicitly requested by the user. if options.dump_unknown: print('--> [unknown packet] %s' % packet, file=sys.stderr) else: print('--> %s' % packet, file=sys.stderr) def print_outgoing(packet): print('<-- %s' % packet, file=sys.stderr) connection.register_packet_listener( print_incoming, Packet, early=True) connection.register_packet_listener( print_outgoing, Packet, outgoing=True) def handle_join_game(join_game_packet): print('Connected.') connection.register_packet_listener( handle_join_game, clientbound.play.JoinGamePacket) def print_chat(chat_packet): print("Message (%s): %s" % ( chat_packet.field_string('position'), chat_packet.json_data)) connection.register_packet_listener( print_chat, clientbound.play.ChatMessagePacket) def handle_zzz(chat_packet): if json.loads(chat_packet.json_data).get("with", " ")[-1] == "zzz": connection.disconnect() time.sleep(5) connection.connect() connection.register_packet_listener( handle_zzz, clientbound.play.ChatMessagePacket) connection.connect() while True: try: text = input() if text == "/respawn": print("respawning...") packet = serverbound.play.ClientStatusPacket() packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN connection.write_packet(packet) else: packet = serverbound.play.ChatPacket() packet.message = text connection.write_packet(packet) except KeyboardInterrupt: print("Bye!") sys.exit()
#playSong("thetop") if kb.is_pressed("ctrl+shift"): #MultiDimPrint("https://cdn.discordapp.com/attachments/255681374025941003/677868965674090526/wall.schematic", (219, 231, -155)) while kb.is_pressed("ctrl+shift"): pass if kb.is_pressed("f4"): exit() updatePosition() if __name__ == '__main__': # connect and register listeners dj = Connection("127.0.0.1", username="******") dj.connect() dj.register_packet_listener( _setPosition, clientbound.play.player_position_and_look_packet. PlayerPositionAndLookPacket) dj.register_packet_listener(_onChatMessage, clientbound.play.ChatMessagePacket) print("Connecting...") while pos is None: # wait for server to update dj's position pass print("Connected") sendChat("<3") main()
def make_connection(*args, **kwds): kwds["initial_version"] = self.lowest_version return Connection(*args, **kwds)
def main() -> int: # Handle program arguments ap = argparse.ArgumentParser( prog="mchat", description="A console chat client for most Minecraft server versions") ap.add_argument("server_address", help="IP address of a Minecraft server") ap.add_argument("-p", "--port", help="Minecraft server port (default: %(default)s)", type=int, default=25565) ap.add_argument("-u", "--username", help="Minecraft username or email") ap.add_argument( "-v", "--version", help="Client -> Server protocol version to use (default: %(default)s)", default="1.16.4") args = ap.parse_args() # Verify server version to keep the terminal looking clean if args.version not in SUPPORTED_MINECRAFT_VERSIONS.keys(): console.print( f"[bold yellow]{args.version} is not a valid Minecraft version. Versions from {list(SUPPORTED_MINECRAFT_VERSIONS.keys())[0]} to {list(SUPPORTED_MINECRAFT_VERSIONS.keys())[-1]} are allowed." ) return 1 # Do authentication if not args.username: username = Prompt.ask("Username or email") else: username = args.username password = getpass.getpass("Password: "******"[bright_black]Loaded authentication information") # Determine the actual protocol version number protocol_version_num = SUPPORTED_MINECRAFT_VERSIONS[args.version] console.print( f"[bright_black]Selecting protocol version {protocol_version_num}") # Authenticate with Mojang auth_token = AuthenticationToken() console.print(f"[bright_black]Contacting Yggdrasil...") try: auth_token.authenticate(username, password) except YggdrasilError as e: console.print(f"[bold red]Failed to authenticate Minecraft session") return 1 # Open a connection server_connection = Connection(args.server_address, args.port, auth_token, allowed_versions=[protocol_version_num]) try: server_connection.connect() except: console.print(f"[bold red]Could not connect to server") return 1 # Set up an incoming chat handler server_connection.register_packet_listener(incomingChatHandler, ChatMessagePacket) console.print(f"[bright_black]Listen to incoming chat packets") # Set up input loop console.print( "All further inputs will be sent to server chat. Press CTRL+C to stop") try: while True: # Get a line from the user chat_message = console.input() # Send the chat message packet = serverbound.play.ChatPacket() packet.message = chat_message server_connection.write_packet(packet) except KeyboardInterrupt as e: print("\rGoodbye") return 0
class Main(object): def __init__(self, options): self.auth_token = authentication.AuthenticationToken() try: self.auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() print("Logged in as " + self.auth_token.username) self.network = Connection(options.address, options.port, self.auth_token) self.network.connect() self.register_listeners() #sys.stdout = Speaker(self) while not self.network.playing: pass self.respawn() while True: try: self.tick() except KeyboardInterrupt: print("Bye!") sys.exit() def register_listeners(self): self.network.register_packet_listener(self.print_chat, ChatMessagePacket) self.network.register_packet_listener(self.set_pos, PlayerPositionAndLookPacket) self.network.register_packet_listener(self.recieve_plugin_message, PluginMessage) self.network.register_packet_listener(self.set_health, UpdateHealth) def tick(self): text = input() self.speak(text) def speak(self, message): packet = ChatPacket() packet.message = message self.network.write_packet(packet) def set_health(self, health_packet): if health_packet.health == 0.0: print "RESPAWN" self.respawn() def respawn(self): packet = ClientStatus() packet.action_id = 0 self.network.write_packet(packet) #print packet def print_chat(self, chat_packet): print type(chat_packet) print("Position: " + str(chat_packet.position)) print("Data: " + chat_packet.json_data) def recieve_plugin_message(self, plugin_message): data = plugin_message.data.split("|") if len(data) == 2 and data[0] == "MC": if data[1] == "Brand": print "Brand:", plugin_message.channel else: print "PLUGIN MESSAGE:", data[1], plugin_message.channel
def main(): options = get_options() if options.offline: print("Connecting in offline mode...") connection = Connection( options.address, options.port, username=options.username) else: auth_token = authentication.AuthenticationToken() try: auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() print("Logged in as %s..." % auth_token.username) connection = Connection( options.address, options.port, auth_token=auth_token) if options.dump_packets: def print_incoming(packet): if type(packet) is Packet: # This is a direct instance of the base Packet type, meaning # that it is a packet of unknown type, so we do not print it. return print('--> %s' % packet, file=sys.stderr) def print_outgoing(packet): print('<-- %s' % packet, file=sys.stderr) connection.register_packet_listener( print_incoming, Packet, early=True) connection.register_packet_listener( print_outgoing, Packet, outgoing=True) once = False def handle_join_game(join_game_packet): message_queue.append(("CONNECTION", "**Connected**")) once = True print('Connected.') connection.register_packet_listener( handle_join_game, clientbound.play.JoinGamePacket) def print_chat(chat_packet): print("[%s]: %s" % ( chat_packet.field_string('position'), parse_chat_item(json.loads(chat_packet.json_data)))) connection.register_packet_listener( print_chat, clientbound.play.ChatMessagePacket) # Add a deque for chat messages and register a method to get them message_queue = deque() def forward_chat(chat_packet): msg = parse_chat_item(json.loads(chat_packet.json_data)) if msg.startswith("<"): author, message = parse_message(msg) if (author != auth_token.username and message != ""): # Don't put in queue your own messages! message_queue.append((author, message)) connection.register_packet_listener( forward_chat, clientbound.play.ChatMessagePacket) # More maybe? Add here shit for a chatbot # Auto respawn because we can't send chat while dead def auto_respawn(update_health_packet): if update_health_packet.health <= 0: print("Respawning") packet = serverbound.play.ClientStatusPacket() packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN connection.write_packet(packet) connection.register_packet_listener( auto_respawn, clientbound.play.UpdateHealthPacket) # Start the discord thread and provide the message deque botThread = DiscordBotThread(message_queue, connection) botThread.daemon = True botThread.start() connection.connect() while True: try: text = input() if text == "/respawn": print("respawning...") packet = serverbound.play.ClientStatusPacket() packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN connection.write_packet(packet) else: packet = serverbound.play.ChatPacket() packet.message = text connection.write_packet(packet) except KeyboardInterrupt: print("Bye!") sys.exit()
auth_token.refresh() except (IOError, YggdrasilError): # IF there is no authentication file authenticate using username and password try: options = get_options() auth_token.authenticate(options["username"], options["password"]) except YggdrasilError as e: print(e) sys.exit() with open('minecraft.auth', 'w') as fout: fout.write(auth_token.client_token + '\n') fout.write(auth_token.access_token) print("Logged in as %s..." % auth_token.username) connection = Connection("localhost", 25565, auth_token=auth_token) def handle_join_game(join_game_packet): print('Connected.') connection.register_packet_listener(handle_join_game, clientbound.play.JoinGamePacket) # def print_chat(chat_packet): # print("Message (%s): %s" % ( # chat_packet.field_string('position'), chat_packet.json_data)) # connection.register_packet_listener( # print_chat, clientbound.play.ChatMessagePacket)
class MinecraftDiscordBridge(): def __init__(self): self.return_code = os.EX_OK self.session_token = "" self.uuid_cache = bidict() self.webhooks = [] self.bot_username = "" self.next_message_time = datetime.now(timezone.utc) self.previous_message = "" self.player_list = bidict() self.previous_player_list = bidict() self.accept_join_events = False self.tab_header = "" self.tab_footer = "" # Initialize the discord part self.discord_bot = discord.Client() self.config = Configuration("config.json") self.connection_retries = 0 self.auth_token = None self.connection = None self.setup_logging(self.config.logging_level) self.database_session = DatabaseSession() self.logger = logging.getLogger("bridge") self.database_session.initialize(self.config) self.bot_perms = discord.Permissions() self.bot_perms.update(manage_messages=True, manage_webhooks=True) # Async http request pool self.req_future_session = FuturesSession(max_workers=100) self.reactor_thread = Thread(target=self.run_auth_server, args=(self.config.auth_port, )) self.aioloop = asyncio.get_event_loop() # We need to import twisted after setting up the logger because twisted hijacks our logging from . import auth_server auth_server.DATABASE_SESSION = self.database_session if self.config.es_enabled: if self.config.es_auth: self.es_logger = ElasticsearchLogger(self.req_future_session, self.config.es_url, self.config.es_username, self.config.es_password) else: self.es_logger = ElasticsearchLogger(self.req_future_session, self.config.es_url) @self.discord_bot.event async def on_ready(): # pylint: disable=W0612 self.logger.info("Discord bot logged in as %s (%s)", self.discord_bot.user.name, self.discord_bot.user.id) self.logger.info( "Discord bot invite link: %s", discord.utils.oauth_url(client_id=self.discord_bot.user.id, permissions=self.bot_perms)) await self.discord_bot.change_presence( activity=discord.Game("mc!help for help")) self.webhooks = [] session = self.database_session.get_session() channels = session.query(DiscordChannel).all() session.close() for channel in channels: channel_id = channel.channel_id discord_channel = self.discord_bot.get_channel(channel_id) if discord_channel is None: session = self.database_session.get_session() session.query(DiscordChannel).filter_by( channel_id=channel_id).delete() session.close() continue channel_webhooks = await discord_channel.webhooks() found = False for webhook in channel_webhooks: if webhook.name == "_minecraft" and webhook.user == self.discord_bot.user: self.webhooks.append(webhook.url) found = True self.logger.debug("Found webhook %s in channel %s", webhook.name, discord_channel.name) if not found: # Create the hook await discord_channel.create_webhook(name="_minecraft") @self.discord_bot.event async def on_message(message): # pylint: disable=W0612 # We do not want the bot to reply to itself if message.author == self.discord_bot.user: return this_channel = message.channel.id # PM Commands if message.content.startswith("mc!help"): try: send_channel = message.channel if isinstance(message.channel, discord.abc.GuildChannel): await message.delete() dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel msg = self.get_discord_help_string() await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return elif message.content.startswith("mc!register"): try: send_channel = message.channel if isinstance(message.channel, discord.abc.GuildChannel): await message.delete() dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel session = self.database_session.get_session() discord_account = session.query(DiscordAccount).filter_by( discord_id=message.author.id).first() if not discord_account: new_discord_account = DiscordAccount(message.author.id) session.add(new_discord_account) session.commit() discord_account = session.query( DiscordAccount).filter_by( discord_id=message.author.id).first() new_token = self.generate_random_auth_token(16) account_link_token = AccountLinkToken( message.author.id, new_token) discord_account.link_token = account_link_token session.add(account_link_token) session.commit() msg = "Please connect your minecraft account to `{}.{}:{}` in order to link it to this bridge!"\ .format(new_token, self.config.auth_dns, self.config.auth_port) session.close() del session await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return # Global Commands elif message.content.startswith("mc!chathere"): if isinstance(message.channel, discord.abc.PrivateChannel): msg = "Sorry, this command is only available in public channels." await message.channel.send(msg) return if message.author.id not in self.config.admin_users: await message.delete() try: dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() dm_channel = message.author.dm_channel msg = "Sorry, you do not have permission to execute that command!" await dm_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return session = self.database_session.get_session() channels = session.query(DiscordChannel).filter_by( channel_id=this_channel).all() if not channels: new_channel = DiscordChannel(this_channel) session.add(new_channel) session.commit() session.close() del session webhook = await message.channel.create_webhook( name="_minecraft") self.webhooks.append(webhook.url) msg = "The bot will now start chatting here! To stop this, run `mc!stopchathere`." await message.channel.send(msg) else: msg = "The bot is already chatting in this channel! To stop this, run `mc!stopchathere`." await message.channel.send(msg) return elif message.content.startswith("mc!stopchathere"): if isinstance(message.channel, discord.abc.PrivateChannel): msg = "Sorry, this command is only available in public channels." await message.channel.send(msg) return if message.author.id not in self.config.admin_users: await message.delete() try: dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() dm_channel = message.author.dm_channel msg = "Sorry, you do not have permission to execute that command!" await dm_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return session = self.database_session.get_session() deleted = session.query(DiscordChannel).filter_by( channel_id=this_channel).delete() session.commit() session.close() for webhook in await message.channel.webhooks(): if webhook.name == "_minecraft" and webhook.user == self.discord_bot.user: # Copy the list to avoid some problems since # we're deleting indicies form it as we loop # through it if webhook.url in self.webhooks[:]: self.webhooks.remove(webhook.url) await webhook.delete() if deleted < 1: msg = "The bot was not chatting here!" await message.channel.send(msg) return else: msg = "The bot will no longer chat here!" await message.channel.send(msg) return elif message.content.startswith("mc!tab"): send_channel = message.channel try: if isinstance(message.channel, discord.abc.GuildChannel): await message.delete() dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel player_list = ", ".join( list(map(lambda x: x[1], self.player_list.items()))) msg = "{}\n" \ "Players online: {}\n" \ "{}".format(self.escape_markdown( self.strip_colour(self.tab_header)), self.escape_markdown( self.strip_colour(player_list)), self.escape_markdown( self.strip_colour(self.tab_footer))) await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return elif message.content.startswith("mc!botlink"): send_channel = message.channel try: if isinstance(message.channel, discord.abc.GuildChannel): await message.delete() dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel msg = "Use the following link to invite this bot to a guild:\n{}".format( discord.utils.oauth_url( client_id=self.discord_bot.user.id, permissions=self.bot_perms)) await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return elif message.content.startswith("mc!about"): send_channel = message.channel try: if isinstance(message.channel, discord.abc.GuildChannel): await message.delete() dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel msg = "This bot is running minecraft-discord-bridge version {}.\n" \ "The source code is available at https://github.com/starcraft66/minecraft-discord-bridge" \ .format(minecraft_discord_bridge.__version__) await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return elif message.content.startswith("mc!"): # Catch-all send_channel = message.channel try: if isinstance(message.channel, discord.abc.GuildChannel): await message.delete() dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel msg = "Unknown command, type `mc!help` for a list of commands." await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return elif "https://discord.gg" in message.content.lower(): await message.delete() # Deletes the message # Add something more if you want to msg = f"{message.author.mention} invites aren't allowed!" # Your message await send_channel.send(msg) elif not message.author.bot: session = self.database_session.get_session() channel_should_chat = session.query(DiscordChannel).filter_by( channel_id=this_channel).first() if channel_should_chat: await message.delete() discord_user = session.query(DiscordAccount).filter_by( discord_id=message.author.id).first() if discord_user: if discord_user.minecraft_account: minecraft_uuid = discord_user.minecraft_account.minecraft_uuid session.close() del session minecraft_username = self.mc_uuid_to_username( minecraft_uuid) # Max chat message length: 256, bot username does not count towards this # Does not count|Counts # <BOT_USERNAME> minecraft_username: message padding = 2 + len(minecraft_username) message_to_send = self.remove_emoji( message.clean_content.encode('utf-8').decode( 'ascii', 'replace')).strip() message_to_discord = self.escape_markdown( message.clean_content) total_len = padding + len(message_to_send) if total_len > 256: message_to_send = message_to_send[:(256 - padding)] message_to_discord = message_to_discord[:( 256 - padding)] elif not message_to_send: return session = self.database_session.get_session() channels = session.query(DiscordChannel).all() session.close() del session if message_to_send == self.previous_message or \ datetime.now(timezone.utc) < self.next_message_time: send_channel = message.channel try: if isinstance(message.channel, discord.abc.GuildChannel): dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel msg = "Your message \"{}\" has been rate-limited.".format( message.clean_content) await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send( msg) await asyncio.sleep(3) await error_msg.delete() return self.previous_message = message_to_send self.next_message_time = datetime.now( timezone.utc) + timedelta( seconds=self.config.message_delay) self.logger.info( "Outgoing message from discord: Username: %s Message: %s", minecraft_username, message_to_send) for channel in channels: discord_channel = self.discord_bot.get_channel( channel.channel_id) if not discord_channel: session = self.database_session.get_session( ) session.query(DiscordChannel).filter_by( channel_id=channel.channel_id).delete( ) session.close() continue webhooks = await discord_channel.webhooks() for webhook in webhooks: if webhook.name == "_minecraft": await webhook.send( username=minecraft_username, avatar_url= "https://visage.surgeplay.com/face/160/{}" .format(minecraft_uuid), content=message_to_discord) packet = serverbound.play.ChatPacket() packet.message = "{}: {}".format( minecraft_username, message_to_send) self.connection.write_packet(packet) else: send_channel = message.channel try: if isinstance(message.channel, discord.abc.GuildChannel): dm_channel = message.author.dm_channel if not dm_channel: await message.author.create_dm() send_channel = message.author.dm_channel msg = "Unable to send chat message: there is no Minecraft account linked to this discord " \ "account, please run `mc!register`." await send_channel.send(msg) return except discord.errors.Forbidden: if isinstance(message.author, discord.abc.User): msg = "{}, please allow private messages from this bot.".format( message.author.mention) error_msg = await message.channel.send(msg) await asyncio.sleep(3) await error_msg.delete() return finally: session.close() del session else: session.close() del session def run(self): self.logger.debug( "Checking if the server {} is online before connecting.") if not self.config.mc_online: self.logger.info("Connecting in offline mode...") while not self.is_server_online(): self.logger.info( 'Not connecting to server because it appears to be offline.' ) time.sleep(15) self.bot_username = self.config.mc_username self.connection = Connection( self.config.mc_server, self.config.mc_port, username=self.config.mc_username, handle_exception=self.minecraft_handle_exception) else: self.auth_token = authentication.AuthenticationToken() try: self.auth_token.authenticate(self.config.mc_username, self.config.mc_password) except YggdrasilError as ex: self.logger.info(ex) sys.exit(os.EX_TEMPFAIL) self.bot_username = self.auth_token.profile.name self.logger.info("Logged in as %s...", self.auth_token.profile.name) while not self.is_server_online(): self.logger.info( 'Not connecting to server because it appears to be offline.' ) time.sleep(15) self.connection = Connection( self.config.mc_server, self.config.mc_port, auth_token=self.auth_token, handle_exception=self.minecraft_handle_exception) self.register_handlers(self.connection) self.connection_retries += 1 self.reactor_thread.start() self.connection.connect() try: self.aioloop.run_until_complete( self.discord_bot.start(self.config.discord_token)) except (KeyboardInterrupt, SystemExit): # log out of discord self.aioloop.run_until_complete(self.discord_bot.logout()) # log out of minecraft self.connection.disconnect() # shut down auth server from twisted.internet import reactor reactor.callFromThread(reactor.stop) # clean up auth server thread self.reactor_thread.join() finally: # close the asyncio event loop discord uses self.aioloop.close() return self.return_code def mc_uuid_to_username(self, mc_uuid: str): if mc_uuid not in self.uuid_cache: try: short_uuid = mc_uuid.replace("-", "") mojang_response = self.req_future_session.get( "https://api.mojang.com/user/profiles/{}/names".format( short_uuid)).result().json() if len(mojang_response) > 1: # Multiple name changes player_username = mojang_response[-1]["name"] else: # Only one name player_username = mojang_response[0]["name"] self.uuid_cache[mc_uuid] = player_username return player_username except RequestException as ex: self.logger.error(ex, exc_info=True) self.logger.error( "Failed to lookup %s's username using the Mojang API.", mc_uuid) else: return self.uuid_cache[mc_uuid] def mc_username_to_uuid(self, username: str): if username not in self.uuid_cache.inv: try: player_uuid = self.req_future_session.get( "https://api.mojang.com/users/profiles/minecraft/{}". format(username)).result().json()["id"] long_uuid = uuid.UUID(player_uuid) self.uuid_cache.inv[username] = str(long_uuid) return player_uuid except RequestException: self.logger.error( "Failed to lookup %s's username using the Mojang API.", username) else: return self.uuid_cache.inv[username] def get_discord_help_string(self): help_str = ( "Admin commands:\n" "`mc!chathere`: Starts outputting server messages in this channel\n" "`mc!stopchathere`: Stops outputting server messages in this channel\n" "User commands:\n" "`mc!tab`: Sends you the content of the server's player/tab list\n" "`mc!register`: Starts the minecraft account registration process\n" "`mc!botlink`: Sends you the link to invite this bot to a guild\n" "`mc!about`: Sends you information about the running bridge\n" "To start chatting on the minecraft server, please register your account using `mc!register`." ) return help_str # https://stackoverflow.com/questions/33404752/removing-emojis-from-a-string-in-python def remove_emoji(self, dirty_string): emoji_pattern = re.compile( "[" u"\U0001F600-\U0001F64F" # emoticons u"\U0001F300-\U0001F5FF" # symbols & pictographs u"\U0001F680-\U0001F6FF" # transport & map symbols u"\U0001F1E0-\U0001F1FF" # flags (iOS) u"\U0001F900-\U0001FAFF" # CJK Compatibility Ideographs # u"\U00002702-\U000027B0" # u"\U000024C2-\U0001F251" "]+", flags=re.UNICODE) return emoji_pattern.sub(r'', dirty_string) def escape_markdown(self, md_string): # Don't mess with urls url_regex = re.compile( r'^(?:http|ftp)s?://' # http:// or https:// r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) escaped_string = "" # Split the message into pieces, each "word" speparated into a string is a piece # Discord ignores formatting characters in urls so we can't just replace the whole # string... We need to go through words one by one to find out what is a url (don't) # escape) and what isn't (escape). for piece in md_string.split(" "): if url_regex.match(piece): escaped_string += "{} ".format(piece) continue # Absolutely needs to go first or it will replace our escaping slashes! piece = piece.replace("\\", "\\\\") piece = piece.replace("_", "\\_") piece = piece.replace("*", "\\*") escaped_string += "{} ".format(piece) if escaped_string.startswith(">"): escaped_string = "\\" + escaped_string escaped_string.strip() return escaped_string def strip_colour(self, dirty_string): colour_pattern = re.compile( u"\U000000A7" # selection symbol ".", flags=re.UNICODE) return colour_pattern.sub(r'', dirty_string) def setup_logging(self, level): if level.lower() == "debug": log_level = logging.DEBUG else: log_level = logging.INFO log_format = "%(asctime)s:%(name)s:%(levelname)s:%(message)s" logging.basicConfig(filename="bridge_log.log", format=log_format, level=log_level) stdout_logger = logging.StreamHandler(sys.stdout) stdout_logger.setFormatter(logging.Formatter(log_format)) logging.getLogger().addHandler(stdout_logger) def run_auth_server(self, port): # We need to import twisted after setting up the logger because twisted hijacks our logging from twisted.internet import reactor from .auth_server import AuthFactory # Create factory factory = AuthFactory() # Listen self.logger.info("Starting authentication server on port %d", port) factory.listen("", port) reactor.run(installSignalHandlers=False) def generate_random_auth_token(self, length): letters = string.ascii_lowercase + string.digits + string.ascii_uppercase return ''.join(random.choice(letters) for i in range(length)) def handle_disconnect(self, json_data=""): self.logger.info('Disconnected.') if json_data: self.logger.info("Disconnect json data: %s", json_data) if self.connection_retries >= self.config.failsafe_retries: self.logger.info( "Failed to join the server %s times in a row. Exiting.", self.connection_retries) self.logger.info( "Use a process supervisor if you wish to automatically restart the bridge." ) # This is possibly a huge hack... Since we cannot reliably raise exceptions on this thread # for them to be caught on the main thread, we call interrupt_main to raise a KeyboardInterrupt # on main and tell it to shut the bridge down. self.return_code = os.EX_TEMPFAIL _thread.interrupt_main() return self.previous_player_list = self.player_list.copy() self.accept_join_events = False self.player_list = bidict() if self.connection.connected: self.logger.info( "Forced a disconnection because the connection is still connected." ) self.connection.disconnect(immediate=True) time.sleep(15) while not self.is_server_online(): self.logger.info( 'Not reconnecting to server because it appears to be offline.') time.sleep(15) self.logger.info('Reconnecting.') self.connection_retries += 1 self.connection.connect() def handle_disconnect_packet(self, disconnect_packet): self.handle_disconnect(disconnect_packet.json_data) def minecraft_handle_exception(self, exception, exc_info): self.logger.error("A minecraft exception occured! %s:", exception, exc_info=exc_info) self.handle_disconnect() def is_server_online(self): server = MinecraftServer.lookup("{}:{}".format(self.config.mc_server, self.config.mc_port)) try: status = server.status() del status return True except ConnectionRefusedError: return False # AttributeError: 'TCPSocketConnection' object has no attribute 'socket' # This might not be required as it happens upstream except AttributeError: return False def register_handlers(self, connection): connection.register_packet_listener(self.handle_join_game, clientbound.play.JoinGamePacket) connection.register_packet_listener(self.handle_chat, clientbound.play.ChatMessagePacket) connection.register_packet_listener( self.handle_health_update, clientbound.play.UpdateHealthPacket) connection.register_packet_listener(self.handle_disconnect_packet, clientbound.play.DisconnectPacket) connection.register_packet_listener( self.handle_tab_list, clientbound.play.PlayerListItemPacket) connection.register_packet_listener( self.handle_player_list_header_and_footer_update, clientbound.play.PlayerListHeaderAndFooterPacket) def handle_player_list_header_and_footer_update(self, header_footer_packet): self.logger.debug("Got Tablist H/F Update: header=%s", header_footer_packet.header) self.logger.debug("Got Tablist H/F Update: footer=%s", header_footer_packet.footer) self.tab_header = json.loads(header_footer_packet.header)["text"] self.tab_footer = json.loads(header_footer_packet.footer)["text"] def handle_tab_list(self, tab_list_packet): self.logger.debug("Processing tab list packet") for action in tab_list_packet.actions: if isinstance( action, clientbound.play.PlayerListItemPacket.AddPlayerAction): self.logger.debug( "Processing AddPlayerAction tab list packet, name: %s, uuid: %s", action.name, action.uuid) username = action.name player_uuid = action.uuid if action.name not in self.player_list.inv: self.player_list.inv[action.name] = action.uuid else: # Sometimes we get a duplicate add packet on join idk why return if action.name not in self.uuid_cache.inv: self.uuid_cache.inv[action.name] = action.uuid # Initial tablist backfill if self.accept_join_events: webhook_payload = { 'username': username, 'avatar_url': "https://visage.surgeplay.com/face/160/{}".format( player_uuid), 'content': '', 'embeds': [{ 'color': 65280, 'title': '**Joined the game**' }] } for webhook in self.webhooks: self.req_future_session.post(webhook, json=webhook_payload) if self.config.es_enabled: self.es_logger.log_connection( uuid=action.uuid, reason=ConnectionReason.CONNECTED, count=len(self.player_list)) return else: # The bot's name is sent last after the initial back-fill if action.name == self.bot_username: self.accept_join_events = True if self.config.es_enabled: diff = set(self.previous_player_list.keys()) - set( self.player_list.keys()) for idx, player_uuid in enumerate(diff): self.es_logger.log_connection( uuid=player_uuid, reason=ConnectionReason.DISCONNECTED, count=len(self.previous_player_list) - (idx + 1)) # Don't bother announcing the bot's own join message (who cares) but log it for analytics still if self.config.es_enabled: self.es_logger.log_connection( uuid=action.uuid, reason=ConnectionReason.CONNECTED, count=len(self.player_list)) if self.config.es_enabled: self.es_logger.log_connection(uuid=action.uuid, reason=ConnectionReason.SEEN) if isinstance( action, clientbound.play.PlayerListItemPacket.RemovePlayerAction): self.logger.debug( "Processing RemovePlayerAction tab list packet, uuid: %s", action.uuid) username = self.mc_uuid_to_username(action.uuid) player_uuid = action.uuid webhook_payload = { 'username': username, 'avatar_url': "https://visage.surgeplay.com/face/160/{}".format( player_uuid), 'content': '', 'embeds': [{ 'color': 16711680, 'title': '**Left the game**' }] } for webhook in self.webhooks: self.req_future_session.post(webhook, json=webhook_payload) del self.uuid_cache[action.uuid] if action.uuid in self.player_list: del self.player_list[action.uuid] if self.config.es_enabled: self.es_logger.log_connection( uuid=action.uuid, reason=ConnectionReason.DISCONNECTED, count=len(self.player_list)) def handle_join_game(self, join_game_packet): self.logger.info('Connected and joined game as entity id %d', join_game_packet.entity_id) self.player_list = bidict() self.connection_retries = 0 def handle_chat(self, chat_packet): json_data = json.loads(chat_packet.json_data) if "extra" not in json_data: return chat_string = "" for chat_component in json_data["extra"]: chat_string += chat_component["text"] # Handle chat message regexp_match = re.match("<(.*?)> (.*)", chat_string, re.M | re.I) if regexp_match: username = regexp_match.group(1) original_message = regexp_match.group(2) player_uuid = self.mc_username_to_uuid(username) if username.lower() == self.bot_username.lower(): # Don't relay our own messages if self.config.es_enabled: bot_message_match = re.match( "<{}> (.*?): (.*)".format(self.bot_username.lower()), chat_string, re.M | re.I) if bot_message_match: self.es_logger.log_chat_message( uuid=self.mc_username_to_uuid( bot_message_match.group(1)), display_name=bot_message_match.group(1), message=bot_message_match.group(2), message_unformatted=chat_string) self.es_logger.log_raw_message( msg_type=chat_packet.Position.name_from_value( chat_packet.position), message=chat_packet.json_data) return self.logger.info( "Incoming message from minecraft: Username: %s Message: %s", username, original_message) self.logger.debug("msg: %s", repr(original_message)) message = self.escape_markdown( self.remove_emoji(original_message.strip().replace( "@", "@\N{zero width space}"))) webhook_payload = { 'username': username, 'avatar_url': "https://visage.surgeplay.com/face/160/{}".format(player_uuid), 'content': '{}'.format(message) } for webhook in self.webhooks: self.req_future_session.post(webhook, json=webhook_payload) if self.config.es_enabled: self.es_logger.log_chat_message( uuid=player_uuid, display_name=username, message=original_message, message_unformatted=chat_string) if self.config.es_enabled: self.es_logger.log_raw_message( msg_type=chat_packet.Position.name_from_value( chat_packet.position), message=chat_packet.json_data) def handle_health_update(self, health_update_packet): if health_update_packet.health <= 0: self.logger.debug("Respawned the player because it died") packet = serverbound.play.ClientStatusPacket() packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN self.connection.write_packet(packet)
class Player: """ A class built to handle all required actions to maintain: - Gaining auth tokens, and connecting to online minecraft servers. - Clientbound chat - Serverbound chat Warnings -------- This class explicitly expects a username & password, then expects to be able to connect to a server in online mode. If you wish to add different functionality please view the example headless client, `start.py`, for how to implement it. """ def __init__(self, username, password, *, admins=None): """ Init handles the following: - Client Authentication - Setting the current connection state - Setting the recognized 'admins' for this instance Parameters ---------- username : String Used for authentication password : String Used for authentication admins : list, optional The minecraft accounts to auto accept tpa's requests from Raises ------ YggdrasilError Username or Password was incorrect """ self.kickout = False self.admins = [] if admins is None else admins self.auth_token = authentication.AuthenticationToken() self.auth_token.authenticate(username, password) def Parser(self, data): """ Converts the chat packet received from the server into human readable strings Parameters ---------- data : JSON The chat data json receive from the server Returns ------- message : String The text received from the server in human readable form """ message = DefaultParser(data) # This is where you would call other parsers if not message: return False if "teleport" in message.lower(): self.HandleTpa(message) return message def HandleTpa(self, message): """ Using the given message, figure out whether or not to accept the tpa Parameters ---------- message : String The current chat, where 'tpa' was found in message.lower() """ try: found = re.search( "(.+?) has requested that you teleport to them.", message ).group(1) if found in self.admins: self.SendChat("/tpyes") return except AttributeError: pass try: found = re.search("(.+?) has requested to teleport to you.", message).group( 1 ) if found in self.admins: self.SendChat("/tpyes") return except AttributeError: pass def SendChat(self, msg): """ Send a given message to the server Parameters ---------- msg : String The message to send to the server """ msg = str(msg) if len(msg) > 0: packet = serverbound.play.ChatPacket() packet.message = msg self.connection.write_packet(packet) def ReceiveChat(self, chat_packet): """ The listener for ClientboundChatPackets Parameters ---------- chat_packet : ClientboundChatPacket The incoming chat packet chat_packet.json : JSON The chat packet to pass of to our Parser for handling """ message = self.Parser(chat_packet.json_data) if not message: # This means our Parser failed lol print("Parser failed") return print(message) def SetServer(self, ip, port=25565, handler=None): """ Sets the server, ready for connection Parameters ---------- ip : str The server to connect to port : int, optional The port to connect on handler : Function pointer, optional Points to the function used to handle Clientbound chat packets """ handler = handler or self.ReceiveChat self.ip = ip self.port = port self.connection = Connection( ip, port, auth_token=self.auth_token, handle_exception=print ) self.connection.register_packet_listener( handler, clientbound.play.ChatMessagePacket ) self.connection.exception_handler(print) def Connect(self): """ Actually connect to the server for this player and maintain said connection Notes ----- This is a blocking function and will not return until `Disconnect()` is called on said instance. """ self.connection.connect() print(f"Connected to server with: {self.auth_token.username}") while True: time.sleep(1) if self.kickout: break def Disconnect(self): """ In order to disconnect the client, and break the blocking loop this method must be called """ self.kickout = True self.connection.disconnect()
raise ValueError("Invalid server address: '%s'." % options.server) options.address = match.group("host") or match.group("addr") options.port = int(match.group("port") or 25565) return options def useitem(): global connection global packet connection.write_packet(packet) options = get_options() if options.offline: print("Connecting in offline mode...") connection = Connection( options.address, options.port, username=options.username) else: auth_token = authentication.AuthenticationToken() try: auth_token.authenticate(options.username, options.password) except YggdrasilError as e: print(e) sys.exit() print("Logged in as %s..." % auth_token.username) connection = Connection( options.address, options.port, auth_token=auth_token) packet = minecraft.networking.packets.serverbound.play.UseItemPacket() packet.hand = 0 if options.dump_packets:
def main(): auth_token = authentication.AuthenticationToken() try: with open('minecraft.auth', 'r') as f: auth_token.client_token, auth_token.access_token = f.read().splitlines() # Library has issues need to do some hackey stuff to make sure it works. # I would use validate, but that would require some rewriting as well. auth_token.username = "******" auth_token.refresh() except (IOError, YggdrasilError): # IF there is no authentication file authenticate using username and password try: options = get_options() auth_token.authenticate(options["username"], options["password"]) except YggdrasilError as e: print(e) sys.exit() with open('minecraft.auth', 'w') as fout: fout.write(auth_token.client_token + '\n') fout.write(auth_token.access_token) print("Logged in as %s..." % auth_token.username) connection = Connection( "localhost", 25565, auth_token=auth_token) def handle_join_game(join_game_packet): print('Connected.') connection.register_packet_listener( handle_join_game, clientbound.play.JoinGamePacket) def print_chat(chat_packet): print("Message (%s): %s" % ( chat_packet.field_string('position'), chat_packet.json_data)) connection.register_packet_listener( print_chat, clientbound.play.ChatMessagePacket) global connected connected = True def disconnect(disconnect_packet): print("You were disconnected: %s" % disconnect_packet.json_data) global connected connected = False connection.register_packet_listener(disconnect, clientbound.play.DisconnectPacket) connection.connect() while connected: try: text = input() if text == "/respawn": print("respawning...") packet = serverbound.play.ClientStatusPacket() packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN connection.write_packet(packet) else: packet = serverbound.play.ChatPacket() packet.message = text connection.write_packet(packet) except KeyboardInterrupt: print("Bye!") sys.exit()