mineconnect 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include requirements.txt
3
+ recursive-include mineconnect *.yml
4
+ recursive-include mineconnect *.json
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: mineconnect
3
+ Version: 1.0.0
4
+ Summary: Minecraft TCP/UDP to WebSocket bridge with Cloudflare tunnel support
5
+ Author: MineConnect
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.8
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Requires-Python: >=3.8
12
+ Requires-Dist: websockets>=12.0
13
+ Dynamic: author
14
+ Dynamic: classifier
15
+ Dynamic: requires-dist
16
+ Dynamic: requires-python
17
+ Dynamic: summary
@@ -0,0 +1,160 @@
1
+ # MineConnect - Installation & Usage
2
+
3
+ A Python module for bridging Minecraft Java and Bedrock connections through WebSocket to Cloudflare tunnels.
4
+
5
+ ## Installation
6
+
7
+ ### From Source (Development)
8
+
9
+ ```bash
10
+ cd c:\Projects\MineConnect\server
11
+ pip install -e .
12
+ ```
13
+
14
+ ### From PyPI (Future)
15
+
16
+ ```bash
17
+ pip install mineconnect
18
+ ```
19
+
20
+ ## One-Time Setup
21
+
22
+ Before using MineConnect, run the tunnel setup (requires cloudflared installed):
23
+
24
+ ```bash
25
+ mineconnect-setup
26
+ ```
27
+
28
+ Or:
29
+
30
+ ```bash
31
+ python -m mineconnect.tunnel_setup
32
+ ```
33
+
34
+ This will:
35
+ 1. Authenticate with Cloudflare
36
+ 2. Create two tunnels (java and bedrock)
37
+ 3. Configure DNS routing
38
+ 4. Update configuration files
39
+
40
+ ## Usage
41
+
42
+ ### Simple API
43
+
44
+ ```python
45
+ from mineconnect import mcs
46
+
47
+ # Use default ports (25565 for Java, 19132 for Bedrock)
48
+ session = mcs.config()
49
+ session.start()
50
+ ```
51
+
52
+ ### Custom Ports
53
+
54
+ ```python
55
+ from mineconnect import mcs
56
+
57
+ # Custom Minecraft server ports
58
+ session = mcs.config(tcp_port=25566, udp_port=19133)
59
+ session.start()
60
+ ```
61
+
62
+ ### Single Tunnel Mode
63
+
64
+ ```python
65
+ from mineconnect import mcs
66
+
67
+ # Use one tunnel with two ingress rules instead of two separate tunnels
68
+ session = mcs.config(tcp_port=25565, udp_port=19132, single_tunnel=True)
69
+ session.start()
70
+ ```
71
+
72
+ ### Full Example
73
+
74
+ ```python
75
+ from mineconnect import mcs
76
+
77
+ def main():
78
+ # Configure server with custom ports
79
+ session = mcs.config(
80
+ tcp_port=25565, # Minecraft Java Edition port
81
+ udp_port=19132, # Geyser/Bedrock port
82
+ single_tunnel=False # Use two separate tunnels
83
+ )
84
+
85
+ # Start server - blocks and shows logs until Ctrl+C
86
+ try:
87
+ session.start()
88
+ except KeyboardInterrupt:
89
+ print("Server stopped by user")
90
+
91
+ if __name__ == "__main__":
92
+ main()
93
+ ```
94
+
95
+ ## Parameters
96
+
97
+ ### `mcs.config(tcp_port, udp_port, single_tunnel)`
98
+
99
+ - **tcp_port** (int, default: 25565): Port where your Minecraft Java Edition server is running
100
+ - **udp_port** (int, default: 19132): Port where your Geyser/Bedrock server is running
101
+ - **single_tunnel** (bool, default: False): Use single tunnel with two ingress rules instead of two separate tunnels
102
+
103
+ ### `session.start()`
104
+
105
+ Starts all services:
106
+ - TCP WebSocket bridge (port 8764)
107
+ - UDP WebSocket bridge (port 8765)
108
+ - Cloudflared tunnel(s)
109
+
110
+ Logs are displayed in real-time. Press Ctrl+C to stop all services gracefully.
111
+
112
+ ## Prerequisites
113
+
114
+ 1. **Python 3.8+**
115
+ 2. **cloudflared**: Install from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/
116
+ 3. **Minecraft Java Server**: Running on specified tcp_port (default: localhost:25565)
117
+ 4. **Geyser** (for Bedrock support): Running on specified udp_port (default: localhost:19132)
118
+
119
+ ## Architecture
120
+
121
+ ```
122
+ Minecraft Client → Cloudflare Tunnel → WebSocket Bridge → Local MC Server
123
+ ```
124
+
125
+ - **TCP Bridge**: Handles Java Edition connections via persistent WebSocket
126
+ - **UDP Bridge**: Handles Bedrock Edition connections via WebSocket
127
+ - **Cloudflared**: Creates secure tunnels from Cloudflare edge to local bridges
128
+
129
+ ## Troubleshooting
130
+
131
+ ### "Cannot start TCP-WS-Bridge" or "Cannot start UDP-WS-Bridge"
132
+
133
+ Make sure the websockets package is installed:
134
+ ```bash
135
+ pip install websockets
136
+ ```
137
+
138
+ ### "Cannot reach MC" errors
139
+
140
+ Ensure your Minecraft server is running on the configured port before starting MineConnect.
141
+
142
+ ### Cloudflared not found
143
+
144
+ Install cloudflared and make sure it's in your PATH:
145
+ - Windows: Download from Cloudflare and add to PATH
146
+ - Linux: `wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64`
147
+ - macOS: `brew install cloudflared`
148
+
149
+ ## Development
150
+
151
+ To modify the package:
152
+
153
+ 1. Clone the repository
154
+ 2. Install in editable mode: `pip install -e server/`
155
+ 3. Make changes to files in `server/mineconnect/`
156
+ 4. Changes take effect immediately
157
+
158
+ ## License
159
+
160
+ MIT License
@@ -0,0 +1,44 @@
1
+ """
2
+ MineConnect - Minecraft Server Connection Bridge
3
+
4
+ Simple API for bridging Minecraft Java and Bedrock connections through
5
+ WebSocket to Cloudflare tunnels.
6
+
7
+ Usage:
8
+ from mineconnect import mcs
9
+
10
+ session = mcs.config(tcp_port=25565, udp_port=19132, single_tunnel=False)
11
+ session.start()
12
+ """
13
+
14
+ from .server import MineConnectServer
15
+
16
+ __version__ = "1.0.0"
17
+ __all__ = ["MineConnectServer", "mcs"]
18
+
19
+
20
+ class _MCS:
21
+ """Simple configuration wrapper for MineConnect."""
22
+
23
+ @staticmethod
24
+ def config(tcp_port=25565, udp_port=19132, single_tunnel=False):
25
+ """
26
+ Configure a MineConnect server session.
27
+
28
+ Args:
29
+ tcp_port (int): Minecraft Java Edition server port (default: 25565)
30
+ udp_port (int): Geyser/Bedrock server port (default: 19132)
31
+ single_tunnel (bool): Use single tunnel with two ingress rules (default: False)
32
+
33
+ Returns:
34
+ MineConnectServer: Configured server instance
35
+ """
36
+ return MineConnectServer(
37
+ tcp_port=tcp_port,
38
+ udp_port=udp_port,
39
+ single_tunnel=single_tunnel
40
+ )
41
+
42
+
43
+ # Export the main API
44
+ mcs = _MCS()
@@ -0,0 +1 @@
1
+ {"AccountTag":"7b8aa8d0e13cb1182b8c73cd58bd2b6c","TunnelSecret":"Tzu9w16Q40cmcBD11Yc0Rj8Bv9EOdDiwH1436kRe9nk=","TunnelID":"d75d9307-b76a-410d-9692-16c3cd0926b7","Endpoint":""}
@@ -0,0 +1 @@
1
+ {"AccountTag":"7b8aa8d0e13cb1182b8c73cd58bd2b6c","TunnelSecret":"TvzSIyTh1nveCeMlj3SdEOlQ3WxxxlGFApqAeIkRhtE=","TunnelID":"f06e9c55-aab2-455b-81d5-08423eb92a39","Endpoint":""}
@@ -0,0 +1,184 @@
1
+ """
2
+ MineConnect Server — Main server orchestration class
3
+ """
4
+
5
+ import subprocess
6
+ import sys
7
+ import os
8
+ import time
9
+ import signal
10
+ import threading
11
+ import logging
12
+ from typing import Optional, List, Tuple
13
+
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format="%(asctime)s [%(levelname)s] %(message)s",
17
+ )
18
+
19
+
20
+ class MineConnectServer:
21
+ """
22
+ Main server class that orchestrates TCP/UDP bridges and cloudflared tunnels.
23
+
24
+ Usage:
25
+ server = MineConnectServer(tcp_port=25565, udp_port=19132)
26
+ server.start() # Blocks until stopped with Ctrl+C
27
+ """
28
+
29
+ def __init__(self, tcp_port: int = 25565, udp_port: int = 19132,
30
+ single_tunnel: bool = False, ws_tcp_port: int = 8764,
31
+ ws_udp_port: int = 8765):
32
+ """
33
+ Initialize MineConnect server configuration.
34
+
35
+ Args:
36
+ tcp_port: Minecraft Java Edition server port (default: 25565)
37
+ udp_port: Geyser/Bedrock server port (default: 19132)
38
+ single_tunnel: Use one tunnel with two ingress rules (default: False)
39
+ ws_tcp_port: WebSocket port for TCP bridge (default: 8764)
40
+ ws_udp_port: WebSocket port for UDP bridge (default: 8765)
41
+ """
42
+ self.tcp_port = tcp_port
43
+ self.udp_port = udp_port
44
+ self.single_tunnel = single_tunnel
45
+ self.ws_tcp_port = ws_tcp_port
46
+ self.ws_udp_port = ws_udp_port
47
+
48
+ self.logger = logging.getLogger("mineconnect-server")
49
+ self._children: List[Tuple[str, subprocess.Popen]] = []
50
+ self._running = False
51
+
52
+ # Get package directory for config files
53
+ self.package_dir = os.path.dirname(os.path.abspath(__file__))
54
+
55
+ def _launch(self, name: str, cmd: List[str], cwd: Optional[str] = None) -> Optional[subprocess.Popen]:
56
+ """Launch a subprocess and attach logging."""
57
+ self.logger.info("Starting %s: %s", name, " ".join(cmd))
58
+ try:
59
+ proc = subprocess.Popen(
60
+ cmd,
61
+ stdout=subprocess.PIPE,
62
+ stderr=subprocess.STDOUT,
63
+ text=True,
64
+ cwd=cwd,
65
+ )
66
+ except FileNotFoundError as exc:
67
+ self.logger.error("Cannot start %s: %s", name, exc)
68
+ return None
69
+
70
+ self._children.append((name, proc))
71
+
72
+ def _reader():
73
+ for line in proc.stdout:
74
+ self.logger.info("[%s] %s", name, line.rstrip())
75
+
76
+ threading.Thread(target=_reader, daemon=True).start()
77
+ return proc
78
+
79
+ def _shutdown(self, *_):
80
+ """Gracefully shutdown all services."""
81
+ if not self._running:
82
+ return
83
+
84
+ self.logger.info("Shutting down all services…")
85
+ self._running = False
86
+
87
+ for name, proc in self._children:
88
+ self.logger.info(" Stopping %s", name)
89
+ proc.terminate()
90
+
91
+ for name, proc in self._children:
92
+ try:
93
+ proc.wait(timeout=5)
94
+ except subprocess.TimeoutExpired:
95
+ proc.kill()
96
+
97
+ self._children.clear()
98
+
99
+ def start(self):
100
+ """
101
+ Start the MineConnect server with all bridges and tunnels.
102
+
103
+ This method blocks until interrupted with Ctrl+C or terminated.
104
+ Logs are displayed to the console in real-time.
105
+ """
106
+ self.logger.info("=" * 60)
107
+ self.logger.info(" MineConnect Server Starting")
108
+ self.logger.info(" TCP Port: %d | UDP Port: %d", self.tcp_port, self.udp_port)
109
+ self.logger.info(" Single Tunnel Mode: %s", self.single_tunnel)
110
+ self.logger.info("=" * 60)
111
+
112
+ # Register signal handlers
113
+ signal.signal(signal.SIGINT, self._shutdown)
114
+ signal.signal(signal.SIGTERM, self._shutdown)
115
+
116
+ self._running = True
117
+ py = sys.executable
118
+
119
+ try:
120
+ # 1) TCP<->WS bridge for Java Edition
121
+ tcp_bridge_path = os.path.join(self.package_dir, "tcp_bridge.py")
122
+ self._launch("TCP-WS-Bridge", [
123
+ py, tcp_bridge_path,
124
+ "--ws-port", str(self.ws_tcp_port),
125
+ "--mc-port", str(self.tcp_port),
126
+ ])
127
+
128
+ # 2) UDP<->WS bridge for Bedrock Edition
129
+ udp_bridge_path = os.path.join(self.package_dir, "udp_bridge.py")
130
+ self._launch("UDP-WS-Bridge", [
131
+ py, udp_bridge_path,
132
+ "--ws-port", str(self.ws_udp_port),
133
+ "--geyser-port", str(self.udp_port),
134
+ ])
135
+
136
+ time.sleep(1) # Let bridges initialize
137
+
138
+ # 3) Cloudflared tunnel(s)
139
+ # Run from package directory where configs and creds are located
140
+ if self.single_tunnel:
141
+ cfg = os.path.join(self.package_dir, "tunnel_combined.yml")
142
+ self._launch("Cloudflared", ["cloudflared", "tunnel", "--config", cfg, "run"], cwd=self.package_dir)
143
+ else:
144
+ java_cfg = os.path.join(self.package_dir, "tunnel_java.yml")
145
+ bedrock_cfg = os.path.join(self.package_dir, "tunnel_bedrock.yml")
146
+
147
+ self._launch("Cloudflared-Java", [
148
+ "cloudflared", "tunnel",
149
+ "--config", java_cfg,
150
+ "run",
151
+ ], cwd=self.package_dir)
152
+ self._launch("Cloudflared-Bedrock", [
153
+ "cloudflared", "tunnel",
154
+ "--config", bedrock_cfg,
155
+ "run",
156
+ ], cwd=self.package_dir)
157
+
158
+ self.logger.info("=" * 60)
159
+ self.logger.info(" All services running. Press Ctrl+C to stop.")
160
+ self.logger.info("=" * 60)
161
+
162
+ # Monitor processes
163
+ while self._running:
164
+ for name, proc in self._children:
165
+ if proc.poll() is not None:
166
+ self.logger.warning("%s exited (code %s)", name, proc.returncode)
167
+ if self._running:
168
+ self.logger.error("Service crashed! Initiating shutdown...")
169
+ self._shutdown()
170
+ break
171
+ time.sleep(2)
172
+
173
+ except Exception as exc:
174
+ self.logger.error("Fatal error: %s", exc)
175
+ self._shutdown()
176
+ raise
177
+ finally:
178
+ if self._running:
179
+ self._shutdown()
180
+ self.logger.info("MineConnect server stopped.")
181
+
182
+ def stop(self):
183
+ """Manually stop the server."""
184
+ self._shutdown()
@@ -0,0 +1,161 @@
1
+ """
2
+ TCP <-> WebSocket Bridge for Minecraft Java Edition
3
+
4
+ Runs a WebSocket server. The client keeps a persistent WebSocket and
5
+ sends framed messages:
6
+ 0x01 + data = new TCP session to MC (+ first chunk)
7
+ 0x02 + data = data for current session
8
+ 0x03 = close current session
9
+
10
+ The bridge opens/closes TCP connections to the MC server accordingly.
11
+
12
+ Flow:
13
+ MC Java Client -> [Client Tunnel] -> persistent WS -> [This Bridge] -> TCP -> MC Server
14
+ """
15
+
16
+ import asyncio
17
+ import logging
18
+ import argparse
19
+
20
+ try:
21
+ import websockets
22
+ except ImportError:
23
+ raise SystemExit("Install websockets: pip install websockets")
24
+
25
+ logger = logging.getLogger("tcp-ws-bridge")
26
+
27
+ MSG_NEW = b'\x01'
28
+ MSG_DATA = b'\x02'
29
+ MSG_CLOSE = b'\x03'
30
+
31
+
32
+ async def handle_connection(websocket, mc_host: str, mc_port: int):
33
+ """Handle persistent WS from client tunnel, manage TCP sessions to MC."""
34
+ peer = websocket.remote_address
35
+ logger.info("Client tunnel connected: %s", peer)
36
+
37
+ reader = None
38
+ writer = None
39
+ tcp_read_task = None
40
+
41
+ async def _close_tcp():
42
+ nonlocal reader, writer, tcp_read_task
43
+ if tcp_read_task and not tcp_read_task.done():
44
+ tcp_read_task.cancel()
45
+ tcp_read_task = None
46
+ if writer:
47
+ try:
48
+ writer.close()
49
+ except OSError:
50
+ pass
51
+ reader = None
52
+ writer = None
53
+
54
+ async def _tcp_reader():
55
+ """Read from MC server TCP and send back over WS."""
56
+ nonlocal reader
57
+ try:
58
+ while reader:
59
+ data = await reader.read(65536)
60
+ if not data:
61
+ break
62
+ await websocket.send(MSG_DATA + data)
63
+ except (ConnectionResetError, asyncio.CancelledError, OSError):
64
+ pass
65
+ except websockets.ConnectionClosed:
66
+ pass
67
+
68
+ try:
69
+ async for message in websocket:
70
+ if isinstance(message, str):
71
+ message = message.encode("utf-8")
72
+ if not message:
73
+ continue
74
+
75
+ msg_type = message[0:1]
76
+ payload = message[1:]
77
+
78
+ if msg_type == MSG_NEW:
79
+ # Close any existing session
80
+ await _close_tcp()
81
+
82
+ # Open new TCP to MC server
83
+ try:
84
+ reader, writer = await asyncio.open_connection(mc_host, mc_port)
85
+ logger.info("New TCP session to %s:%s for %s", mc_host, mc_port, peer)
86
+ except (ConnectionRefusedError, OSError) as exc:
87
+ logger.error("Cannot reach MC: %s", exc)
88
+ await websocket.send(MSG_CLOSE)
89
+ continue
90
+
91
+ # Start reading TCP responses
92
+ tcp_read_task = asyncio.ensure_future(_tcp_reader())
93
+
94
+ # Forward the first chunk
95
+ if payload:
96
+ writer.write(payload)
97
+ await writer.drain()
98
+
99
+ elif msg_type == MSG_DATA:
100
+ if writer:
101
+ try:
102
+ writer.write(payload)
103
+ await writer.drain()
104
+ except (ConnectionResetError, OSError):
105
+ await _close_tcp()
106
+ await websocket.send(MSG_CLOSE)
107
+
108
+ elif msg_type == MSG_CLOSE:
109
+ logger.info("Client closed TCP session for %s", peer)
110
+ await _close_tcp()
111
+
112
+ except websockets.ConnectionClosed:
113
+ logger.info("Client tunnel disconnected: %s", peer)
114
+ except Exception as exc:
115
+ logger.error("Bridge error for %s: %s", peer, exc)
116
+ finally:
117
+ await _close_tcp()
118
+ logger.info("Session ended for %s", peer)
119
+
120
+
121
+ async def run_bridge(ws_host: str, ws_port: int, mc_host: str, mc_port: int):
122
+ """Main bridge coroutine."""
123
+ logger.info(
124
+ "TCP<->WS Bridge: ws://%s:%s <-> tcp://%s:%s",
125
+ ws_host, ws_port, mc_host, mc_port,
126
+ )
127
+
128
+ async with websockets.serve(
129
+ lambda ws: handle_connection(ws, mc_host, mc_port),
130
+ ws_host,
131
+ ws_port,
132
+ max_size=None,
133
+ ping_interval=20,
134
+ ping_timeout=30,
135
+ ):
136
+ logger.info("Bridge ready – waiting for connections…")
137
+ await asyncio.Future() # run forever
138
+
139
+
140
+ def main():
141
+ """CLI entry point."""
142
+ logging.basicConfig(
143
+ level=logging.INFO,
144
+ format="%(asctime)s [%(levelname)s] %(message)s",
145
+ )
146
+
147
+ parser = argparse.ArgumentParser(description="TCP<->WS Bridge for MC Java")
148
+ parser.add_argument("--ws-host", default="127.0.0.1")
149
+ parser.add_argument("--ws-port", type=int, default=8764)
150
+ parser.add_argument("--mc-host", default="127.0.0.1")
151
+ parser.add_argument("--mc-port", type=int, default=25565)
152
+ args = parser.parse_args()
153
+
154
+ try:
155
+ asyncio.run(run_bridge(args.ws_host, args.ws_port, args.mc_host, args.mc_port))
156
+ except KeyboardInterrupt:
157
+ logger.info("Shutting down…")
158
+
159
+
160
+ if __name__ == "__main__":
161
+ main()
@@ -0,0 +1,10 @@
1
+ # Cloudflared tunnel config – Bedrock Edition (UDP via WS bridge)
2
+ # Replace TUNNEL_ID_HERE after running setup_tunnels.py
3
+
4
+ tunnel: d75d9307-b76a-410d-9692-16c3cd0926b7
5
+ credentials-file: ./creds/d75d9307-b76a-410d-9692-16c3cd0926b7.json
6
+
7
+ ingress:
8
+ - hostname: bedrock.ideadev.me
9
+ service: http://localhost:8765
10
+ - service: http_status:404
@@ -0,0 +1,13 @@
1
+ # Cloudflared tunnel config – Combined (single tunnel, both editions)
2
+ # Use with: python start_server.py --single-tunnel
3
+ # Replace TUNNEL_ID_HERE after running setup_tunnels.py
4
+
5
+ tunnel: TUNNEL_ID_HERE
6
+ credentials-file: ~/.cloudflared/TUNNEL_ID_HERE.json
7
+
8
+ ingress:
9
+ - hostname: java.ideadev.me
10
+ service: http://localhost:8764
11
+ - hostname: bedrock.ideadev.me
12
+ service: http://localhost:8765
13
+ - service: http_status:404
@@ -0,0 +1,10 @@
1
+ # Cloudflared tunnel config – Java Edition (TCP via WS bridge)
2
+ # Replace TUNNEL_ID_HERE after running setup_tunnels.py
3
+
4
+ tunnel: f06e9c55-aab2-455b-81d5-08423eb92a39
5
+ credentials-file: ./creds/f06e9c55-aab2-455b-81d5-08423eb92a39.json
6
+
7
+ ingress:
8
+ - hostname: java.ideadev.me
9
+ service: http://localhost:8764
10
+ - service: http_status:404
@@ -0,0 +1,99 @@
1
+ """
2
+ One-time setup for MineConnect cloudflared tunnels.
3
+
4
+ Creates two named tunnels, routes DNS to ideadev.me subdomains,
5
+ and patches the YAML config files with the real tunnel IDs.
6
+
7
+ Usage:
8
+ mineconnect-setup # After pip install
9
+ OR
10
+ python -m mineconnect.tunnel_setup
11
+ """
12
+
13
+ import subprocess
14
+ import os
15
+ import sys
16
+ import json
17
+
18
+ DOMAIN = "ideadev.me"
19
+
20
+
21
+ def _run(cmd, check=False):
22
+ print(f" $ {' '.join(cmd)}")
23
+ result = subprocess.run(cmd, capture_output=True, text=True)
24
+ out = (result.stdout + result.stderr).strip()
25
+ if out:
26
+ for line in out.splitlines():
27
+ print(f" {line}")
28
+ if check and result.returncode != 0:
29
+ sys.exit(f"Command failed (exit {result.returncode})")
30
+ return result
31
+
32
+
33
+ def _patch_config(config_dir, filename, tunnel_id):
34
+ path = os.path.join(config_dir, filename)
35
+ if not os.path.exists(path):
36
+ return
37
+ with open(path, "r", encoding="utf-8") as fh:
38
+ text = fh.read()
39
+ text = text.replace("TUNNEL_ID_HERE", tunnel_id)
40
+ with open(path, "w", encoding="utf-8") as fh:
41
+ fh.write(text)
42
+ print(f" -> Updated {filename}")
43
+
44
+
45
+ def main():
46
+ print("=" * 56)
47
+ print(" MineConnect — Tunnel Setup")
48
+ print("=" * 56)
49
+
50
+ # Get config directory (parent of package)
51
+ package_dir = os.path.dirname(os.path.abspath(__file__))
52
+ config_dir = os.path.dirname(package_dir)
53
+
54
+ # 1 — Login
55
+ print("\n[1/5] Cloudflare authentication")
56
+ print(" A browser window will open — please log in.")
57
+ _run(["cloudflared", "tunnel", "login"])
58
+
59
+ # 2 — Create tunnels
60
+ for name in ("mineconnect-java", "mineconnect-bedrock"):
61
+ print(f"\n[2/5] Creating tunnel: {name}")
62
+ _run(["cloudflared", "tunnel", "create", name])
63
+
64
+ # 3 — DNS routing
65
+ print(f"\n[3/5] Routing java.{DOMAIN}")
66
+ _run([
67
+ "cloudflared", "tunnel", "route", "dns",
68
+ "mineconnect-java", f"java.{DOMAIN}",
69
+ ])
70
+
71
+ print(f"\n[4/5] Routing bedrock.{DOMAIN}")
72
+ _run([
73
+ "cloudflared", "tunnel", "route", "dns",
74
+ "mineconnect-bedrock", f"bedrock.{DOMAIN}",
75
+ ])
76
+
77
+ # 4 — Patch config files with real tunnel IDs
78
+ print("\n[5/5] Updating config files")
79
+ result = _run(["cloudflared", "tunnel", "list", "-o", "json"])
80
+ if result.stdout:
81
+ try:
82
+ tunnels = json.loads(result.stdout)
83
+ for t in tunnels:
84
+ name = t.get("name", "")
85
+ tid = t.get("id", "")
86
+ if name == "mineconnect-java":
87
+ _patch_config(config_dir, "tunnel_java.yml", tid)
88
+ elif name == "mineconnect-bedrock":
89
+ _patch_config(config_dir, "tunnel_bedrock.yml", tid)
90
+ except json.JSONDecodeError:
91
+ print(" !! Could not parse tunnel list — edit configs manually.")
92
+
93
+ print("\n" + "=" * 56)
94
+ print(" Setup complete!")
95
+ print("=" * 56)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()
@@ -0,0 +1,112 @@
1
+ """
2
+ UDP <-> WebSocket Bridge for Minecraft Bedrock Edition (Geyser)
3
+
4
+ Runs a WebSocket server. Each incoming WebSocket connection gets its own
5
+ UDP socket to communicate with Geyser. Packets are relayed bidirectionally.
6
+
7
+ Flow:
8
+ Bedrock Client -> [Client Bridge] -> WebSocket -> [This Bridge] -> UDP -> Geyser
9
+ """
10
+
11
+ import asyncio
12
+ import logging
13
+ import argparse
14
+
15
+ try:
16
+ import websockets
17
+ except ImportError:
18
+ raise SystemExit("Install websockets: pip install websockets")
19
+
20
+ logger = logging.getLogger("udp-ws-bridge")
21
+
22
+
23
+ class GeyserRelay(asyncio.DatagramProtocol):
24
+ """Relays UDP responses from Geyser back through the WebSocket."""
25
+
26
+ def __init__(self, websocket):
27
+ self.websocket = websocket
28
+ self.transport = None
29
+
30
+ def connection_made(self, transport):
31
+ self.transport = transport
32
+
33
+ def datagram_received(self, data, addr):
34
+ asyncio.ensure_future(self._forward(data))
35
+
36
+ async def _forward(self, data):
37
+ try:
38
+ await self.websocket.send(data)
39
+ except websockets.ConnectionClosed:
40
+ pass
41
+
42
+ def error_received(self, exc):
43
+ logger.warning("UDP error: %s", exc)
44
+
45
+
46
+ async def handle_connection(websocket, geyser_host: str, geyser_port: int):
47
+ """Bridge one WebSocket client to Geyser over UDP."""
48
+ peer = websocket.remote_address
49
+ logger.info("New WebSocket client: %s", peer)
50
+
51
+ loop = asyncio.get_event_loop()
52
+ transport, protocol = await loop.create_datagram_endpoint(
53
+ lambda: GeyserRelay(websocket),
54
+ remote_addr=(geyser_host, geyser_port),
55
+ )
56
+
57
+ try:
58
+ async for message in websocket:
59
+ if isinstance(message, str):
60
+ message = message.encode("utf-8")
61
+ if isinstance(message, bytes):
62
+ transport.sendto(message)
63
+ except websockets.ConnectionClosed:
64
+ logger.info("Client %s disconnected", peer)
65
+ except Exception as exc:
66
+ logger.error("Bridge error for %s: %s", peer, exc)
67
+ finally:
68
+ transport.close()
69
+ logger.info("Relay closed for %s", peer)
70
+
71
+
72
+ async def run_bridge(ws_host: str, ws_port: int, geyser_host: str, geyser_port: int):
73
+ """Main bridge coroutine."""
74
+ logger.info(
75
+ "UDP<->WS Bridge: ws://%s:%s <-> udp://%s:%s",
76
+ ws_host, ws_port, geyser_host, geyser_port,
77
+ )
78
+
79
+ async with websockets.serve(
80
+ lambda ws: handle_connection(ws, geyser_host, geyser_port),
81
+ ws_host,
82
+ ws_port,
83
+ max_size=None,
84
+ ping_interval=20,
85
+ ping_timeout=30,
86
+ ):
87
+ logger.info("Bridge ready – waiting for connections…")
88
+ await asyncio.Future() # run forever
89
+
90
+
91
+ def main():
92
+ """CLI entry point."""
93
+ logging.basicConfig(
94
+ level=logging.INFO,
95
+ format="%(asctime)s [%(levelname)s] %(message)s",
96
+ )
97
+
98
+ parser = argparse.ArgumentParser(description="UDP<->WS Bridge for Geyser")
99
+ parser.add_argument("--ws-host", default="127.0.0.1")
100
+ parser.add_argument("--ws-port", type=int, default=8765)
101
+ parser.add_argument("--geyser-host", default="127.0.0.1")
102
+ parser.add_argument("--geyser-port", type=int, default=19132)
103
+ args = parser.parse_args()
104
+
105
+ try:
106
+ asyncio.run(run_bridge(args.ws_host, args.ws_port, args.geyser_host, args.geyser_port))
107
+ except KeyboardInterrupt:
108
+ logger.info("Shutting down…")
109
+
110
+
111
+ if __name__ == "__main__":
112
+ main()
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: mineconnect
3
+ Version: 1.0.0
4
+ Summary: Minecraft TCP/UDP to WebSocket bridge with Cloudflare tunnel support
5
+ Author: MineConnect
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.8
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Requires-Python: >=3.8
12
+ Requires-Dist: websockets>=12.0
13
+ Dynamic: author
14
+ Dynamic: classifier
15
+ Dynamic: requires-dist
16
+ Dynamic: requires-python
17
+ Dynamic: summary
@@ -0,0 +1,20 @@
1
+ MANIFEST.in
2
+ README.md
3
+ requirements.txt
4
+ setup.py
5
+ mineconnect/__init__.py
6
+ mineconnect/server.py
7
+ mineconnect/tcp_bridge.py
8
+ mineconnect/tunnel_bedrock.yml
9
+ mineconnect/tunnel_combined.yml
10
+ mineconnect/tunnel_java.yml
11
+ mineconnect/tunnel_setup.py
12
+ mineconnect/udp_bridge.py
13
+ mineconnect.egg-info/PKG-INFO
14
+ mineconnect.egg-info/SOURCES.txt
15
+ mineconnect.egg-info/dependency_links.txt
16
+ mineconnect.egg-info/entry_points.txt
17
+ mineconnect.egg-info/requires.txt
18
+ mineconnect.egg-info/top_level.txt
19
+ mineconnect/creds/d75d9307-b76a-410d-9692-16c3cd0926b7.json
20
+ mineconnect/creds/f06e9c55-aab2-455b-81d5-08423eb92a39.json
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mineconnect-setup = mineconnect.tunnel_setup:main
@@ -0,0 +1 @@
1
+ websockets>=12.0
@@ -0,0 +1 @@
1
+ mineconnect
@@ -0,0 +1 @@
1
+ websockets>=12.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ """
2
+ MineConnect - Minecraft TCP/UDP to WebSocket Bridge with Cloudflare Tunnels
3
+ """
4
+
5
+ from setuptools import setup, find_packages
6
+
7
+ with open("requirements.txt", "r", encoding="utf-8") as f:
8
+ requirements = [line.strip() for line in f if line.strip() and not line.startswith("#")]
9
+
10
+ setup(
11
+ name="mineconnect",
12
+ version="1.0.0",
13
+ description="Minecraft TCP/UDP to WebSocket bridge with Cloudflare tunnel support",
14
+ author="MineConnect",
15
+ packages=find_packages(),
16
+ install_requires=requirements,
17
+ python_requires=">=3.8",
18
+ entry_points={
19
+ "console_scripts": [
20
+ "mineconnect-setup=mineconnect.tunnel_setup:main",
21
+ ],
22
+ },
23
+ classifiers=[
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.8",
26
+ "Programming Language :: Python :: 3.9",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ ],
30
+ package_data={
31
+ "mineconnect": ["*.yml", "creds/*.json"],
32
+ },
33
+ include_package_data=True,
34
+ )