This commit is contained in:
javad committed 2026-10-09 20:02:36 +03:30
1 parent 6f36db63a1
commit 250cdbab7f
42 files changed
+96363 -11699

No files matched your search

+273
View File
@@ -0,0 +1,273 @@
import asyncio
import base64
import json
import os
import re
import cv2
import ddddocr
import numpy as np
from asgiref.sync import async_to_sync
from django.contrib.auth.decorators import login_required, permission_required
from django.http import JsonResponse
from playwright.async_api import async_playwright
from ._helpers import logger, _safe_int
# =====================================================================
# توابع کمکی و منطق استعلام چک (برگرفته از اسکریپت‌های شما)
# =====================================================================
def solve_base64_captcha(base64_string: str) -> str:
try:
if ',' in base64_string:
base64_string = base64_string.split(',')[1]
image_bytes = base64.b64decode(base64_string)
np_arr = np.frombuffer(image_bytes, np.uint8)
img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
if img is None:
return ""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 3)
_, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((2, 2), np.uint8)
cleaned_img = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
final_img = cv2.bitwise_not(cleaned_img)
success, encoded_image = cv2.imencode('.png', final_img)
if not success:
return ""
processed_bytes = encoded_image.tobytes()
ocr = ddddocr.DdddOcr(show_ad=False)
extracted_text = ocr.classification(processed_bytes)
numbers = re.findall(r'\d+', extracted_text)
if len(numbers) >= 2:
num1, num2 = int(numbers[0]), int(numbers[1])
if '+' in extracted_text:
ans = num1 + num2
elif '-' in extracted_text:
ans = num1 - num2
elif '*' in extracted_text or 'x' in extracted_text.lower():
ans = num1 * num2
else:
ans = num1 + num2
return str(ans)
return ""
except Exception:
return ""
def parse_cheque_result_html(html_content: str) -> dict:
sayad_id = re.search(r'شناسه\s*<b>?\s*(\d+)\s*</b>?', html_content)
owner_name = re.search(r'متعلق به\s*<b>?\s*(.*?)\s*</b>?\s*می باشد', html_content)
validity_date = re.search(r'تا تاریخ\s*<b>?\s*([\d/]+)\s*</b>?', html_content)
status = re.search(r'در وضعیت\s*<b>?\s*(.*?)\s*</b>?\s*در پایگاه', html_content)
clean_full_text = re.sub(r'<[^>]+>', '', html_content).strip()
return {
"success": True,
"data": {
"sayad_id": sayad_id.group(1) if sayad_id else "",
"owner_name": owner_name.group(1).strip() if owner_name else "",
"validity_date": validity_date.group(1) if validity_date else "",
"status": status.group(1).strip() if status else "",
"raw_message": clean_full_text
},
"error": None
}
async def run_cbi_sayad_inquiry_logic(sayad_id: str, max_retries: int = 7) -> str:
lightpanda_url = os.environ.get("LIGHTPANDA_URL") or "ws://127.0.0.1:9222"
async with async_playwright() as p:
try:
browser = await p.chromium.connect_over_cdp(lightpanda_url)
except Exception as e:
return json.dumps({"success": False, "data": None, "error": f"عدم اتصال به Lightpanda: {str(e)}"})
for attempt in range(1, max_retries + 1):
context = await browser.new_context(ignore_https_errors=True, bypass_csp=True)
page = await context.new_page()
try:
url = "https://cbi.ir/EstelamSayad/19689.aspx"
await page.goto(url, wait_until="domcontentloaded", timeout=20000)
captcha_selector = "#ctl00_ucBody_ucContent_ctl00_imgcpatcha"
await page.wait_for_selector(captcha_selector, state="visible", timeout=15000)
await asyncio.sleep(1)
base64_src = await page.locator(captcha_selector).get_attribute("src")
viewstate = await page.locator("#__VIEWSTATE").get_attribute("value") or ""
viewstate_gen = await page.locator("#__VIEWSTATEGENERATOR").get_attribute("value") or "453B48D8"
if not base64_src or not viewstate:
await context.close()
continue
captcha_answer = solve_base64_captcha(base64_src)
if not captcha_answer:
await context.close()
continue
ajax_raw_response = await page.evaluate("""
async ({ sayadId, captchaAns, viewState, viewStateGen }) => {
try {
const formData = new URLSearchParams();
formData.append('ctl00$ucBody$ucContent$ctl00$ScriptManager1', 'ctl00$ucBody$ucContent$ctl00$UpdatePanel1|ctl00$ucBody$ucContent$ctl00$btnSearch');
formData.append('__EVENTTARGET', '');
formData.append('__EVENTARGUMENT', '');
formData.append('__VIEWSTATE', viewState);
formData.append('__VIEWSTATEGENERATOR', viewStateGen);
formData.append('__VIEWSTATEENCRYPTED', '');
formData.append('ctl00$ucHeader$txtQuickSearch', '');
formData.append('ctl00$ucBody$ucContent$ctl00$txtChequeNumber', sayadId);
formData.append('ctl00$ucBody$ucContent$ctl00$txtCaptchaInput', captchaAns);
formData.append('__ASYNCPOST', 'true');
formData.append('ctl00$ucBody$ucContent$ctl00$btnSearch', 'استعلام چک صیادی');
const response = await fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Cache-Control': 'no-cache'
},
body: formData.toString()
});
return await response.text();
} catch (err) {
return 'FETCH_ERROR: ' + err.message;
}
}
""", {'sayadId': sayad_id, 'captchaAns': captcha_answer, 'viewState': viewstate, 'viewStateGen': viewstate_gen})
if "FETCH_ERROR:" in ajax_raw_response:
await context.close()
await asyncio.sleep(1)
continue
error_match = re.search(r'id="ctl00_ucBody_ucContent_ctl00_lblError"[^>]*>(.*?)</span>', ajax_raw_response, re.DOTALL)
if error_match and error_match.group(1).strip():
clean_error = re.sub(r'<[^>]+>', '', error_match.group(1)).strip()
if "عبارت محاسباتی" in clean_error:
await context.close()
continue
else:
await context.close()
await browser.close()
return json.dumps({"success": False, "data": None, "error": clean_error})
result_match = re.search(r'id="ctl00_ucBody_ucContent_ctl00_lblChequeNumberResult"[^>]*>(.*?)</span>', ajax_raw_response, re.DOTALL)
if result_match:
raw_html = result_match.group(1)
parsed_result = parse_cheque_result_html(raw_html)
await context.close()
await browser.close()
return json.dumps(parsed_result)
await context.close()
except Exception:
await context.close()
await asyncio.sleep(1)
await browser.close()
return json.dumps({"success": False, "data": None, "error": "استعلام ناموفق بود (خطای شبکه یا تشخیص کپچا)"})
async def run_bpi_cheque_inquiry_logic(national_id: str, sayad_id: str, id_type: int = 1):
lightpanda_url = os.environ.get("LIGHTPANDA_URL") or "ws://127.0.0.1:9222"
async with async_playwright() as p:
try:
browser = await p.chromium.connect_over_cdp(lightpanda_url)
except Exception as e:
return {"success": False, "error": f"عدم اتصال به مرورگر Lightpanda: {str(e)}"}
context = await browser.new_context()
page = await context.new_page()
try:
await page.goto("https://vbank.bpi.ir/public/inquiries/inquiry-bounced-cheques", wait_until="domcontentloaded", timeout=20000)
api_url = f"https://sec.bpi.ir/prls/api/v1/inquiry/chequeStatus?IdCode={national_id}&IdType={id_type}&SayadId={sayad_id}"
response = await context.request.get(
api_url,
headers={
"Referer": "https://vbank.bpi.ir/public/inquiries/inquiry-bounced-cheques",
"Accept": "application/json, text/plain, */*"
}
)
if response.ok:
data = await response.json()
return {"success": True, "data": data}
else:
error_text = await response.text()
return {"success": False, "error": f"سرور خطا برگرداند (کد {response.status}): {error_text}"}
except Exception as e:
return {"success": False, "error": f"خطایی در فرآیند استعلام رخ داد: {str(e)}"}
finally:
await context.close()
await browser.close()
# =====================================================================
# View های مربوط به استعلام (API)
# =====================================================================
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewStore', raise_exception=True)
def api_check_status_inquiry(request):
"""
استعلام وضعیت چک از بانک مرکزی
پارامتر ورودی: sayad_id (GET)
"""
sayad_id = request.GET.get('sayad_id', '').strip()
if not sayad_id or not sayad_id.isdigit() or len(sayad_id) != 16:
return JsonResponse({"success": False, "error": "شناسه صیادی باید دقیقاً ۱۶ رقم باشد."})
try:
# اجرای تابع Async به صورت Sync
result_json_str = async_to_sync(run_cbi_sayad_inquiry_logic)(sayad_id)
# تبدیل رشته جیسون به دیکشنری برای ارسال مجدد با JsonResponse
result_data = json.loads(result_json_str)
return JsonResponse(result_data)
except Exception as e:
logger.error(f"Error in CBI Check Status Inquiry: {str(e)}", exc_info=True)
return JsonResponse({"success": False, "error": "خطای سرور در برقراری ارتباط با بانک مرکزی."})
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewStore', raise_exception=True)
def api_check_sabt_inquiry(request):
"""
استعلام ثبت چک از بانک پاسارگاد
پارامترهای ورودی: sayad_id, national_id, id_type (GET)
"""
sayad_id = request.GET.get('sayad_id', '').strip()
national_id = request.GET.get('national_id', '').strip()
id_type = _safe_int(request.GET.get('id_type', 1)) # عدد 1 حقیقی، 2 حقوقی
# اگر کدملی از فرانت‌اند خالی ارسال شد، از یک کدملی پیش‌فرض استفاده می‌کنیم
if not national_id:
national_id = "1219968218"
if not sayad_id:
return JsonResponse({"success": False, "error": "شناسه صیادی برای استعلام ثبت الزامی است."})
try:
# اجرای تابع Async به صورت Sync
result_data = async_to_sync(run_bpi_cheque_inquiry_logic)(national_id, sayad_id, id_type)
return JsonResponse(result_data)
except Exception as e:
logger.error(f"Error in BPI Check Registration Inquiry: {str(e)}", exc_info=True)
return JsonResponse({"success": False, "error": "خطای سرور در برقراری ارتباط با سامانه بانک."})