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
flee from threats.
## Requirements
## Requirements and setup
- Python >= 3.6
- pip, virtualenv, git, wget, unzip
- Python 3.11+
- The locally updated pyCraft checkout at `../pyCraft`
## Linux Setup
Assuming Debian / Ubuntu based distro:
From the repository root, create and activate a virtual environment, then
install the connection-mode dependencies:
```
$ sudo apt update
$ sudo apt install build-essential python3 python3-dev python3-pip virtualenv git wget unzip
$ git clone https://git.tannercollin.com/tanner/minecraft-bot.git
$ cd minecraft-bot/
$ python3 -m venv .venv
$ source .venv/bin/activate
$ python -m pip install flask flask-cors munch watchdog -e ../pyCraft
```
## 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.
```
$ SERVER=localhost PORT=12345 ./run_linux.sh
```
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.
The current entry point connects and handles AuthMe, but world automation is
not enabled yet: those modules still depend on 1.16-era packet and world data.
## Usage
+32 -16
View File
@@ -1,8 +1,8 @@
import importlib
import json
import threading
import time
import traceback
import json
import logging
log = logging.getLogger('werkzeug')
@@ -18,11 +18,15 @@ from munch import Munch
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from mosfet import bot
from mosfet import connect
global_state = Munch()
g = global_state
g.connection = False
g.connected = False
g.connection_status = 'disconnected'
g.player_position = None
g.last_chat = None
g.name = None
g.mcdata = False
g.pos = False
@@ -57,20 +61,27 @@ def api_global():
def root():
return app.send_static_file('index.html')
reload_timeout = time.time()
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):
global reload_timeout
nonlocal reload_timeout, reload_requested
if time.time() - reload_timeout > 2.0:
reload_timeout = time.time()
reload_requested = True
print('Reloading...')
g.running = False
event_handler = PatternMatchingEventHandler(patterns=['*.py'], ignore_patterns=['./main.py'])
event_handler.on_any_event = reload_bot
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.start()
@@ -78,16 +89,23 @@ def main():
while True:
try:
g.running = True
bot.bot(global_state)
importlib.reload(bot)
except BaseException as e:
connect.main(global_state)
if not reload_requested:
break
reload_requested = False
importlib.reload(connect)
except KeyboardInterrupt:
break
except BaseException:
g.running = True
traceback.print_exc()
print('Locking...')
while g.running:
time.sleep(1)
importlib.reload(bot)
except KeyboardInterrupt:
reload_requested = False
importlib.reload(connect)
finally:
g.running = False
observer.stop()
observer.join()
@@ -106,7 +124,5 @@ def run_api():
port += 1
if __name__ == '__main__':
threading.Thread(target=run_api).start()
time.sleep(1)
threading.Thread(target=run_api, daemon=True).start()
main()
+4 -7
View File
@@ -1,5 +1,5 @@
if __name__ == '__main__':
print('Run main.py instead.')
print('Run mosfet.py instead.')
exit(1)
import os
@@ -251,11 +251,8 @@ def bot(global_state):
if not SERVER:
print()
print('You must specify a server to connect to. For example:')
print('SERVER=minecraft.example.com ./run_linux.sh')
print('SERVER=localhost PORT=12345 ./run_linux.sh')
print()
print('If you want to use your own account:')
print('EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh')
print('SERVER=minecraft.example.com python mosfet.py')
print('SERVER=localhost PORT=12345 python mosfet.py')
os._exit(0)
elif not g.connection:
if EMAIL and PASSWORD:
@@ -274,7 +271,7 @@ def bot(global_state):
print('')
print('Did you forget to specify an email?')
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)
else:
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.modern import (
CancelPostjoinDialog, ChatCommand, PlayShowDialog,
SubmitPostjoinLoginDialog, SystemChat, TeleportPosition,
PlayLogin, SubmitPostjoinLoginDialog, SystemChat, TeleportPosition,
)
@@ -38,7 +38,7 @@ def component_text(value):
return str(value)
def main():
def main(global_state=None):
server = os.environ.get('SERVER', 'games.protospace.ca')
port = int(os.environ.get('PORT', '25565'))
username = os.environ.get('PLAYER_NAME', 'mosfet1')
@@ -48,6 +48,19 @@ def main():
allowed_versions=['26.3'])
connection.authme_password = password
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):
if action in sent_auth_commands:
@@ -61,6 +74,8 @@ def main():
@connection.listener(TeleportPosition)
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':
send_auth_command('register')
@@ -79,6 +94,8 @@ def main():
@connection.listener(SystemChat)
def on_system_chat(packet):
message = component_text(packet.content)
if global_state is not None:
global_state.last_chat = message
print('[server] ' + message, flush=True)
lowered = message.lower()
if 'already registered' in lowered:
@@ -96,16 +113,27 @@ def main():
username, server, port), flush=True)
try:
while connection.connected:
if global_state is not None and not global_state.get('running', True):
break
thread = connection.networking_thread
if thread is None:
break
thread.join(timeout=0.5)
if connection.connected:
connection.disconnect()
if connection.exception:
raise connection.exception
print('Connection ended.', flush=True)
except KeyboardInterrupt:
print('Disconnecting...', flush=True)
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__':
-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."