194 lines
6.5 KiB
Python
194 lines
6.5 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
import os, sys, warnings
|
||
|
|
|
||
|
|
# ========== FORCE FFMPEG PATH (NO WARNING) ==========
|
||
|
|
FFMPEG_DIR = r"C:\Users\inter\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.1-full_build\bin"
|
||
|
|
os.environ["PATH"] = FFMPEG_DIR + os.pathsep + os.environ.get("PATH", "")
|
||
|
|
os.environ["PYDUB_FFMPEG"] = os.path.join(FFMPEG_DIR, "ffmpeg.exe")
|
||
|
|
warnings.filterwarnings("ignore", category=RuntimeWarning, module="pydub.utils")
|
||
|
|
|
||
|
|
# Monkey-patch pydub's which()
|
||
|
|
from pydub.utils import which
|
||
|
|
_original_which = which
|
||
|
|
def _forced_which(prog):
|
||
|
|
if prog in ('ffmpeg', 'avconv'):
|
||
|
|
return os.environ["PYDUB_FFMPEG"]
|
||
|
|
return _original_which(prog)
|
||
|
|
import pydub.utils
|
||
|
|
pydub.utils.which = _forced_which
|
||
|
|
|
||
|
|
import pydub
|
||
|
|
pydub.AudioSegment.converter = os.environ["PYDUB_FFMPEG"]
|
||
|
|
|
||
|
|
# Other imports
|
||
|
|
import json, time, asyncio, threading
|
||
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||
|
|
from telegram import Update
|
||
|
|
from telegram.ext import Application, MessageHandler, filters, ContextTypes
|
||
|
|
import speech_recognition as sr
|
||
|
|
|
||
|
|
# ==================== CONFIG ====================
|
||
|
|
TELEGRAM_TOKEN = "REPLACE WITH YOUR BOT TOKEN"
|
||
|
|
ALLOWED_CHAT_ID = 000000000 #REPLACE WITH YOUR CHAT ID
|
||
|
|
HTTP_PORT = 8082
|
||
|
|
|
||
|
|
command_queue = []
|
||
|
|
queue_lock = threading.Lock()
|
||
|
|
result = None
|
||
|
|
result_event = threading.Event()
|
||
|
|
|
||
|
|
# ==================== HTTP SERVER ====================
|
||
|
|
class CommandHandler(BaseHTTPRequestHandler):
|
||
|
|
def log_message(self, format, *args):
|
||
|
|
pass
|
||
|
|
def do_GET(self):
|
||
|
|
if self.path == '/get_command':
|
||
|
|
self.send_response(200)
|
||
|
|
self.send_header('Content-Type', 'application/json')
|
||
|
|
self.end_headers()
|
||
|
|
with queue_lock:
|
||
|
|
if command_queue:
|
||
|
|
cmd = command_queue.pop(0)
|
||
|
|
self.wfile.write(json.dumps(cmd).encode())
|
||
|
|
print(f"Served: {cmd}")
|
||
|
|
else:
|
||
|
|
self.wfile.write(json.dumps({"action": None}).encode())
|
||
|
|
else:
|
||
|
|
self.send_response(404)
|
||
|
|
def do_POST(self):
|
||
|
|
if self.path == '/post_response':
|
||
|
|
length = int(self.headers.get('Content-Length', 0))
|
||
|
|
raw = self.rfile.read(length)
|
||
|
|
try:
|
||
|
|
data_str = raw.decode('utf-8').strip().rstrip('\x00')
|
||
|
|
global result
|
||
|
|
result = json.loads(data_str)
|
||
|
|
result_event.set()
|
||
|
|
print(f"Result: {result}")
|
||
|
|
except Exception as e:
|
||
|
|
print(f"POST error: {e}")
|
||
|
|
self.send_response(200)
|
||
|
|
self.end_headers()
|
||
|
|
else:
|
||
|
|
self.send_response(404)
|
||
|
|
|
||
|
|
def start_http_server():
|
||
|
|
server = HTTPServer(('127.0.0.1', HTTP_PORT), CommandHandler)
|
||
|
|
print(f"✅ HTTP server on {HTTP_PORT}")
|
||
|
|
server.serve_forever()
|
||
|
|
|
||
|
|
# ==================== AUDIO ====================
|
||
|
|
def transcribe_audio(wav_path):
|
||
|
|
recognizer = sr.Recognizer()
|
||
|
|
with sr.AudioFile(wav_path) as src:
|
||
|
|
recognizer.adjust_for_ambient_noise(src, 0.5)
|
||
|
|
audio = recognizer.record(src)
|
||
|
|
try:
|
||
|
|
return recognizer.recognize_google(audio).lower()
|
||
|
|
except:
|
||
|
|
return ""
|
||
|
|
|
||
|
|
def parse_command(text):
|
||
|
|
text = text.lower().strip()
|
||
|
|
if "close all" in text:
|
||
|
|
return {"action": "CLOSE_ALL"}
|
||
|
|
if "balance" in text:
|
||
|
|
return {"action": "BALANCE"}
|
||
|
|
action = None
|
||
|
|
if text.startswith("buy"):
|
||
|
|
action = "BUY"
|
||
|
|
text = text[3:].strip()
|
||
|
|
elif text.startswith("sell"):
|
||
|
|
action = "SELL"
|
||
|
|
text = text[4:].strip()
|
||
|
|
else:
|
||
|
|
return None
|
||
|
|
# ✅ DEFAULT LOT SIZE = 0.001 (micro lot, ~$117 for EURUSD)
|
||
|
|
volume = 0.001
|
||
|
|
words = text.split()
|
||
|
|
for i, w in enumerate(words):
|
||
|
|
if w == "half":
|
||
|
|
volume = 0.5
|
||
|
|
words.pop(i)
|
||
|
|
break
|
||
|
|
elif w in ["one", "a"]:
|
||
|
|
volume = 1.0
|
||
|
|
words.pop(i)
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
volume = float(w)
|
||
|
|
words.pop(i)
|
||
|
|
break
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
sym_text = " ".join(words)
|
||
|
|
m = {"gold": "XAUUSD", "euro": "EURUSD", "pound": "GBPUSD", "silver": "XAGUSD", "oil": "USOIL"}
|
||
|
|
symbol = "EURUSD"
|
||
|
|
for k, v in m.items():
|
||
|
|
if k in sym_text:
|
||
|
|
symbol = v
|
||
|
|
break
|
||
|
|
return {"action": action, "symbol": symbol, "volume": volume}
|
||
|
|
|
||
|
|
def cleanup(*files):
|
||
|
|
for f in files:
|
||
|
|
if os.path.exists(f):
|
||
|
|
try:
|
||
|
|
os.remove(f)
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def handle_voice(update, context):
|
||
|
|
if update.effective_chat.id != ALLOWED_CHAT_ID:
|
||
|
|
await update.message.reply_text("Unauthorized")
|
||
|
|
return
|
||
|
|
await update.message.reply_text("🎤 Processing...")
|
||
|
|
voice = await update.message.voice.get_file()
|
||
|
|
ogg, wav = "temp.ogg", "temp.wav"
|
||
|
|
try:
|
||
|
|
await voice.download_to_drive(ogg)
|
||
|
|
audio = pydub.AudioSegment.from_ogg(ogg)
|
||
|
|
audio = audio.set_frame_rate(16000).set_channels(1)
|
||
|
|
audio.export(wav, format="wav")
|
||
|
|
text = transcribe_audio(wav)
|
||
|
|
if not text:
|
||
|
|
await update.message.reply_text("❌ Could not understand")
|
||
|
|
return
|
||
|
|
await update.message.reply_text(f"📝 Heard: {text}")
|
||
|
|
cmd = parse_command(text)
|
||
|
|
if not cmd:
|
||
|
|
await update.message.reply_text("❌ Invalid command. Example: 'buy 0.001 euro'")
|
||
|
|
return
|
||
|
|
with queue_lock:
|
||
|
|
command_queue.append(cmd)
|
||
|
|
await update.message.reply_text("⏳ Sending to MT5...")
|
||
|
|
global result
|
||
|
|
result = None
|
||
|
|
result_event.clear()
|
||
|
|
if result_event.wait(timeout=10):
|
||
|
|
if result and result.get("status") == "success":
|
||
|
|
msg = f"✅ Success!\nTicket: {result['ticket']}\nPrice: {result['price']}"
|
||
|
|
else:
|
||
|
|
msg = f"❌ Error: {result.get('message') if result else 'Unknown'}"
|
||
|
|
else:
|
||
|
|
msg = "⏰ No response from MT5. Is EA running?"
|
||
|
|
await update.message.reply_text(msg)
|
||
|
|
except Exception as e:
|
||
|
|
await update.message.reply_text(f"❌ {str(e)[:100]}")
|
||
|
|
finally:
|
||
|
|
cleanup(ogg, wav)
|
||
|
|
|
||
|
|
async def error_handler(update, context):
|
||
|
|
print(f"Telegram error: {context.error}")
|
||
|
|
|
||
|
|
def main():
|
||
|
|
threading.Thread(target=start_http_server, daemon=True).start()
|
||
|
|
app = Application.builder().token(TELEGRAM_TOKEN).build()
|
||
|
|
app.add_handler(MessageHandler(filters.VOICE, handle_voice))
|
||
|
|
app.add_error_handler(error_handler)
|
||
|
|
print("✅ Telegram bot polling...")
|
||
|
|
app.run_polling()
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|