Add Threading and Lock

This commit is contained in:
amirhossein 2026-01-07 08:03:24 -08:00
parent a8749e3239
commit 1bf944a0cf

400
main.py
View File

@ -3,9 +3,9 @@ import time
import json import json
import urllib.parse import urllib.parse
import traceback import traceback
import subprocess # <--- اضافه شد import subprocess
import sys # <--- اضافه شد import sys
import threading # <--- اضافه شد import threading
from flask import Flask, request, jsonify, send_file from flask import Flask, request, jsonify, send_file
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
@ -15,6 +15,10 @@ CHROMIUM_PATH = "/usr/bin/chromium-browser"
if not os.path.exists(CHROMIUM_PATH): if not os.path.exists(CHROMIUM_PATH):
CHROMIUM_PATH = "/usr/bin/chromium" CHROMIUM_PATH = "/usr/bin/chromium"
# --- قفل برای صف‌بندی درخواست‌های سنگین ---
# این قفل باعث می‌شود لایک/فالو/کامنت یکی‌یکی انجام شوند
TASK_LOCK = threading.Lock()
# مرورگر اصلی (فقط یک بار اجرا می‌شود) # مرورگر اصلی (فقط یک بار اجرا می‌شود)
playwright_instance = None playwright_instance = None
browser = None browser = None
@ -130,228 +134,237 @@ def inject_cookies(context, cookies_data):
# 🚀 مسیرها (Routes) # 🚀 مسیرها (Routes)
# ======================================================= # =======================================================
@app.route('/')
def health_check():
"""این مسیر همیشه سریع جواب می‌دهد، حتی اگر ربات مشغول باشد"""
return "OK - Server is Alive", 200
@app.route('/auto_like', methods=['POST']) @app.route('/auto_like', methods=['POST'])
def auto_like(): def auto_like():
context = None # استفاده از قفل: اگر کسی داخل باشد، بقیه منتظر می‌مانند (صف)
page = None with TASK_LOCK:
try: context = None
# 1. دریافت داده‌ها page = None
data = request.json
proxy_str = data.get('proxy') # <--- گرفتن پروکسی از ورودی
cookies = data.get('cookies')
post_url = data.get('post_url')
print(f"Request received for LIKE using proxy: {proxy_str}")
# 2. ساخت کانتکست اختصاصی
context = create_context_with_proxy(proxy_str)
page = context.new_page()
# 3. تزریق کوکی و رفتن به صفحه
inject_cookies(context, cookies)
try: try:
page.goto(post_url, wait_until="networkidle", timeout=60000) # 1. دریافت داده‌ها
except: pass data = request.json
proxy_str = data.get('proxy')
# 4. لاگیک لایک (همان کد هوشمند قبلی) cookies = data.get('cookies')
status = "Failed" post_url = data.get('post_url')
target_btn = None
# تشخیص وضعیت
for _ in range(20):
if page.locator('svg[aria-label="Unlike"]').count() > 0 or page.locator('svg[aria-label="نپسندیدن"]').count() > 0:
status = "Already Liked"
break
likes = page.locator('svg[aria-label="Like"]') print(f"Request received for LIKE using proxy: {proxy_str}")
if likes.count() == 0: likes = page.locator('svg[aria-label="پسندیدن"]')
if likes.count() > 0 and likes.first.is_visible():
target_btn = likes.first
break
page.wait_for_timeout(1000)
if status == "Already Liked": # 2. ساخت کانتکست اختصاصی
pass context = create_context_with_proxy(proxy_str)
elif target_btn: page = context.new_page()
target_btn.click(force=True)
page.wait_for_timeout(2000) # 3. تزریق کوکی و رفتن به صفحه
if page.locator('svg[aria-label="Unlike"]').count() > 0: inject_cookies(context, cookies)
status = "Success: Liked [Verified]"
try:
page.goto(post_url, wait_until="networkidle", timeout=60000)
except: pass
# 4. لاگیک لایک
status = "Failed"
target_btn = None
# تشخیص وضعیت
for _ in range(20):
if page.locator('svg[aria-label="Unlike"]').count() > 0 or page.locator('svg[aria-label="نپسندیدن"]').count() > 0:
status = "Already Liked"
break
likes = page.locator('svg[aria-label="Like"]')
if likes.count() == 0: likes = page.locator('svg[aria-label="پسندیدن"]')
if likes.count() > 0 and likes.first.is_visible():
target_btn = likes.first
break
page.wait_for_timeout(1000)
if status == "Already Liked":
pass
elif target_btn:
target_btn.click(force=True)
page.wait_for_timeout(2000)
if page.locator('svg[aria-label="Unlike"]').count() > 0:
status = "Success: Liked [Verified]"
else:
status = "Success: Clicked (No Verify)"
else: else:
status = "Success: Clicked (No Verify)" page.mouse.dblclick(page.viewport_size['width']/2, page.viewport_size['height']/2)
else: status = "Success: DoubleTap"
page.mouse.dblclick(page.viewport_size['width']/2, page.viewport_size['height']/2)
status = "Success: DoubleTap"
page.screenshot(path="current_view.png") page.screenshot(path="current_view.png")
return jsonify({"status": status, "proxy_used": proxy_str}) return jsonify({"status": status, "proxy_used": proxy_str})
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
finally: finally:
# بسیار مهم: بستن کانتکست برای آزاد کردن رم و آی‌پی if context: context.close()
if context: context.close()
@app.route('/auto_comment', methods=['POST']) @app.route('/auto_comment', methods=['POST'])
def auto_comment(): def auto_comment():
context = None with TASK_LOCK:
page = None context = None
try: page = None
data = request.json
proxy_str = data.get('proxy')
cookies = data.get('cookies')
post_url = data.get('post_url')
comment_text = data.get('comment_text')
print(f"Request received for COMMENT using proxy: {proxy_str}")
context = create_context_with_proxy(proxy_str)
page = context.new_page()
inject_cookies(context, cookies)
try: try:
page.goto(post_url, wait_until="networkidle", timeout=60000) data = request.json
except: pass proxy_str = data.get('proxy')
cookies = data.get('cookies')
post_url = data.get('post_url')
comment_text = data.get('comment_text')
if page.locator("input[name='username']").count() > 0: print(f"Request received for COMMENT using proxy: {proxy_str}")
return jsonify({"status": "Failed", "reason": "Login Redirect"}), 401
# لاگیک کامنت (همان کد نهایی و وریفای شده) context = create_context_with_proxy(proxy_str)
bubbles = [page.locator(s) for s in ['svg[aria-label="Comment"]', 'svg[aria-label="محاوره"]', 'svg[class*="_ab6-"]']] page = context.new_page()
bubbles.append(page.get_by_role("button").filter(has_text="Comment")) inject_cookies(context, cookies)
for b in bubbles:
if b.count() > 0 and b.first.is_visible():
b.first.click(force=True)
break
input_ready = False
for _ in range(30):
placeholders = page.get_by_text("Add a comment...", exact=False)
if placeholders.count() > 0 and placeholders.last.is_visible():
placeholders.last.click(force=True)
input_ready = True
break
forms = page.locator("form")
if forms.count() > 0 and forms.last.is_visible():
forms.last.click(force=True)
input_ready = True
break
page.wait_for_timeout(1000)
status = "Failed"
if input_ready:
page.wait_for_timeout(1000)
candidates = [
page.locator('div[contenteditable="true"][role="textbox"]').last,
page.locator('form textarea').last
]
final_box = next((c for c in candidates if c.is_visible()), None)
if final_box: try:
final_box.click() page.goto(post_url, wait_until="networkidle", timeout=60000)
final_box.type(comment_text, delay=100) except: pass
page.wait_for_timeout(500)
post_btn = page.locator('div[role="button"]').filter(has_text="Post").last
if not post_btn.is_visible(): post_btn = page.locator('div[role="button"]').filter(has_text="ارسال").last
if post_btn.is_visible():
post_btn.click()
status = "Success: Posted [Verified]"
else:
page.keyboard.press("Enter")
status = "Success: Enter [Verified]"
page.screenshot(path="current_view.png") if page.locator("input[name='username']").count() > 0:
return jsonify({"status": status, "proxy_used": proxy_str}) return jsonify({"status": "Failed", "reason": "Login Redirect"}), 401
except Exception as e: # لاگیک کامنت
traceback.print_exc() bubbles = [page.locator(s) for s in ['svg[aria-label="Comment"]', 'svg[aria-label="محاوره"]', 'svg[class*="_ab6-"]']]
return jsonify({"error": str(e)}), 500 bubbles.append(page.get_by_role("button").filter(has_text="Comment"))
finally:
if context: context.close() for b in bubbles:
if b.count() > 0 and b.first.is_visible():
b.first.click(force=True)
break
input_ready = False
for _ in range(30):
placeholders = page.get_by_text("Add a comment...", exact=False)
if placeholders.count() > 0 and placeholders.last.is_visible():
placeholders.last.click(force=True)
input_ready = True
break
forms = page.locator("form")
if forms.count() > 0 and forms.last.is_visible():
forms.last.click(force=True)
input_ready = True
break
page.wait_for_timeout(1000)
status = "Failed"
if input_ready:
page.wait_for_timeout(1000)
candidates = [
page.locator('div[contenteditable="true"][role="textbox"]').last,
page.locator('form textarea').last
]
final_box = next((c for c in candidates if c.is_visible()), None)
if final_box:
final_box.click()
final_box.type(comment_text, delay=100)
page.wait_for_timeout(500)
post_btn = page.locator('div[role="button"]').filter(has_text="Post").last
if not post_btn.is_visible(): post_btn = page.locator('div[role="button"]').filter(has_text="ارسال").last
if post_btn.is_visible():
post_btn.click()
status = "Success: Posted [Verified]"
else:
page.keyboard.press("Enter")
status = "Success: Enter [Verified]"
page.screenshot(path="current_view.png")
return jsonify({"status": status, "proxy_used": proxy_str})
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
finally:
if context: context.close()
@app.route('/auto_follow', methods=['POST']) @app.route('/auto_follow', methods=['POST'])
def auto_follow(): def auto_follow():
context = None with TASK_LOCK:
page = None context = None
try: page = None
data = request.json
proxy_str = data.get('proxy')
cookies = data.get('cookies')
target_user = data.get('target_username')
print(f"Request received for FOLLOW using proxy: {proxy_str}")
context = create_context_with_proxy(proxy_str)
page = context.new_page()
inject_cookies(context, cookies)
try: try:
page.goto(f"https://www.instagram.com/{target_user}/", wait_until="networkidle", timeout=60000) data = request.json
except: pass proxy_str = data.get('proxy')
cookies = data.get('cookies')
target_user = data.get('target_username')
status = "Failed" print(f"Request received for FOLLOW using proxy: {proxy_str}")
target_btn = None
done_keys = ["Following", "Requested", "دنبال شد", "درخواست شد"]
follow_keys = ["Follow", "Follow Back", "دنبال کردن"]
for _ in range(20):
buttons = page.locator("header button")
if buttons.count() == 0: buttons = page.locator("button")
found = False
for i in range(buttons.count()):
btn = buttons.nth(i)
if not btn.is_visible(): continue
txt = btn.text_content().strip()
if any(k in txt for k in done_keys):
status = f"Already: {txt}"
found = True; break
if ("Message" in txt or "پیام" in txt) and page.get_by_text("Follow", exact=True).count() == 0:
pass
if any(k == txt or k in txt for k in follow_keys) and "Following" not in txt: context = create_context_with_proxy(proxy_str)
target_btn = btn page = context.new_page()
found = True; break inject_cookies(context, cookies)
if found: break
page.wait_for_timeout(1000) try:
page.goto(f"https://www.instagram.com/{target_user}/", wait_until="networkidle", timeout=60000)
except: pass
if "Already" in status: status = "Failed"
pass target_btn = None
elif target_btn: done_keys = ["Following", "Requested", "دنبال شد", "درخواست شد"]
target_btn.click(force=True) follow_keys = ["Follow", "Follow Back", "دنبال کردن"]
for _ in range(15):
for _ in range(20):
buttons = page.locator("header button")
if buttons.count() == 0: buttons = page.locator("button")
found = False
for i in range(buttons.count()):
btn = buttons.nth(i)
if not btn.is_visible(): continue
txt = btn.text_content().strip()
if any(k in txt for k in done_keys):
status = f"Already: {txt}"
found = True; break
if ("Message" in txt or "پیام" in txt) and page.get_by_text("Follow", exact=True).count() == 0:
pass
if any(k == txt or k in txt for k in follow_keys) and "Following" not in txt:
target_btn = btn
found = True; break
if found: break
page.wait_for_timeout(1000) page.wait_for_timeout(1000)
full_text = page.locator("header").text_content()
if any(k in full_text for k in done_keys):
status = "Success: Followed [Verified]"
break
if "Success" not in status: status = "Success: Clicked (Timeout)"
page.screenshot(path="current_view.png")
return jsonify({"status": status, "target": target_user})
except Exception as e: if "Already" in status:
return jsonify({"error": str(e)}), 500 pass
finally: elif target_btn:
if context: context.close() target_btn.click(force=True)
for _ in range(15):
page.wait_for_timeout(1000)
full_text = page.locator("header").text_content()
if any(k in full_text for k in done_keys):
status = "Success: Followed [Verified]"
break
if "Success" not in status: status = "Success: Clicked (Timeout)"
page.screenshot(path="current_view.png")
return jsonify({"status": status, "target": target_user})
except Exception as e:
return jsonify({"error": str(e)}), 500
finally:
if context: context.close()
@app.route('/screenshot') @app.route('/screenshot')
def serve_screenshot(): def serve_screenshot():
# اسکرین شات پشت قفل نمی‌ماند
if os.path.exists("current_view.png"): if os.path.exists("current_view.png"):
return send_file("current_view.png", mimetype='image/png') return send_file("current_view.png", mimetype='image/png')
return "No image" return "No image"
# ======================================================= # =======================================================
# 🔄 تابع آپدیت خودکار (اضافه شده) # 🔄 تابع آپدیت خودکار
# ======================================================= # =======================================================
def auto_update_loop(): def auto_update_loop():
"""هر 60 ثانیه چک می‌کند اگر آپدیت آمده بود، برنامه را ریستارت می‌کند""" """هر 60 ثانیه چک می‌کند اگر آپدیت آمده بود، برنامه را ریستارت می‌کند"""
@ -381,7 +394,6 @@ def auto_update_loop():
print("[UPDATER] Restarting application...\n") print("[UPDATER] Restarting application...\n")
# 5. Restart # 5. Restart
# بستن مرورگر قبل از ریستارت (اختیاری ولی تمیزتر)
if playwright_instance: if playwright_instance:
playwright_instance.stop() playwright_instance.stop()
@ -389,7 +401,7 @@ def auto_update_loop():
except Exception as e: except Exception as e:
print(f"[UPDATER ERROR]: {e}") print(f"[UPDATER ERROR]: {e}")
time.sleep(60) # اگر خطا داد، صبر کن و دوباره تلاش کن time.sleep(60)
if __name__ == '__main__': if __name__ == '__main__':
# --- شروع ترد آپدیت کننده در پس‌زمینه --- # --- شروع ترد آپدیت کننده در پس‌زمینه ---
@ -397,6 +409,8 @@ if __name__ == '__main__':
update_thread.start() update_thread.start()
print(">>> Auto-updater started in background...") print(">>> Auto-updater started in background...")
# حتما threaded=False باشد تا تداخل در Playwright پیش نیاید
start_browser_engine() # استارت اولیه موتور start_browser_engine() # استارت اولیه موتور
app.run(host='0.0.0.0', port=5000, threaded=False)
# تغییر مهم: threaded=True فعال شد
# درخواست‌های همزمان پذیرفته می‌شوند، اما توابع سنگین با TASK_LOCK صف‌بندی می‌شوند
app.run(host='0.0.0.0', port=5000, threaded=True)