@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 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. Keep this comment outside the block: :: a "::" line inside a ( ) block is a parse error. 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 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. ) :: Try winget machine scope, then user scope -- the latter needs no admin. :: winget's exit code is unreliable, being non-zero when the package is already :: installed, so after each attempt we re-resolve tesseract.exe instead of :: trusting errorlevel. :: NOTE: comments must stay OUTSIDE the parenthesised blocks below. A "::" line :: inside a ( ) block is a parse error, and any parenthesis in the comment text :: closes the block early. call :find_tesseract if not defined TESS_EXE ( echo Installing Tesseract OCR... 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. Covers clean :: Win10 machines where winget is missing or fails. :: The size check guards against a truncated download or an HTML error page; :: the installer is ~50 MB. :: NOTE on /D=: measured behaviour is that the official Tesseract installer :: self-elevates (it requests admin) and the elevated relaunch DISCARDS /D=, so :: it always lands machine-wide in "C:\Program Files\Tesseract-OCR" regardless :: of TS_DEST. /D= is kept as best-effort only. The practical consequence is :: that Tesseract needs admin/UAC -- there is no per-user install path with the :: official installer. Either outcome is fine at runtime because find_tesseract :: and src\d2r_image\ocr.py both search the machine-wide and per-user paths. :: /D= must still come last and unquoted, and breaks on paths with spaces, so :: it is only passed when the target path has none. 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 }" ) set "TS_SIZE=0" if exist "!TS_INSTALLER!" for %%A in ("!TS_INSTALLER!") do set "TS_SIZE=%%~zA" if !TS_SIZE! GEQ 20971520 ( 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