Files
my-botty/install.bat
alexpolo1 61a88d2968 fix(install): make Tesseract/OCR setup work on clean Win10/Win11
Follow-up to the conda scope fix: the same admin/winget assumptions broke
the OCR backend, which is what actually carries item text reading since
tesserocr's MSVC DLL chain commonly fails to load.

- install.bat installed Tesseract via `winget install` with no --scope,
  i.e. machine-wide into "C:\Program Files", which requires admin. On a
  clean non-admin box this failed and left NO working OCR backend at all
  (tesserocr already fails), so OCR_READY=0 and item reading was dead.
  Now: winget machine scope -> winget --scope user -> direct download of
  the official NSIS installer with a per-user /D= target. Also stops
  trusting winget's exit code (non-zero when already installed) and
  re-resolves tesseract.exe after each attempt.
- The downloaded installer is size-checked (~50 MB; <20 MB = failed
  download) before being executed, matching the Miniforge handling.
- Added a :find_tesseract subroutine that resolves tesseract.exe from
  Program Files, Program Files (x86), %LOCALAPPDATA%\Programs,
  %ProgramData% and PATH. Verification now uses the resolved path instead
  of the hardcoded "C:\Program Files" one.
- ocr.py: added Program Files (x86) and the per-user
  %LOCALAPPDATA%\Programs\Tesseract-OCR location to the runtime search
  order, since per-user installs are not on PATH.
- run_botty.bat: only export PYTESSERACT_TESSERACT_CMD when the file
  exists, falling back to the per-user path, so a stale machine-wide
  value cannot shadow a valid per-user install.

Verified on this machine: all install.bat dependency imports OK
(cv2/mss/numpy/transitions/rapidfuzz/pydantic/pytesseract/yaml/discord),
pytesseract resolves tesseract 5.5.0, osdetect reports the win11 profile,
config loads, 140 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 21:28:02 +02:00

489 lines
18 KiB
Batchfile

