Rename to mosfet.py, delete linux bash scripts

This commit is contained in:
Tanner
2026-11-03 20:07:47 +00:00
parent d43533bcfe
commit 891f14b988
7 changed files with 82 additions and 254 deletions
+15 -42
View File
@@ -7,61 +7,34 @@ Mosfet is able to farm wood by cutting trees, farm crops, gather sand, farm
netherwart, and trade with villagers to get emeralds. He can eat, sleep, and netherwart, and trade with villagers to get emeralds. He can eat, sleep, and
flee from threats. flee from threats.
## Requirements ## Requirements and setup
- Python >= 3.6 - Python 3.11+
- pip, virtualenv, git, wget, unzip - The locally updated pyCraft checkout at `../pyCraft`
## Linux Setup From the repository root, create and activate a virtual environment, then
install the connection-mode dependencies:
Assuming Debian / Ubuntu based distro:
``` ```
$ sudo apt update $ python3 -m venv .venv
$ sudo apt install build-essential python3 python3-dev python3-pip virtualenv git wget unzip $ source .venv/bin/activate
$ python -m pip install flask flask-cors munch watchdog -e ../pyCraft
$ git clone https://git.tannercollin.com/tanner/minecraft-bot.git
$ cd minecraft-bot/
``` ```
## Running ## Running
If you want to use the built-in burner account (Minecraft name `mattstack`): Run the bot directly with Python. For the Protospace offline-mode server:
``` ```
$ SERVER=minecraft.example.com ./run_linux.sh $ SERVER=games.protospace.ca python mosfet.py
``` ```
Or, use `PORT` to specify a custom port to connect to: `mosfet.py` defaults to that server, player name `mosfet1`, and the current
AuthMe password. Override `SERVER`, `PORT`, `PLAYER_NAME`, or `GAME_PASSWORD`
through environment variables as needed. Microsoft authentication is not used.
``` The current entry point connects and handles AuthMe, but world automation is
$ SERVER=localhost PORT=12345 ./run_linux.sh not enabled yet: those modules still depend on 1.16-era packet and world data.
```
Or, if you have your own alt account for the bot:
```
$ EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh
```
### Current offline servers (Minecraft 26.3)
The legacy automation in `main.py` uses 1.16-era world/chunk packet handling.
For an offline-mode server running Minecraft 26.3, use the minimal current-
protocol connection mode instead. It joins as `mosfet1` by default, answers
AuthMe registration/login prompts using `GAME_PASSWORD`, and stays connected;
world automation is not enabled in this mode.
The locally updated pyCraft checkout is expected at `../pyCraft`:
```
$ ./run_connect.sh
```
You can override `SERVER`, `PORT`, `PLAYER_NAME`, or `GAME_PASSWORD` through
environment variables. The current AuthMe default is `mosfetbot`. pyCraft is
being extended locally for protocol 777; no Microsoft authentication is used
in this mode.
## Usage ## Usage
+32 -16
View File
@@ -1,8 +1,8 @@
import importlib import importlib
import json
import threading import threading
import time import time
import traceback import traceback
import json
import logging import logging
log = logging.getLogger('werkzeug') log = logging.getLogger('werkzeug')
@@ -18,11 +18,15 @@ from munch import Munch
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler from watchdog.events import PatternMatchingEventHandler
from mosfet import bot from mosfet import connect
global_state = Munch() global_state = Munch()
g = global_state g = global_state
g.connection = False g.connection = False
g.connected = False
g.connection_status = 'disconnected'
g.player_position = None
g.last_chat = None
g.name = None g.name = None
g.mcdata = False g.mcdata = False
g.pos = False g.pos = False
@@ -57,20 +61,27 @@ def api_global():
def root(): def root():
return app.send_static_file('index.html') return app.send_static_file('index.html')
reload_timeout = time.time()
def main(): def main():
"""Connect using the current offline/AuthMe protocol bootstrap.
The legacy automation loop is deliberately not started yet: its packet
handlers still target Minecraft 1.16 and will be migrated incrementally.
"""
reload_timeout = time.time()
reload_requested = False
def reload_bot(event): def reload_bot(event):
global reload_timeout nonlocal reload_timeout, reload_requested
if time.time() - reload_timeout > 2.0: if time.time() - reload_timeout > 2.0:
reload_timeout = time.time() reload_timeout = time.time()
reload_requested = True
print('Reloading...') print('Reloading...')
g.running = False g.running = False
event_handler = PatternMatchingEventHandler(patterns=['*.py'], ignore_patterns=['./main.py'])
event_handler.on_any_event = reload_bot
observer = Observer() observer = Observer()
event_handler = PatternMatchingEventHandler(
patterns=['*.py'], ignore_patterns=['./mosfet.py'])
event_handler.on_any_event = reload_bot
observer.schedule(event_handler, 'mosfet', recursive=True) observer.schedule(event_handler, 'mosfet', recursive=True)
observer.start() observer.start()
@@ -78,16 +89,23 @@ def main():
while True: while True:
try: try:
g.running = True g.running = True
bot.bot(global_state) connect.main(global_state)
importlib.reload(bot) if not reload_requested:
except BaseException as e: break
reload_requested = False
importlib.reload(connect)
except KeyboardInterrupt:
break
except BaseException:
g.running = True g.running = True
traceback.print_exc() traceback.print_exc()
print('Locking...') print('Locking...')
while g.running: while g.running:
time.sleep(1) time.sleep(1)
importlib.reload(bot) reload_requested = False
except KeyboardInterrupt: importlib.reload(connect)
finally:
g.running = False
observer.stop() observer.stop()
observer.join() observer.join()
@@ -106,7 +124,5 @@ def run_api():
port += 1 port += 1
if __name__ == '__main__': if __name__ == '__main__':
threading.Thread(target=run_api).start() threading.Thread(target=run_api, daemon=True).start()
time.sleep(1)
main() main()
+4 -7
View File
@@ -1,5 +1,5 @@
if __name__ == '__main__': if __name__ == '__main__':
print('Run main.py instead.') print('Run mosfet.py instead.')
exit(1) exit(1)
import os import os
@@ -251,11 +251,8 @@ def bot(global_state):
if not SERVER: if not SERVER:
print() print()
print('You must specify a server to connect to. For example:') print('You must specify a server to connect to. For example:')
print('SERVER=minecraft.example.com ./run_linux.sh') print('SERVER=minecraft.example.com python mosfet.py')
print('SERVER=localhost PORT=12345 ./run_linux.sh') print('SERVER=localhost PORT=12345 python mosfet.py')
print()
print('If you want to use your own account:')
print('EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh')
os._exit(0) os._exit(0)
elif not g.connection: elif not g.connection:
if EMAIL and PASSWORD: if EMAIL and PASSWORD:
@@ -274,7 +271,7 @@ def bot(global_state):
print('') print('')
print('Did you forget to specify an email?') print('Did you forget to specify an email?')
print('If you want to use your own account:') print('If you want to use your own account:')
print('EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh') print('Set SERVER and run python mosfet.py.')
os._exit(0) os._exit(0)
else: else:
print('No username or password provided, using burner minecraft account...') print('No username or password provided, using burner minecraft account...')
+30 -2
View File
@@ -6,7 +6,7 @@ import os
from minecraft.networking.connection import Connection from minecraft.networking.connection import Connection
from minecraft.networking.modern import ( from minecraft.networking.modern import (
CancelPostjoinDialog, ChatCommand, PlayShowDialog, CancelPostjoinDialog, ChatCommand, PlayShowDialog,
SubmitPostjoinLoginDialog, SystemChat, TeleportPosition, PlayLogin, SubmitPostjoinLoginDialog, SystemChat, TeleportPosition,
) )
@@ -38,7 +38,7 @@ def component_text(value):
return str(value) return str(value)
def main(): def main(global_state=None):
server = os.environ.get('SERVER', 'games.protospace.ca') server = os.environ.get('SERVER', 'games.protospace.ca')
port = int(os.environ.get('PORT', '25565')) port = int(os.environ.get('PORT', '25565'))
username = os.environ.get('PLAYER_NAME', 'mosfet1') username = os.environ.get('PLAYER_NAME', 'mosfet1')
@@ -48,6 +48,19 @@ def main():
allowed_versions=['26.3']) allowed_versions=['26.3'])
connection.authme_password = password connection.authme_password = password
sent_auth_commands = set() sent_auth_commands = set()
if global_state is not None:
global_state.connection = connection
global_state.name = username
global_state.connected = False
global_state.connection_status = 'connecting'
global_state.player_position = None
@connection.listener(PlayLogin)
def on_play_login(packet):
if global_state is not None:
global_state.connected = True
global_state.connection_status = 'in_game'
def send_auth_command(action): def send_auth_command(action):
if action in sent_auth_commands: if action in sent_auth_commands:
@@ -61,6 +74,8 @@ def main():
@connection.listener(TeleportPosition) @connection.listener(TeleportPosition)
def on_spawn(packet): def on_spawn(packet):
if global_state is not None:
global_state.player_position = (packet.x, packet.y, packet.z)
if getattr(connection, 'authme_mode', 'register') == 'register': if getattr(connection, 'authme_mode', 'register') == 'register':
send_auth_command('register') send_auth_command('register')
@@ -79,6 +94,8 @@ def main():
@connection.listener(SystemChat) @connection.listener(SystemChat)
def on_system_chat(packet): def on_system_chat(packet):
message = component_text(packet.content) message = component_text(packet.content)
if global_state is not None:
global_state.last_chat = message
print('[server] ' + message, flush=True) print('[server] ' + message, flush=True)
lowered = message.lower() lowered = message.lower()
if 'already registered' in lowered: if 'already registered' in lowered:
@@ -96,16 +113,27 @@ def main():
username, server, port), flush=True) username, server, port), flush=True)
try: try:
while connection.connected: while connection.connected:
if global_state is not None and not global_state.get('running', True):
break
thread = connection.networking_thread thread = connection.networking_thread
if thread is None: if thread is None:
break break
thread.join(timeout=0.5) thread.join(timeout=0.5)
if connection.connected:
connection.disconnect()
if connection.exception: if connection.exception:
raise connection.exception raise connection.exception
print('Connection ended.', flush=True) print('Connection ended.', flush=True)
except KeyboardInterrupt: except KeyboardInterrupt:
print('Disconnecting...', flush=True) print('Disconnecting...', flush=True)
connection.disconnect() connection.disconnect()
finally:
if connection.connected:
connection.disconnect()
if global_state is not None:
global_state.connection = False
global_state.connected = False
global_state.connection_status = 'disconnected'
if __name__ == '__main__': if __name__ == '__main__':
-19
View File
@@ -1,19 +0,0 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"
if ! command -v uv >/dev/null 2>&1; then
echo "Install uv to create the local Python environment." >&2
exit 1
fi
if [ ! -x .venv/bin/python ]; then
uv venv .venv
fi
uv pip install --python .venv/bin/python -e ../pyCraft
export SERVER="${SERVER:-games.protospace.ca}"
export PORT="${PORT:-25565}"
export PLAYER_NAME="${PLAYER_NAME:-mosfet1}"
export GAME_PASSWORD="${GAME_PASSWORD:-mosfetbot}"
exec .venv/bin/python -m mosfet.connect
-79
View File
@@ -1,79 +0,0 @@
#!/bin/bash
set -eu
if [ ! -d "mosfet" ]
then
echo "You must run this from the mosfet-minecraft-bot directory."
exit
fi
if ! command -v virtualenv &> /dev/null
then
echo "virtualenv could not be found, please install with:"
echo "sudo apt install virtualenv"
exit
fi
if ! command -v wget &> /dev/null
then
echo "wget could not be found, please install with:"
echo "sudo apt install wget"
exit
fi
if ! command -v unzip &> /dev/null
then
echo "unzip could not be found, please install with:"
echo "sudo apt install unzip"
exit
fi
if ! command -v python3 &> /dev/null
then
echo "python3 could not be found, please install with:"
echo "sudo apt install python3"
exit
fi
if ! command -v pip3 &> /dev/null
then
echo "pip3 could not be found, please install with:"
echo "sudo apt install pip3"
exit
fi
# download minecraft data
if [ ! -d "minecraft_data" ]
then
echo "Grabbing minecraft data..."
VERSION="1.16.4"
wget -Omcdata.zip "https://apimon.de/mcdata/$VERSION/$VERSION.zip"
mkdir minecraft_data
unzip mcdata.zip -d minecraft_data
rm mcdata.zip
fi
# https://github.com/pypa/virtualenv/issues/1029
PS1=${PS1:-}
# create virtual environment
if [ ! -d "env" ]
then
echo "Installing Python requirements..."
virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt
else
source env/bin/activate
fi
echo "Running bot..."
python main.py
-88
View File
@@ -1,88 +0,0 @@
#!/bin/bash
set -eu
if [ ! -d "mosfet" ]
then
echo "You must run this from the mosfet-minecraft-bot directory."
exit
fi
if ! command -v virtualenv &> /dev/null
then
echo "virtualenv could not be found, please install with:"
echo "sudo apt install virtualenv"
exit
fi
if ! command -v wget &> /dev/null
then
echo "wget could not be found, please install with:"
echo "sudo apt install wget"
exit
fi
if ! command -v unzip &> /dev/null
then
echo "unzip could not be found, please install with:"
echo "sudo apt install unzip"
exit
fi
if ! command -v python3 &> /dev/null
then
echo "python3 could not be found, please install with:"
echo "sudo apt install python3"
exit
fi
if ! command -v pip3 &> /dev/null
then
echo "pip3 could not be found, please install with:"
echo "sudo apt install pip3"
exit
fi
if ! command -v git &> /dev/null
then
echo "git could not be found, please install with:"
echo "sudo apt install pip3"
exit
fi
# download minecraft data
echo "Grabbing minecraft data..."
VERSION="1.16.4"
wget -Omcdata.zip "https://apimon.de/mcdata/$VERSION/$VERSION.zip"
rm -r minecraft_data || true
mkdir minecraft_data
unzip mcdata.zip -d minecraft_data
rm mcdata.zip
# update code
git stash
git pull --rebase
git stash pop || true
# https://github.com/pypa/virtualenv/issues/1029
PS1=${PS1:-}
# create virtual environment
echo "Installing Python requirements..."
rm -r env || true
rm -r __pycache__ || true
virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt
echo "Done."