diff --git a/Dockerfile b/Dockerfile index fa26ed90..4ba85085 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:lts-bookworm-slim RUN apt-get update && \ apt-get upgrade -y && \ - apt-get install -y --no-install-recommends ffmpeg && \ + apt-get install -y --no-install-recommends ffmpeg stockfish && \ rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/chess-engine/stockfish.js b/chess-engine/stockfish.js new file mode 100644 index 00000000..414ece8d --- /dev/null +++ b/chess-engine/stockfish.js @@ -0,0 +1,69 @@ +import { spawn, execSync } from 'child_process'; + +export const DIFFICULTY_MAP = { + 'beginner': 0, 'easy': 2, 'middle': 5, 'hard': 8, + 'advanced': 10, 'expert': 12, 'candidate': 14, + 'master': 16, 'grandmaster': 18, 'champion': 20 +}; + +const STOCKFISH_PATHS = [ + 'stockfish', + '/usr/bin/stockfish', + '/usr/games/stockfish', + '/usr/local/bin/stockfish', + '/data/data/com.termux/files/usr/bin/stockfish', + '/opt/homebrew/bin/stockfish', +]; + +function findStockfish() { + for (const path of STOCKFISH_PATHS) { + try { + execSync(`"${path}" uci`, { timeout: 2000, stdio: 'pipe' }); + return path; + } catch (_) {} + } + return null; +} + +const STOCKFISH_BIN = findStockfish(); + +export function getStockfishMove(fen, skillLevel = 0) { + return new Promise((resolve, reject) => { + if (!STOCKFISH_BIN) return reject(new Error('Stockfish tidak ditemukan')); + const engine = spawn(STOCKFISH_BIN); + let resolved = false; + const timeout = setTimeout(() => { + if (!resolved) { resolved = true; engine.kill(); reject(new Error('stockfish timeout')); } + }, 8000); + let buffer = ''; + engine.stdout.on('data', (data) => { + buffer += data.toString(); + const lines = buffer.split('\n'); + buffer = lines.pop(); + for (const line of lines) { + if (line.startsWith('bestmove') && !resolved) { + resolved = true; + clearTimeout(timeout); + engine.kill(); + const move = line.trim().split(' ')[1]; + resolve(move && move !== '(none)' ? move : null); + } + } + }); + engine.stderr.on('data', (data) => { + if (!resolved) { + resolved = true; + clearTimeout(timeout); + engine.kill(); + reject(new Error(data.toString().trim())); + } + }); + engine.on('error', (err) => { if (!resolved) { resolved = true; clearTimeout(timeout); reject(err); } }); + const moveTime = 50 + skillLevel * 30; + engine.stdin.write('uci\n'); + engine.stdin.write(`setoption name Skill Level value ${skillLevel}\n`); + engine.stdin.write('ucinewgame\n'); + engine.stdin.write(`position fen ${fen}\n`); + engine.stdin.write(`go movetime ${moveTime}\n`); + }); +} diff --git a/gitdiff.sh b/gitdiff.sh new file mode 100644 index 00000000..63b14256 --- /dev/null +++ b/gitdiff.sh @@ -0,0 +1,543 @@ +#!/bin/bash + +show_help() { + cat << 'SlP' +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +CARA PAKAI: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + bash gitdiff.sh [OPSI] [ARGUMEN GIT DIFF] + bash gitdiff.sh [COMMIT] + bash gitdiff.sh [COMMIT1] [COMMIT2] + bash gitdiff.sh [COMMIT1] [COMMIT2] -- [FILE...] + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +OPSI +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + -h, --help Lihat bantuan ini + -o Output ke file — .html atau .md + Contoh: -o diff.html, -o diff.md + -U Jumlah baris context (default: 2) + Contoh: -U0 (tanpa context), -U5 (5 baris) + --staged Bandingkan staging vs commit terakhir + --cached Sama kayak --staged + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +REFERENSI COMMIT +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Hash penuh : 9beb418f9e6849... + Hash pendek : 9beb418 + HEAD : commit terakhir + HEAD~1 : 1 commit sebelum HEAD + HEAD~N : N commit sebelum HEAD + HEAD^ : parent dari HEAD (sama kayak HEAD~1) + HEAD^^ : parent dari parent (sama kayak HEAD~2) + branch_name : ujung dari branch tersebut + tag_name : commit yang ditandai + origin/main : ujung dari remote branch + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +CONTOH PEMAKAIAN +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + # Working tree vs staging (belum di-add) + bash gitdiff.sh + + # Staging vs commit terakhir (udah di-add, belum commit) + bash gitdiff.sh --staged + bash gitdiff.sh --cached + + # Working tree vs commit terakhir (semua perubahan) + bash gitdiff.sh HEAD + + # Dua commit tertentu + bash gitdiff.sh 7ce6e13 a9d104b + + # Commit terakhir vs sebelumnya + bash gitdiff.sh HEAD~1 HEAD + + # 3 commit ke belakang vs sekarang + bash gitdiff.sh HEAD~3 HEAD + + # Antar branch + bash gitdiff.sh main fix/bug-pairing + bash gitdiff.sh develop staging + + # Branch lokal vs remote + bash gitdiff.sh main origin/main + + # Antar tag + bash gitdiff.sh v1.0.0 v2.0.0 + + # File tertentu aja + bash gitdiff.sh 7ce6e13 a9d104b -- utils/helpers.js + + # Beberapa file sekaligus + bash gitdiff.sh 20bbef5 15a83fa -- middleware/auth.js services/notification.js + + # Semua file dengan ekstensi tertentu + bash gitdiff.sh 20bbef5 15a83fa -- '*.js' + bash gitdiff.sh 20bbef5 15a83fa -- '*.json' + + # Folder tertentu aja + bash gitdiff.sh 8a4419e cdcd588 -- lib/ + bash gitdiff.sh 8a4419e cdcd588 -- src/ + + # Context lebih banyak + bash gitdiff.sh -U5 20bbef5 15a83fa + + # Tanpa context sama sekali + bash gitdiff.sh -U0 20bbef5 15a83fa + + # Output ke file HTML + bash gitdiff.sh HEAD~1 HEAD -o diff.html + + # Output ke file Markdown + bash gitdiff.sh HEAD~1 HEAD -o diff.md + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +FORMAT OUTPUT +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ======================================== + FILE : lib/exif.js + ======================================== + + 4|4 import Crypto from 'crypto'; ← context (nggak berubah) + - 5 import FileType from 'file-type' ← dihapus (nomor file lama) + + 5 import { fileTypeFrom... } ← ditambah (nomor file baru) + 6|6 import { fileURLToPath } ← context + + ---------------------------------------- ← pemisah antar hunk + + Nomor baris: + lama|baru → baris nggak berubah, nomor di file lama | file baru + lama → baris dihapus, cuma ada di file lama + baru → baris ditambah, cuma ada di file baru + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +WARNA +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + merah (-) baris dihapus + hijau (+) baris ditambah + cyan info commit & ringkasan + redup nomor baris + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +TIPS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + # Kalau panjang — pipe ke less + bash gitdiff.sh 8a4419e cdcd588 | less -R + + # Simpan ke file + bash gitdiff.sh 8a4419e cdcd588 > hasil.txt + + # Jadiin alias permanen — tambahin ke ~/.bashrc atau ~/.zshrc + alias gd='bash /path/ke/gitdiff.sh' + + # Cek hash commit yang ada + git log --oneline + +SlP +} + +# ── Parse args ────────────────────────────────────────────── +OUTPUT="" +DIFF_ARGS=() + +for arg in "$@"; do + case "$arg" in + -h|--help) show_help; exit 0 ;; + esac +done + +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + OUTPUT="$2" + shift 2 + ;; + -o*) + OUTPUT="${1#-o}" + shift + ;; + *) + DIFF_ARGS+=("$1") + shift + ;; + esac +done + +COMMIT1="${DIFF_ARGS[0]}" +COMMIT2="${DIFF_ARGS[1]}" + +print_commit_info() { + local ref="$1" + local label="$2" + local hash msg author date + hash=$(git rev-parse --short "$ref" 2>/dev/null) || return + msg=$(git log -1 --format="%s" "$ref" 2>/dev/null) + author=$(git log -1 --format="%an" "$ref" 2>/dev/null) + date=$(git log -1 --format="%ci" "$ref" 2>/dev/null) + echo "$label $hash — $msg ($author, $date)" +} + +INFO1="$(print_commit_info "$COMMIT1" "FROM")" +INFO2="$(print_commit_info "$COMMIT2" " TO")" + +# ── MODE: TERMINAL ─────────────────────────────────────────── +if [[ -z "$OUTPUT" ]]; then + git -c color.ui=never --no-pager diff -U2 "${DIFF_ARGS[@]}" | awk \ + -v info1="$INFO1" \ + -v info2="$INFO2" \ + ' + BEGIN { + eq = "========================================" + hr = "----------------------------------------" + RED = "\033[31m" + GRN = "\033[32m" + RST = "\033[m" + DIM = "\033[2m" + CYN = "\033[36m" + BLD = "\033[1m" + + if (info1 != "") print CYN BLD info1 RST + if (info2 != "") print CYN BLD info2 RST + if (info1 != "" || info2 != "") print "" + + total_add = 0 + total_del = 0 + file_count = 0 + } + + { + if ($0 ~ /^diff --git /) { + if (in_file) print "" + match($0, / b\/(.+)$/, m) + print eq + print "FILE : " m[1] + print eq + in_file = 1 + in_hunk = 0 + first_hunk = 1 + file_count++ + next + } + + if ($0 ~ /^(index |--- |\+\+\+ )/) next + if ($0 ~ /^\\ No newline/) next + + if ($0 ~ /^@@ /) { + if (!first_hunk) { + print "" + print hr + } + print "" + first_hunk = 0 + in_hunk = 1 + match($0, /^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@/, n) + old_ln = n[1] + new_ln = n[3] + next + } + + if (in_hunk) { + if ($0 ~ /^-/) { + print RED "-" DIM sprintf("%4d ", old_ln) RST " " substr($0, 2) RST + old_ln++ + total_del++ + } else if ($0 ~ /^\+/) { + print GRN "+" DIM sprintf("%4d ", new_ln) RST " " substr($0, 2) RST + new_ln++ + total_add++ + } else { + print " " DIM sprintf("%4d|%-4d", old_ln, new_ln) RST " " substr($0, 2) + old_ln++ + new_ln++ + } + } + } + + END { + if (in_file) print "" + print "" + print eq + printf "%s%s%d file%s changed%s",BLD,CYN, file_count, (file_count>1?"s":""), RST + if (total_add > 0) printf " " GRN "+" total_add RST + if (total_del > 0) printf " " RED "-" total_del RST + print "" + print eq + } + ' + exit 0 +fi + +# ── MODE: MARKDOWN ─────────────────────────────────────────── +if [[ "$OUTPUT" == *.md ]]; then + git -c color.ui=never --no-pager diff -U2 "${DIFF_ARGS[@]}" | gawk \ + -v info1="$INFO1" \ + -v info2="$INFO2" \ + -v outfile="$OUTPUT" \ + ' + BEGIN { + total_add = 0 + total_del = 0 + file_count = 0 + out = "" + cur_file = "" + cur_block = "" + cur_add = 0 + cur_del = 0 + in_hunk = 0 + first_hunk = 1 + } + + function flush_file() { + if (cur_file == "") return + if (!first_hunk) cur_block = cur_block "```\n" + stat = "" + if (cur_add > 0) stat = stat " +" cur_add + if (cur_del > 0) stat = stat " -" cur_del + out = out "### `" cur_file "`" stat "\n\n" + out = out cur_block "\n" + cur_file = "" + cur_block = "" + cur_add = 0 + cur_del = 0 + first_hunk = 1 + } + + { + if ($0 ~ /^diff --git /) { + flush_file() + match($0, / b\/(.+)$/, m) + cur_file = m[1] + in_hunk = 0 + first_hunk = 1 + file_count++ + next + } + + if ($0 ~ /^(index |--- |\+\+\+ )/) next + if ($0 ~ /^\\ No newline/) next + + if ($0 ~ /^@@ /) { + if (first_hunk) { + cur_block = cur_block "```diff\n" + } else { + cur_block = cur_block "...\n" + } + first_hunk = 0 + in_hunk = 1 + match($0, /^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@/, n) + old_ln = n[1] + new_ln = n[3] + next + } + + if (in_hunk) { + if ($0 ~ /^-/) { + cur_block = cur_block sprintf("-%4d %s\n", old_ln, substr($0, 2)) + old_ln++; total_del++; cur_del++ + } else if ($0 ~ /^\+/) { + cur_block = cur_block sprintf("+%4d %s\n", new_ln, substr($0, 2)) + new_ln++; total_add++; cur_add++ + } else { + cur_block = cur_block sprintf(" %4d|%-4d %s\n", old_ln, new_ln, substr($0, 2)) + old_ln++; new_ln++ + } + } + } + + END { + flush_file() + + summary = file_count " file" (file_count > 1 ? "s" : "") " changed" + if (total_add > 0) summary = summary ", +" total_add + if (total_del > 0) summary = summary ", -" total_del + + header = "# git diff\n\n" + if (info1 != "") header = header "> " info1 "\n" + if (info2 != "") header = header "> " info2 "\n" + if (info1 != "" || info2 != "") header = header "\n" + header = header "**" summary "**\n\n---\n\n" + + print header out > outfile + print "✅ Markdown saved to: " outfile > "/dev/stderr" + } + ' + exit 0 +fi + +# ── MODE: HTML ─────────────────────────────────────────────── +git -c color.ui=never --no-pager diff -U2 "${DIFF_ARGS[@]}" | gawk \ + -v info1="$INFO1" \ + -v info2="$INFO2" \ + -v outfile="$OUTPUT" \ +' +function esc(s, r) { + r = s + gsub(/&/, "\\&", r) + gsub(//, "\\>", r) + return r +} + +BEGIN { + total_add = 0 + total_del = 0 + file_count = 0 + files_html = "" + cur_file = "" + cur_rows = "" + cur_add = 0 + cur_del = 0 + + split("Dockerfile:🐳 .sh:🐧 .bat:🪟 .js:🟨 .ts:🔷 .json:📦 .nix:❄️ .md:📝 .yml:⚙️ .yaml:⚙️ .env:🔒 .py:🐍 .go:🐹", icons_raw, " ") + for (i in icons_raw) { + cnt = split(icons_raw[i], kv, ":") + icons[kv[1]] = kv[2] + } +} + +function get_icon(fname, ext, base) { + base = fname + sub(/.*\//, "", base) + if (base in icons) return icons[base] + if (match(fname, /\.[^.]+$/)) { + ext = substr(fname, RSTART) + if (ext in icons) return icons[ext] + } + return "📄" +} + +function flush_file() { + if (cur_file == "") return + icon = get_icon(cur_file) + stat = "" + if (cur_add > 0) stat = stat "+" cur_add "" + if (cur_del > 0) stat = stat "−" cur_del "" + files_html = files_html \ + "
" \ + "
" \ + "" icon "" \ + "" esc(cur_file) "" \ + "
" stat "
" \ + "
" \ + "" cur_rows "
" \ + "
" + cur_file = "" + cur_rows = "" + cur_add = 0 + cur_del = 0 +} + +function row(cls, sign, lo, ln, code) { + cur_rows = cur_rows \ + "" \ + "" sign "" \ + "" lo "" \ + "" ln "" \ + "" esc(code) "" \ + "" +} + +function hunk_sep() { + cur_rows = cur_rows "· · ·" +} + +{ + if ($0 ~ /^diff --git /) { + flush_file() + match($0, / b\/(.+)$/, m) + cur_file = m[1] + in_hunk = 0 + first_hunk = 1 + file_count++ + next + } + + if ($0 ~ /^(index |--- |\+\+\+ )/) next + if ($0 ~ /^\\ No newline/) next + + if ($0 ~ /^@@ /) { + if (!first_hunk) hunk_sep() + first_hunk = 0 + in_hunk = 1 + match($0, /^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@/, n) + old_ln = n[1] + new_ln = n[3] + next + } + + if (in_hunk) { + if ($0 ~ /^-/) { + row("del", "−", old_ln, "", substr($0, 2)) + old_ln++; total_del++; cur_del++ + } else if ($0 ~ /^\+/) { + row("add", "+", "", new_ln, substr($0, 2)) + new_ln++; total_add++; cur_add++ + } else { + row("ctx", "", old_ln, new_ln, substr($0, 2)) + old_ln++; new_ln++ + } + } +} + +END { + flush_file() + + summary = file_count " file" (file_count > 1 ? "s" : "") " changed" + from_html = (info1 != "") ? "
" esc(info1) "
" : "" + to_html = (info2 != "") ? "
" esc(info2) "
" : "" + commit_block = (from_html != "" || to_html != "") ? \ + "
" from_html to_html "
" : "" + + badge_files = "" summary "" + badge_add = (total_add > 0) ? "+" total_add "" : "" + badge_del = (total_del > 0) ? "−" total_del "" : "" + + html = "\n" \ +"\n\n" \ +"\n" \ +"\n" \ +"git diff\n" \ +"\n\n\n" \ +"
git diff
" commit_block "
\n" \ +"
" badge_files badge_add badge_del "
\n" \ +"
" files_html "
\n" \ +"\n" + + print html > outfile + print "✅ HTML saved to: " outfile > "/dev/stderr" +} +' diff --git a/install.bat b/install.bat index 9511f57a..4925f060 100644 --- a/install.bat +++ b/install.bat @@ -35,6 +35,11 @@ winget install -e --id ImageMagick.ImageMagick --accept-source-agreements --acce echo %CYAN%[INFO] Midnight Commander (mc) is skipped as it is natively for Unix-like systems.%NC% +echo %YELLOW%[WARNING] Stockfish is not available via winget. Please download and install manually:%NC% +echo %YELLOW% https://stockfishchess.org/download/%NC% +echo %YELLOW% Then add stockfish.exe to your PATH.%NC% +echo. + echo %CYAN%[INFO] Installing PM2 and Yarn globally...%NC% call npm install -g pm2 yarn @@ -53,4 +58,4 @@ echo or terminal window so the system can register the new environment variables echo ========================================================== echo %PURPLE%[START] Starting application...%NC% call npm start -pause \ No newline at end of file +pause \ No newline at end of file diff --git a/install.sh b/install.sh index 8cc0c47b..be7449a5 100644 --- a/install.sh +++ b/install.sh @@ -50,22 +50,24 @@ main() { case "$PKG" in pkg) # Termux pkg update -y - pkg install -y git nodejs ffmpeg mc yarn imagemagick + pkg install -y git nodejs ffmpeg mc yarn imagemagick stockfish ;; apt|apt-get) # Ubuntu/Debian run_as_root $PKG update -y - run_as_root $PKG install -y git nodejs npm ffmpeg mc yarn imagemagick + run_as_root $PKG install -y git nodejs npm ffmpeg mc yarn imagemagick stockfish ;; pacman) # Arch Linux run_as_root pacman -Syu --noconfirm - run_as_root pacman -S --noconfirm git nodejs npm ffmpeg mc yarn imagemagick + run_as_root pacman -S --noconfirm git nodejs npm ffmpeg mc yarn imagemagick stockfish ;; dnf) # Fedora/AlmaLinux - run_as_root dnf install -y git nodejs npm ffmpeg mc yarn ImageMagick + run_as_root dnf install -y git nodejs npm ffmpeg mc yarn ImageMagick stockfish ;; yum) # CentOS/RHEL run_as_root yum install -y epel-release run_as_root yum install -y git nodejs npm ffmpeg mc ImageMagick + # NOTE: stockfish tidak tersedia di repo CentOS/RHEL + # Install manual: https://stockfishchess.org/download/ ;; esac diff --git a/naze.js b/naze.js index c00a28a3..7af73a07 100644 --- a/naze.js +++ b/naze.js @@ -1,4 +1,5 @@ import './settings.js'; +import { DIFFICULTY_MAP, getStockfishMove } from './chess-engine/stockfish.js'; import fs from 'fs'; import os from 'os'; import util from 'util'; @@ -548,7 +549,8 @@ const naze = async (naze, m, msg, store) => { turn: savedData.turn, botMode: savedData.botMode, time: savedData.time, - _fen: savedData._fen + _fen: savedData._fen, + difficulty: savedData.difficulty ?? 0 }); } if (chess[m.sender].isCheckmate() || chess[m.sender].isDraw() || chess[m.sender].isGameOver()) { @@ -572,15 +574,41 @@ const naze = async (naze, m, msg, store) => { delete chess[m.sender]; return m.reply(`♟Permainan Selesai\nPemenang: @${m.sender.split('@')[0]}`); } - const moves = chess[m.sender].moves({ verbose: true }); - const botMove = moves[Math.floor(Math.random() * moves.length)]; - chess[m.sender].move(botMove); + const skillLevel = chess[m.sender].difficulty ?? 0; + let botMove; + try { + const sfMoveStr = await getStockfishMove(chess[m.sender].fen(), skillLevel); + if (sfMoveStr) { + const from = sfMoveStr.slice(0, 2); + const to = sfMoveStr.slice(2, 4); + const promotion = sfMoveStr[4] || 'q'; + botMove = chess[m.sender].move({ from, to, promotion }); + } + } catch (_) { + const moves = chess[m.sender].moves({ verbose: true }); + const randomMove = moves[Math.floor(Math.random() * moves.length)]; + botMove = chess[m.sender].move(randomMove); + await m.reply(`⚠️ *Stockfish engine gagal*, bot gerak secara random.\nPastikan Stockfish sudah terinstall di server.`); + } + if (!botMove) { + const moves = chess[m.sender].moves({ verbose: true }); + botMove = chess[m.sender].move(moves[Math.floor(Math.random() * moves.length)]); + } chess[m.sender]._fen = chess[m.sender].fen(); chess[m.sender].time = Date.now(); if (chess[m.sender].isGameOver()) { + const encodedFen = encodeURI(chess[m.sender]._fen); + const boardUrls = [`https://www.chess.com/dynboard?fen=${encodedFen}&size=3&coordinates=inside`,`https://www.chess.com/dynboard?fen=${encodedFen}&board=graffiti&piece=graffiti&size=3&coordinates=inside`,`https://chessboardimage.com/${encodedFen}.png`,`https://backscattering.de/web-boardimage/board.png?fen=${encodedFen}&coordinates=true&size=765`,`https://fen2image.chessvision.ai/${encodedFen}/`]; + for (let url of boardUrls) { + try { + const { data } = await axios.get(url, { responseType: 'arraybuffer' }); + await m.reply({ image: data, caption: `♟️CHESS GAME (vs BOT)\n\nLangkahmu: ${from} → ${to}\nLangkah bot: ${botMove.from} → ${botMove.to}\n\n♟Permainan Selesai\nPemenang: BOT`, mentions: [m.sender] }); + break; + } catch (e) {} + } delete chess[m.sender]; - return m.reply(`♟Permainan Selesai\nPemenang: BOT`); + return; } const encodedFen = encodeURI(chess[m.sender]._fen); const boardUrls = [`https://www.chess.com/dynboard?fen=${encodedFen}&size=3&coordinates=inside`,`https://www.chess.com/dynboard?fen=${encodedFen}&board=graffiti&piece=graffiti&size=3&coordinates=inside`,`https://chessboardimage.com/${encodedFen}.png`,`https://backscattering.de/web-boardimage/board.png?fen=${encodedFen}&coordinates=true&size=765`,`https://fen2image.chessvision.ai/${encodedFen}/`]; @@ -3984,11 +4012,16 @@ Select Bot Settings: delete chess[m.sender]; return m.reply('Sukses Menghapus Sesi vs BOT') } else { + const difficulty = args[1]?.toLowerCase(); + if (!difficulty || !(difficulty in DIFFICULTY_MAP)) { + return m.reply(`♿️Pilih Level Kesulitan:\n\n${Object.keys(DIFFICULTY_MAP).join(' | ')}\n\nContoh: ${prefix + command} bot easy\nContoh: ${prefix + command} computer hard`); + } const { DEFAUT_POSITION } = await import('chess.js').then(m => m.Chess); chess[m.sender] = new Chess(DEFAUT_POSITION); chess[m.sender]._fen = chess[m.sender].fen(); chess[m.sender].turn = m.sender; chess[m.sender].botMode = true; + chess[m.sender].difficulty = DIFFICULTY_MAP[difficulty]; chess[m.sender].time = Date.now(); const encodedFen = encodeURI(chess[m.sender]._fen); const boardUrls = [`https://www.chess.com/dynboard?fen=${encodedFen}&size=3&coordinates=inside`,`https://www.chess.com/dynboard?fen=${encodedFen}&board=graffiti&piece=graffiti&size=3&coordinates=inside`,`https://chessboardimage.com/${encodedFen}.png`,`https://backscattering.de/web-boardimage/board.png?fen=${encodedFen}&coordinates=true&size=765`,`https://fen2image.chessvision.ai/${encodedFen}/`]; @@ -4985,4 +5018,4 @@ Select Bot Settings: } } -export default naze; \ No newline at end of file +export default naze; \ No newline at end of file diff --git a/replit.nix b/replit.nix index ce7c67a0..e5fc2550 100644 --- a/replit.nix +++ b/replit.nix @@ -1,18 +1,19 @@ -{ pkgs }: { - deps = [ - pkgs.nodejs_22 - pkgs.nodePackages.typescript - pkgs.ffmpeg - pkgs.git - pkgs.neofetch - pkgs.speedtest-cli - pkgs.wget - pkgs.yarn - pkgs.libuuid - ]; - env = { - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ - pkgs.libuuid - ]; - }; -} \ No newline at end of file +{ pkgs }: { + deps = [ + pkgs.nodejs_22 + pkgs.nodePackages.typescript + pkgs.ffmpeg + pkgs.git + pkgs.neofetch + pkgs.speedtest-cli + pkgs.wget + pkgs.yarn + pkgs.stockfish + pkgs.libuuid + ]; + env = { + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ + pkgs.libuuid + ]; + }; +}