@echo off
setlocal enabledelayedexpansion
:: Always run from the folder this script lives in
cd /d "%~dp0"
echo ============================================
echo Botty - Dependency Installer
echo ============================================
echo.
:: --- Detect Windows version ---
for /f "tokens=3" %%A in ('reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v CurrentBuild 2^>nul') do set "WIN_BUILD=%%A"
if not defined WIN_BUILD (
echo WARNING: Could not detect Windows build number.
set "WIN_BUILD=0"
)
echo Detected Windows build: %WIN_BUILD%
set /a "WIN_BUILD_NUM=%WIN_BUILD%" 2>nul || set "WIN_BUILD_NUM=0"
if %WIN_BUILD_NUM% GEQ 22000 (
echo Detected Windows 11 -- using relative mouse mode ^(no ABSOLUTE flag^)
set "WIN11=1"
set "REQ_FILE=requirements-win11.txt"
set "ENV_FILE=environment-win11.yml"
) else (
echo Detected Windows 10 or earlier — using absolute mouse mode
set "WIN11=0"
set "REQ_FILE=requirements-win10.txt"
set "ENV_FILE=environment-win10.yml"
)
if not exist "%REQ_FILE%" set "REQ_FILE=requirements.txt"
if not exist "%ENV_FILE%" set "ENV_FILE=environment.yml"
echo Requirements profile: %REQ_FILE%
echo Environment profile: %ENV_FILE%
echo.
:: --- Find conda ---
:: Miniforge3 JustMe default: %LOCALAPPDATA%\miniforge3 (newer) or
:: %USERPROFILE%\miniforge3 (older)
set "CONDA_EXE="
for %%C in (
"%LOCALAPPDATA%\miniforge3\Scripts\conda.exe"
"%USERPROFILE%\miniforge3\Scripts\conda.exe"
"%LOCALAPPDATA%\miniconda3\Scripts\conda.exe"
"%USERPROFILE%\miniconda3\Scripts\conda.exe"
"%USERPROFILE%\anaconda3\Scripts\conda.exe"
"%ProgramData%\miniforge3\Scripts\conda.exe"
"%ProgramData%\miniconda3\Scripts\conda.exe"
"%ProgramData%\anaconda3\Scripts\conda.exe"
) do (
if exist %%C (
set "CONDA_EXE=%%~C"
goto :found_conda
)
)
echo conda not found — installing Miniforge3 automatically...
echo This is a one-time setup (~100 MB). Please wait.
echo.
:: --- Method 1: winget (cleanest; available on Win10 1709+ and all Win11) ---
:: --scope user is critical: without it winget defaults to a machine-wide
:: install (lands in %ProgramData%) which REQUIRES admin. A normal double-click
:: (no elevation) then fails silently and conda never installs. --scope user
:: installs to %USERPROFILE%\miniforge3 with no admin needed — which is also one
:: of the locations :rescan_conda searches.
winget --version >nul 2>&1
if %errorlevel% equ 0 (
echo Installing via winget...
winget install --id CondaForge.Miniforge3 --exact --silent --scope user ^
--accept-package-agreements --accept-source-agreements
:: winget returns non-zero when the package is already installed, so don't
:: trust the exit code — rescan for conda and only fall through to the
:: direct download if it's genuinely still missing.
for %%C in (
"%LOCALAPPDATA%\miniforge3\Scripts\conda.exe"
"%USERPROFILE%\miniforge3\Scripts\conda.exe"
"%ProgramData%\miniforge3\Scripts\conda.exe"
) do if exist %%C goto :rescan_conda
echo winget did not produce a usable conda — falling back to direct download.
echo.
)
:: --- Method 2: direct download + silent NSIS install ---
:: Uses curl (built into Win10/11) with PowerShell as fallback.
:: $ProgressPreference=SilentlyContinue is critical — without it PowerShell's
:: progress bar drops download speed from ~50 MB/s to ~1 MB/s.
:: start /wait "" is required: plain "installer /S" can return before install
:: finishes. /D= intentionally omitted — NSIS /D= breaks on paths with spaces.
set "MF_INSTALLER=%TEMP%\Miniforge3-installer.exe"
set "MF_URL=https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Windows-x86_64.exe"
del /q "%MF_INSTALLER%" >nul 2>&1
echo Downloading Miniforge3...
curl -Lk --progress-bar "%MF_URL%" -o "%MF_INSTALLER%" 2>&1
if %errorlevel% neq 0 (
echo curl failed, trying PowerShell...
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"$ProgressPreference='SilentlyContinue'; try { Invoke-WebRequest -Uri '%MF_URL%' -OutFile '%MF_INSTALLER%' -UseBasicParsing; exit 0 } catch { Write-Host $_.Exception.Message; exit 1 }"
)
if not exist "%MF_INSTALLER%" (
echo.
echo ERROR: Could not download Miniforge3. Check your internet connection, or
echo install it manually then re-run install.bat:
echo https://github.com/conda-forge/miniforge/releases/latest
echo.
pause
exit /b 1
)
:: Verify the download is a real installer, not a truncated file or an HTML
:: error page served with a 200. The Miniforge installer is ~78 MB; anything
:: under 40 MB means the download failed even though a file exists.
for %%A in ("%MF_INSTALLER%") do set "MF_SIZE=%%~zA"
if not defined MF_SIZE set "MF_SIZE=0"
if %MF_SIZE% LSS 41943040 (
echo.
echo ERROR: Downloaded Miniforge3 installer is only %MF_SIZE% bytes — the
echo download was incomplete or blocked. Check your internet/proxy, or install
echo manually then re-run install.bat:
echo https://github.com/conda-forge/miniforge/releases/latest
echo.
del /q "%MF_INSTALLER%" >nul 2>&1
pause
exit /b 1
)
echo Installing Miniforge3 (this takes ~1 minute)...
start /wait "" "%MF_INSTALLER%" /S /InstallationType=JustMe /AddToPath=0 /RegisterPython=0 /NoShortcuts=1 /NoRegistry=1
if %errorlevel% neq 0 (
echo.
echo ERROR: Miniforge3 installer failed (exit code %errorlevel%).
echo Try running it manually: %MF_INSTALLER%
echo.
pause
exit /b 1
)
del /q "%MF_INSTALLER%" >nul 2>&1
echo Miniforge3 installed.
:rescan_conda
echo.
:: Re-scan for conda now that Miniforge is installed
for %%C in (
"%LOCALAPPDATA%\miniforge3\Scripts\conda.exe"
"%USERPROFILE%\miniforge3\Scripts\conda.exe"
"%LOCALAPPDATA%\miniconda3\Scripts\conda.exe"
"%USERPROFILE%\miniconda3\Scripts\conda.exe"
"%USERPROFILE%\anaconda3\Scripts\conda.exe"
"%ProgramData%\miniforge3\Scripts\conda.exe"
"%ProgramData%\miniconda3\Scripts\conda.exe"
"%ProgramData%\anaconda3\Scripts\conda.exe"
) do (
if exist %%C (
set "CONDA_EXE=%%~C"
goto :found_conda
)
)
echo ERROR: Miniforge3 installed but conda.exe still not found. Please restart
echo the installer or contact support.
pause
exit /b 1
:found_conda
echo Found conda: %CONDA_EXE%
echo.
:: --- Test conda works ---
echo Testing conda...
"%CONDA_EXE%" --version >nul 2>&1
if %errorlevel% neq 0 (
echo.
echo ERROR: conda found but failed to run. Your conda installation may be broken.
echo Try re-installing Miniforge: https://github.com/conda-forge/miniforge
echo.
pause
exit /b 1
)
echo conda is working.
echo.
:: --- Find botty Python ---
set "PYTHON="
for %%C in (
"%LOCALAPPDATA%\miniforge3\envs\botty\python.exe"
"%LOCALAPPDATA%\miniconda3\envs\botty\python.exe"
"%USERPROFILE%\miniforge3\envs\botty\python.exe"
"%USERPROFILE%\miniconda3\envs\botty\python.exe"
"%USERPROFILE%\anaconda3\envs\botty\python.exe"
"%ProgramData%\miniforge3\envs\botty\python.exe"
"%ProgramData%\miniconda3\envs\botty\python.exe"
"%ProgramData%\anaconda3\envs\botty\python.exe"
) do (
if exist %%C (
set "PYTHON=%%~C"
goto :check_env
)
)
:: --- Create botty env ---
echo Creating 'botty' conda environment...
"%CONDA_EXE%" env create -f "%ENV_FILE%"
if %errorlevel% neq 0 (
echo.
echo ERROR: conda env create failed. See output above.
pause
exit /b 1
)
:check_env
:: Re-scan for Python after env creation
set "PYTHON="
for %%C in (
"%LOCALAPPDATA%\miniforge3\envs\botty\python.exe"
"%LOCALAPPDATA%\miniconda3\envs\botty\python.exe"
"%USERPROFILE%\miniforge3\envs\botty\python.exe"
"%USERPROFILE%\miniconda3\envs\botty\python.exe"
"%USERPROFILE%\anaconda3\envs\botty\python.exe"
"%ProgramData%\miniforge3\envs\botty\python.exe"
"%ProgramData%\miniconda3\envs\botty\python.exe"
"%ProgramData%\anaconda3\envs\botty\python.exe"
) do (
if exist %%C (
set "PYTHON=%%~C"
goto :env_ready
)
)
echo.
echo ERROR: botty env was created but python.exe was not found.
pause
exit /b 1
:env_ready
echo Botty Python: %PYTHON%
:: --- Install pip requirements ---
:: --progress-bar off: pip's progress bar uses \r to redraw in place; when this
:: script's output is redirected to a file (e.g. run_install_capture.bat) those
:: redraws become millions of lines and the log balloons to many GB. Turning the
:: bar off keeps the install log small without hiding warnings/errors.
echo.
echo Installing Botty requirements from %REQ_FILE%...
"%CONDA_EXE%" run -n botty python -m pip install --progress-bar off -r "%REQ_FILE%"
if %errorlevel% neq 0 (
echo.
echo ERROR: pip requirements install failed for %REQ_FILE%.
pause
exit /b 1
)
echo.
echo Botty osdetect:
"%CONDA_EXE%" run -n botty python -c "import sys; sys.path.insert(0, 'src'); from utils.os_detect import describe_current_os; print(describe_current_os())"
:: --- OCR setup ---
:: Two OCR backends, tried in order at runtime:
:: 1. tesserocr — C extension calling tesseract DLLs directly (fast)
:: 2. pytesseract — subprocess calling tesseract.exe (always works if exe exists)
:: At least one must work. Install.bat sets up both and reports which is ready.
echo.
echo Setting up OCR...
:: Find the botty env directory
set "BOTTY_ENV_DIR="
for %%C in (
"%LOCALAPPDATA%\miniforge3\envs\botty"
"%LOCALAPPDATA%\miniconda3\envs\botty"
"%USERPROFILE%\miniforge3\envs\botty"
"%USERPROFILE%\miniconda3\envs\botty"
"%USERPROFILE%\anaconda3\envs\botty"
"%ProgramData%\miniforge3\envs\botty"
"%ProgramData%\miniconda3\envs\botty"
"%ProgramData%\anaconda3\envs\botty"
) do (
if exist %%C (
set "BOTTY_ENV_DIR=%%~C"
goto :env_found
)
)
echo ERROR: Could not find botty env directory.
pause
exit /b 1
:env_found
set "TESS_PATH=%BOTTY_ENV_DIR%\Library\bin;%BOTTY_ENV_DIR%\Library\lib;%BOTTY_ENV_DIR%\DLLs;%BOTTY_ENV_DIR%\Scripts"
:: --- Backend 1: tesserocr (fast path, best-effort) ---
:: tesserocr uses the C++ API of tesseract and needs an MSVC-compiled tesseract DLL.
:: The bundled dependencies folder provides:
:: tesseract52.dll — MSVC tesseract 5.2.0 (conda-forge, compiled vs2019)
:: tesserocr.cp310-...pyd — MSVC tesserocr 2.5.2 linked against tesseract52.dll
:: (both require leptonica-1.78.0.dll from the conda tesseract=4.x install below)
:: If any step fails the bot still works via pytesseract below.
:: Remove any existing pip/wheel tesserocr
"%PYTHON%" -m pip uninstall tesserocr -y >nul 2>&1
:: conda tesseract 4.x provides leptonica-1.78.0.dll (MSVC) which tesseract52.dll needs
"%CONDA_EXE%" install -n botty "tesseract=4.*" -c conda-forge -y >nul 2>&1
:: conda tesseract.exe crashes on Win10 and Win11 — disable it, keep the DLLs
if exist "%BOTTY_ENV_DIR%\Library\bin\tesseract.exe" (
rename "%BOTTY_ENV_DIR%\Library\bin\tesseract.exe" "tesseract_disabled.exe" >nul 2>&1
)
:: Deploy the MSVC tesseract52.dll (tesserocr.pyd imports exactly this name)
if exist "dependencies\tesseract52.dll" (
copy /y "dependencies\tesseract52.dll" "%BOTTY_ENV_DIR%\Library\bin\tesseract52.dll" >nul
)
:: Deploy the conda-forge tesserocr .pyd (compiled against tesseract52.dll)
if exist "dependencies\tesserocr.cp310-win_amd64.pyd" (
copy /y "dependencies\tesserocr.cp310-win_amd64.pyd" "%BOTTY_ENV_DIR%\lib\site-packages\tesserocr.cp310-win_amd64.pyd" >nul
) else if exist "dependencies\tesserocr-2.5.2-cp310-cp310-win_amd64.whl" (
"%CONDA_EXE%" run -n botty python -m pip install --quiet --force-reinstall --no-deps "dependencies\tesserocr-2.5.2-cp310-cp310-win_amd64.whl" >nul 2>&1
)
:: --- Backend 2: pytesseract (reliable fallback) ---
:: This is the backend that actually carries OCR on most machines (tesserocr's
:: MSVC DLL chain frequently fails), so it must install without admin rights and
:: without winget -- neither is guaranteed on a clean Win10 box.
"%CONDA_EXE%" run -n botty python -m pip install --progress-bar off pytesseract >nul 2>&1
if %errorlevel% neq 0 (
echo WARNING: Could not install pytesseract Python wrapper.
)
call :find_tesseract
if not defined TESS_EXE (
echo Installing Tesseract OCR...
:: Try winget machine scope, then user scope (no admin needed). winget's
:: exit code is unreliable (non-zero when already installed), so after each
:: attempt we re-resolve tesseract.exe rather than trusting errorlevel.
winget --version >nul 2>&1
if !errorlevel! equ 0 (
winget install --id tesseract-ocr.tesseract --exact --silent ^
--accept-package-agreements --accept-source-agreements >nul 2>&1
call :find_tesseract
if not defined TESS_EXE (
winget install --id tesseract-ocr.tesseract --exact --silent --scope user ^
--accept-package-agreements --accept-source-agreements >nul 2>&1
call :find_tesseract
)
)
)
:: Last resort: direct download of the official NSIS installer and a per-user
:: install. Covers clean Win10 machines with no winget and no admin rights.
if not defined TESS_EXE (
echo winget unavailable or failed -- downloading Tesseract directly...
set "TS_INSTALLER=%TEMP%\tesseract-setup.exe"
set "TS_URL=https://github.com/tesseract-ocr/tesseract/releases/download/5.5.0/tesseract-ocr-w64-setup-5.5.0.20241111.exe"
set "TS_DEST=%LOCALAPPDATA%\Programs\Tesseract-OCR"
del /q "!TS_INSTALLER!" >nul 2>&1
curl -Lk --progress-bar "!TS_URL!" -o "!TS_INSTALLER!" 2>&1
if not exist "!TS_INSTALLER!" (
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"$ProgressPreference='SilentlyContinue'; try { Invoke-WebRequest -Uri '!TS_URL!' -OutFile '!TS_INSTALLER!' -UseBasicParsing; exit 0 } catch { exit 1 }"
)
:: Sanity-check the size (installer is ~50 MB) before executing it.
set "TS_SIZE=0"
if exist "!TS_INSTALLER!" for %%A in ("!TS_INSTALLER!") do set "TS_SIZE=%%~zA"
if !TS_SIZE! GEQ 20971520 (
:: NSIS /D= must be last and unquoted, and breaks on paths containing
:: spaces -- so only use it when the target path has none.
echo !TS_DEST! | find " " >nul
if !errorlevel! equ 0 (
start /wait "" "!TS_INSTALLER!" /S
) else (
start /wait "" "!TS_INSTALLER!" /S /D=!TS_DEST!
)
del /q "!TS_INSTALLER!" >nul 2>&1
call :find_tesseract
)
)
if defined TESS_EXE (
echo Tesseract: !TESS_EXE!
) else (
echo WARNING: Tesseract could not be installed automatically. Install manually:
echo https://github.com/tesseract-ocr/tesseract/releases
echo Download the win64 installer, run it, then re-run install.bat.
)
:: --- Verify OCR: at least one backend must work ---
echo.
echo Checking OCR backends...
set "PATH=%TESS_PATH%;%PATH%"
set "OCR_READY=0"
"%CONDA_EXE%" run -n botty python -c "import os; os.add_dll_directory(r'%BOTTY_ENV_DIR%\Library\bin'); from tesserocr import PyTessBaseAPI, OEM; PyTessBaseAPI(psm=7,oem=OEM.LSTM_ONLY,path='assets/tessdata',lang='hover-eng_inconsolata_inv_th_fast').End()" >nul 2>&1
if %errorlevel% == 0 (
echo tesserocr: OK ^(fast path^)
set "OCR_READY=1"
) else (
echo tesserocr: not available ^(DLL issue -- bot will use pytesseract instead^)
)
if defined TESS_EXE (
"%CONDA_EXE%" run -n botty python -c "import pytesseract; pytesseract.pytesseract.tesseract_cmd=r'!TESS_EXE!'; pytesseract.get_tesseract_version()" >nul 2>&1
if !errorlevel! == 0 (
echo pytesseract: OK ^(reliable fallback^)
set "OCR_READY=1"
) else (
echo pytesseract: tesseract.exe found but call failed
)
) else (
echo pytesseract: tesseract.exe not found -- install from link above if needed
)
if "%OCR_READY%"=="0" (
echo.
echo WARNING: No OCR backend is working. Item hover text will not be read.
echo Install Tesseract OCR: https://github.com/tesseract-ocr/tesseract/releases
echo Then re-run install.bat.
)
:: --- Verify all dependencies ---
echo.
echo Verifying all dependencies...
set "ALL_OK=1"
for %%M in (cv2 mss numpy transitions rapidfuzz pydantic pytesseract yaml) do (
set "PATH=%TESS_PATH%;%PATH%"
"%CONDA_EXE%" run -n botty python -c "import %%M" >nul 2>&1
if !errorlevel! neq 0 (
echo FAILED: %%M
set "ALL_OK=0"
) else (
echo %%M: OK
)
)
:: discord.py imports aiohttp which calls ssl.create_default_context() at import
:: time. A malformed cert in the Windows store triggers ASN1 NOT_ENOUGH_DATA.
:: The bot patches this at runtime via _patch_ssl(); check import with the patch.
set "PATH=%TESS_PATH%;%PATH%"
"%CONDA_EXE%" run -n botty python -c "import ssl; ssl.SSLContext.load_default_certs = lambda *a, **kw: None; import discord" >nul 2>&1
if !errorlevel! neq 0 (
echo FAILED: discord
set "ALL_OK=0"
) else (
echo discord: OK
)
if "%OCR_READY%"=="0" set "ALL_OK=0"
if "%ALL_OK%"=="1" (
echo.
echo All dependencies verified.
) else (
echo.
echo WARNING: Some dependencies failed above.
echo This is often a PATH issue. Try running from a fresh CMD/PowerShell:
echo conda activate botty
echo python -c "import cv2"
echo.
)
:install_done
echo.
echo ============================================
echo Installation complete!
echo Run botty with: run_botty.bat
echo ============================================
echo.
goto :eof
:: --- Resolve tesseract.exe into TESS_EXE ---
:: Checks machine-wide (winget default / manual install) and per-user (winget
:: --scope user / our direct NSIS fallback) locations, plus PATH. Sets TESS_EXE
:: to the first hit, or clears it if none found.
:find_tesseract
set "TESS_EXE="
for %%T in (
"C:\Program Files\Tesseract-OCR\tesseract.exe"
"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe"
"%LOCALAPPDATA%\Programs\Tesseract-OCR\tesseract.exe"
"%ProgramData%\Tesseract-OCR\tesseract.exe"
) do (
if exist %%T (
set "TESS_EXE=%%~T"
goto :eof
)
)
for /f "delims=" %%T in ('where tesseract 2^>nul') do (
set "TESS_EXE=%%T"
goto :eof
)
goto :eof