feat(ancpi): extract Angular AJAX endpoints from ShowCartItems page
This commit is contained in:
@@ -77,36 +77,68 @@ export async function GET(req: Request) {
|
||||
}
|
||||
|
||||
// ── Step: search ──
|
||||
// SearchEstate.action returns HTML (Angular app), not JSON.
|
||||
// Need to find the real AJAX endpoints from the Angular JS code.
|
||||
if (step === "search") {
|
||||
const client = await EpayClient.create(username, password);
|
||||
const countyIdx = resolveEpayCountyIndex("Cluj")!;
|
||||
|
||||
const results: Record<string, unknown> = {};
|
||||
// Add to cart first
|
||||
const basketRowId = await client.addToCart(14200);
|
||||
|
||||
// First add to cart — SearchEstate might only work with active cart
|
||||
let basketRowId: number | null = null;
|
||||
try {
|
||||
basketRowId = await client.addToCart(14200);
|
||||
results["_basketRowId"] = basketRowId;
|
||||
} catch (e) {
|
||||
results["_cartError"] = (e as Error).message;
|
||||
// Load ShowCartItems.action — the Angular form page
|
||||
const cartPageHtml = await client.getRawHtml(
|
||||
`${process.env.ANCPI_BASE_URL || "https://epay.ancpi.ro/epay"}/ShowCartItems.action`,
|
||||
);
|
||||
|
||||
// Extract Angular AJAX endpoints and form structure
|
||||
// Look for: SearchEstate, EpayJsonInterceptor, ng-controller, $http
|
||||
const patterns = [
|
||||
/SearchEstate[^"'\s]*/g,
|
||||
/EpayJsonInterceptor[^"'\s]*/g,
|
||||
/EditCartItem[^"'\s]*/g,
|
||||
/\$http\.(post|get)\s*\(\s*['"]([^'"]+)['"]/g,
|
||||
/action\s*[:=]\s*['"]([^'"]*)['"]/g,
|
||||
/url\s*[:=]\s*['"]([^'"]*?\.action[^'"]*)['"]/g,
|
||||
/ng-controller\s*=\s*["']([^"']+)["']/g,
|
||||
/searchEstate|SearchEstate|cautaImobil/gi,
|
||||
];
|
||||
|
||||
const found: Record<string, string[]> = {};
|
||||
for (const pat of patterns) {
|
||||
const matches = cartPageHtml.match(pat);
|
||||
if (matches && matches.length > 0) {
|
||||
found[pat.source.slice(0, 40)] = [...new Set(matches)].slice(0, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Test SearchEstate: without uatId (optional now)
|
||||
results["345295_noUat"] = await client.searchEstate("345295", countyIdx).catch((e: Error) => ({ error: e.message }));
|
||||
// Also extract any inline JSON data (Angular scope initialization)
|
||||
const jsonMatches = cartPageHtml.match(/ng-init\s*=\s*["']([^"']{10,500})["']/g);
|
||||
|
||||
// Test with uatId=24 (spec says Cluj-Napoca)
|
||||
results["345295_uat24"] = await client.searchEstate("345295", countyIdx, 24).catch((e: Error) => ({ error: e.message }));
|
||||
// Find county/UAT dropdown references
|
||||
const selectMatches = cartPageHtml.match(/<select[^>]*(?:judet|county|uat)[^>]*>/gi);
|
||||
|
||||
// Other parcels without uatId
|
||||
results["63565_noUat"] = await client.searchEstate("63565", countyIdx).catch((e: Error) => ({ error: e.message }));
|
||||
results["88089_noUat"] = await client.searchEstate("88089", countyIdx).catch((e: Error) => ({ error: e.message }));
|
||||
// Look for the specific search function
|
||||
const searchFuncMatch = cartPageHtml.match(
|
||||
/function\s+\w*[Ss]earch\w*\s*\([^)]*\)\s*\{[^}]{0,500}/g,
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
step: "search",
|
||||
countyIdx,
|
||||
basketRowId,
|
||||
results,
|
||||
pageLength: cartPageHtml.length,
|
||||
angularEndpoints: found,
|
||||
ngInit: jsonMatches?.slice(0, 5) ?? [],
|
||||
selectDropdowns: selectMatches?.slice(0, 5) ?? [],
|
||||
searchFunctions: searchFuncMatch?.slice(0, 3) ?? [],
|
||||
// Also check: does the page contain "SearchEstate" anywhere?
|
||||
hasSearchEstate: cartPageHtml.includes("SearchEstate"),
|
||||
hasJsonInterceptor: cartPageHtml.includes("EpayJsonInterceptor"),
|
||||
// Sample of any .action URLs in the page
|
||||
actionUrls: [...new Set(
|
||||
(cartPageHtml.match(/['"][^'"]*\.action[^'"]*['"]/g) ?? [])
|
||||
.map((s: string) => s.replace(/['"]/g, ""))
|
||||
.filter((s: string) => s.length < 100),
|
||||
)].slice(0, 20),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -245,6 +245,17 @@ export class EpayClient {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Raw HTML fetch (for page scraping) ─────────────────── */
|
||||
|
||||
async getRawHtml(url: string): Promise<string> {
|
||||
const response = await this.client.get(url, {
|
||||
timeout: DEFAULT_TIMEOUT_MS,
|
||||
maxRedirects: 5,
|
||||
validateStatus: () => true,
|
||||
});
|
||||
return String(response.data ?? "");
|
||||
}
|
||||
|
||||
/* ── Credits ───────────────────────────────────────────────── */
|
||||
|
||||
async getCredits(): Promise<number> {
|
||||
|
||||
Reference in New Issue
Block a user