From 58e3d16a11dd9f21d4a0f5c5c8019399d696ccfd Mon Sep 17 00:00:00 2001 From: khr Date: Fri, 27 Mar 2020 07:26:50 +0100 Subject: [PATCH] fixes for python 3 and general cleanup --- .gitignore | 3 +- ServerWrapper_v0.1.0.py | 181 +++++++++++++++++++++++++--------------- 2 files changed, 116 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 55f3b4c..ff837a4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ logs/ matrix-python-sdk-master/ old/ test/ -world/ \ No newline at end of file +world/ +*.swp diff --git a/ServerWrapper_v0.1.0.py b/ServerWrapper_v0.1.0.py index eaf4f6f..ebe014b 100644 --- a/ServerWrapper_v0.1.0.py +++ b/ServerWrapper_v0.1.0.py @@ -1,22 +1,26 @@ +import argparse +import atexit +import base64 +import glob +import io import json +import logging import os +import re import select import socket import sys -import glob -import re import subprocess from subprocess import PIPE import struct import threading import time +import urllib +from urllib.parse import urlparse + from matrix_client.api import MatrixHttpApi import requests from flask import Flask, jsonify, request -import atexit -import base64 -import io, urllib -from urlparse import urlparse #constants @@ -27,6 +31,9 @@ app = Flask(__name__) minecraft = None roomsync = {} +LOG = logging.getLogger(__name__) + + class socket_util(object): def __init__(self, host, port): self.host = host @@ -35,11 +42,11 @@ class socket_util(object): self.soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.msglist = [] self.addr = None - print ("Socket Init Complete") + LOG.info ("Socket Init Complete") self.proc = None self.exit = False atexit.register(self.close_socket) - print "Starting Messaging Thread" + LOG.info("Starting Messaging Thread") msg_process = threading.Thread(target=self.msg_process) msg_process.daemon = True msg_process.start() @@ -59,7 +66,7 @@ class socket_util(object): def send(self, message): #select returns (readable, writable, error) status on the objects passed in the corresponding lists r, w, e = select.select([], [self.soc], [], 1) - #print "w" + str(w) + #LOG.info("w" + str(w)) if w == []: return 1 string_message = json.dumps(message) @@ -81,7 +88,7 @@ class socket_util(object): def receive(self): r,s,e = select.select([self.soc], [], [], 1) - #print "r" + str(r) + #LOG.info("r" + str(r)) if r == []: return "" message_size = self.read_int() @@ -90,7 +97,7 @@ class socket_util(object): return None data = self.read(message_size) if data == None: - print "data_none" + LOG.debug("data_none") return None message = json.loads(data) @@ -113,21 +120,22 @@ class socket_util(object): return data class MinecraftWrapper(socket_util): - def __init__(self, host, port): - super(MinecraftWrapper,self).__init__(host, port) - print "Starting Wrapper Polling Thread" + def __init__(self, command, host, port): + super().__init__(host, port) + self.command = command + LOG.info("Starting Wrapper Polling Thread") poll_process = threading.Thread(target=self.cli_poll) poll_process.daemon = True poll_process.start() self.socket_reset() def socket_reset(self): - super(MinecraftWrapper,self).socket_reset() + super().socket_reset() self.soc.connect((self.host, self.port)) - print "Socket Connected" + LOG.info("Socket Connected") def exe_mc(self): - self.proc = subprocess.Popen(sys.argv[1:], shell=True, stdout=PIPE, stdin=PIPE, universal_newlines=True) + self.proc = subprocess.Popen(self.command, shell=True, stdout=PIPE, stdin=PIPE, universal_newlines=True) for stdout_line in iter(self.proc.stdout.readline, ""): yield stdout_line return_code = self.proc.wait() @@ -146,7 +154,7 @@ class MinecraftWrapper(socket_util): self.msg_handle(rcv) if status == 0: self.msglist.pop() except Exception as e: - print e + LOG.info(e) self.socket_reset() @@ -155,7 +163,7 @@ class MinecraftWrapper(socket_util): if msg[0] == '/': self.proc.stdin.write(msg + '\n') else: - print(msg) + LOG.info(msg) def proc_monitor(self): try: @@ -164,47 +172,53 @@ class MinecraftWrapper(socket_util): self.close_socket() sys.exit(0) except: - print "poll error" + LOG.error("poll error") pass def cli_poll(self): prog = re.compile("^\[(.*)\] \[(.*)\]: <(.*)> (.*)") for line in self.exe_mc(): - print(line.rstrip('\n')) + LOG.info(line.rstrip('\n')) # regex to get user and text: ^<(.*)> (.*)\n result = prog.search(line) if result: - #print("user: " + result.group(3) + " msg: " +result.group(4).rstrip('\n')) + #LOG.info("user: " + result.group(3) + " msg: " +result.group(4).rstrip('\n')) #msb.send({"user":result.group(3),"msg":result.group(4).rstrip('\n')}) self.msglist.insert(0, {"user":result.group(3),"msg":result.group(4).rstrip('\n')}) class MinecraftServerBridge(socket_util): - def __init__(self, host, port): + def __init__( + self, + minecraft_bind_host: int, + minecraft_bind_port: int, + matrix_bind_port: int, + appservice_token: str + ): #starting threads - print ("Starting Appservice Webserver") - flask_thread = threading.Thread(target=app.run,kwargs={"port":global_config['bridge_matrixapi_port']}) + LOG.info ("Starting Appservice Webserver") + flask_thread = threading.Thread(target=app.run,kwargs={ "port": minecraft_bind_port }) flask_thread.daemon = True flask_thread.start() #socket and other init - super(MinecraftServerBridge,self).__init__(host, port) - print ("Calling Matrix Api") - self.api = MatrixHttpApi("http://localhost:8008", token=global_config['as_token']) + super().__init__(minecraft_bind_host, minecraft_bind_port) + LOG.info ("Calling Matrix Api") + self.api = MatrixHttpApi("http://localhost:8008", token=appservice_token) self.user_re = re.compile("(?<=\@).*(?=\:)") self.avatar_update_log = {} - print ("Finished Init") + LOG.info ("Finished Init") def socket_reset(self): - super(MinecraftServerBridge,self).socket_reset() - print "Server Binding to " + self.host + " " + str(self.port) + super().socket_reset() + LOG.info("Server Binding to " + self.host + " " + str(self.port)) self.soc.bind((self.host, self.port)) - print "Server Bound" + LOG.info("Server Bound") self.soc.listen(1) - print "Server listen to host" + LOG.info("Server listen to host") self.soc, self.addr = self.soc.accept() self.soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - print "Server accepted connection: " + str(self.addr) + LOG.info("Server accepted connection: " + str(self.addr)) def msg_process(self): @@ -217,7 +231,7 @@ class MinecraftServerBridge(socket_util): if rcv != "" and rcv != None: self.msg_handle(rcv) except Exception as e: - print e + LOG.info(e) self.socket_reset() @@ -225,41 +239,41 @@ class MinecraftServerBridge(socket_util): #for msg, create user and post as user #add minecraft user to minecraft channel, if this fails, no big deal try: - print "trying to create id..." + LOG.info("trying to create id...") new_user = "@mc_" + msg['user'] user_id = new_user + ":" + global_config['server_name'] self.api.register("m.login.application_service",username = "mc_" + msg['user']) except Exception as e: - print e + LOG.info(e) #for each room we're aware of, post server chat inside. Eventually 1 room should equal 1 server for room in roomsync: #generate a unique transaction id based on the current time txn_id = str(int(time.time() * 1000)) #attempt to join room - print "trying to join room as user and as bridge manager" + LOG.info("trying to join room as user and as bridge manager") self.api._send("POST", '/rooms/'+room+'/join', query_params={"user_id": user_id}, headers={"Content-Type":"application/json"}) self.api._send("POST", '/rooms/'+room+'/join', headers={"Content-Type":"application/json"}) #set our display name to something nice - print "trying to set display name..." + LOG.info("trying to set display name...") self.api._send("PUT", '/profile/'+user_id+'/displayname/', content={"displayname":msg["user"]}, query_params={"user_id": user_id}, headers={"Content-Type":"application/json"}) #get our mc skin!! #backup: #avatar_url = "https://www.minecraftskinstealer.com/face.php?u="+msg['user'] #only get this if the user hasn't updated in a long time - print "Checking if we need to update avatar..." + LOG.info("Checking if we need to update avatar...") if msg['user'] not in self.avatar_update_log.keys() or abs(self.avatar_update_log[msg['user']] - time.time()) > 180: self.avatar_update_log[msg['user']] = time.time() avatar_url = self.get_mc_skin(msg['user'], user_id) if avatar_url: - print "avatar_url is " + avatar_url + LOG.info("avatar_url is " + avatar_url) self.api._send("PUT", '/profile/'+user_id+'/avatar_url/', content={"avatar_url":avatar_url}, query_params={"user_id": user_id}, headers={"Content-Type":"application/json"}) #attempt to post in room - print "Attempting to post in Room" + LOG.info("Attempting to post in Room") self.api._send("PUT", '/rooms/'+room+'/send/m.room.message/' + txn_id, content={"msgtype":"m.text","body":msg["msg"]}, query_params={"user_id": user_id}, headers={"Content-Type":"application/json"}) def get_mc_skin(self, user, user_id): - print("Getting Minecraft Avatar") + LOG.info("Getting Minecraft Avatar") from PIL import Image mojang_info = requests.get('https://api.mojang.com/users/profiles/minecraft/'+user).json() #get uuid mojang_info = requests.get('https://sessionserver.mojang.com/session/minecraft/profile/'+mojang_info['id']).json() #get more info from uuid @@ -275,23 +289,23 @@ class MinecraftServerBridge(socket_util): #compare to user's current id so we're not uploading the same pic twice #GET /_matrix/client/r0/profile/{userId}/avatar_url - print "Getting Current Avatar URL" + LOG.info("Getting Current Avatar URL") curr_url = self.api._send("GET", '/profile/'+user_id+'/avatar_url/', query_params={"user_id": user_id}, headers={"Content-Type":"application/json"}) upload = True if 'avatar_url' in curr_url.keys(): - print "Checking Avatar..." + LOG.info("Checking Avatar...") file = io.BytesIO(urllib.urlopen(self.api.get_download_url(curr_url['avatar_url'])).read()) im = Image.open(file) image_buffer_curr = io.BytesIO() im.save(image_buffer_curr, "PNG") if (image_buffer_head.getvalue()) == (image_buffer_curr.getvalue()): - print "Image Same" + LOG.debug("Image Same") upload = False if upload: #upload img #POST /_matrix/media/r0/upload - print "Returning updated avatar" - print image_buffer_head + LOG.debug("Returning updated avatar") + LOG.debug(image_buffer_head) return self.api.media_upload(image_buffer_head.getvalue(), "image/png")["content_uri"] else: return None @@ -299,12 +313,12 @@ class MinecraftServerBridge(socket_util): @app.route("/transactions/", methods=["PUT"]) def on_receive_events(transaction): - print("got event") + LOG.info("got event") events = request.get_json()["events"] for event in events: - print "User: %s Room: %s" % (event["user_id"], event["room_id"]) - print "Event Type: %s" % event["type"] - print "Content: %s" % event["content"] + LOG.info("User: %s Room: %s" % (event["user_id"], event["room_id"])) + LOG.info("Event Type: %s" % event["type"]) + LOG.info("Content: %s" % event["content"]) roomsync[event["room_id"]] = "" if event['type'] == 'm.room.message' and \ event['content']['msgtype'] == 'm.text' and \ @@ -318,7 +332,7 @@ def on_receive_events(transaction): @app.route("/rooms/", methods=["GET"]) def on_room(room): - print "returning: " + str(room) + LOG.info("returning: " + str(room)) return jsonify({}) @@ -331,7 +345,7 @@ def make_config(configfile, server=True): json.dump(bridge_cfg_skeleton, outfile) else: json.dump(wrapper_cfg_skeleton, outfile) - print "Please edit {0} and then run again!".format(configfile) + LOG.error("Please edit {0} and then run again!".format(configfile)) sys.exit(0) elif glob.glob(configfile): @@ -339,26 +353,59 @@ def make_config(configfile, server=True): read_config = json.load(config) return read_config -if __name__=="__main__": - if glob.glob("minecraft_server.*"): - print "Running Minecraft Server Wrapper Mode" + +def main(): + logging.basicConfig() + + parser = argparse.ArgumentParser() + mode_group = parser.add_mutually_exclusive_group(required=True) + mode_group.add_argument( + "--minecraft_wrapper", + dest="mode", + action="store_const", + const="wrapper", + help="Run in Minecraft server wrapper mode", + ) + mode_group.add_argument( + "--matrix_bridge", + dest="mode", + action="store_const", + const="bridge", + help="Run in Matrix Appservice mode", + ) + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + + if args.mode == "wrapper": + LOG.info("Running Minecraft Server Wrapper Mode") global_config = make_config("wrapper.json", server=False) ip_addr_info = socket.gethostbyname_ex(global_config['server_name']) - minecraft = MinecraftWrapper(ip_addr_info[2][0], global_config['wrapper_mcdata_port']) + minecraft = MinecraftWrapper( + args.command, + host=ip_addr_info[2][0], + port=global_config['wrapper_mcdata_port'], + ) else: - print "Running Minecraft Matrix Bridge Mode" + LOG.info("Running Minecraft Matrix Bridge Mode") global_config = make_config("server.json", server=True) - minecraft = MinecraftServerBridge("localhost", global_config['bridge_mcdata_port']) - print "All Threads Running" + minecraft = MinecraftServerBridge( + minecraft_bind_host="0.0.0.0", + minecraft_bind_port=global_config['bridge_mcdata_port'], + matrix_bind_port=global_config["bridge_matrixapi_port"], + appservice_token=global_config["as_token"], + ) + LOG.info("All Threads Running") cmd = "" - while(not minecraft.exit): + # Allow stdin commands to the minecraft server + while (not minecraft.exit): if minecraft.proc != None and 'stop' not in cmd: - cmd = raw_input() + cmd = input() minecraft.proc.stdin.write(cmd + '\n') else: time.sleep(1) - print "Calling exit() in main thread..." + LOG.info("Calling exit() in main thread...") sys.exit() - - - \ No newline at end of file + + +if __name__ == "__main__": + main()