Rename to mosfet.py, delete linux bash scripts
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
+33
-17
@@ -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,18 +89,25 @@ 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()
|
||||||
|
|
||||||
def run_api():
|
def run_api():
|
||||||
host = '0.0.0.0'
|
host = '0.0.0.0'
|
||||||
@@ -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
@@ -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
@@ -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__':
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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."
|
|
||||||
Reference in New Issue
Block a user