141 lines
5.9 KiB
Python
141 lines
5.9 KiB
Python
|
|
"""
|
||
|
|
Direct capture script for JS MINING AND ENGINEERING EMP201 periods.
|
||
|
|
Uses .click() on the EMP201 link (form loads on same page via JS).
|
||
|
|
"""
|
||
|
|
import base64
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import re
|
||
|
|
import subprocess
|
||
|
|
import tempfile
|
||
|
|
import time
|
||
|
|
|
||
|
|
|
||
|
|
AB = "C:\\Users\\SJ\\AppData\\Roaming\\npm\\agent-browser.cmd"
|
||
|
|
|
||
|
|
PERIOD_URLS = {
|
||
|
|
"202603": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=168198289&TaxPayerID=31142387&ReadOnly=1&Period=202603&TPReturnITID=0&TPReturnPITID=79672519&HistoryInd=1",
|
||
|
|
"202604": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=168885394&TaxPayerID=31142387&ReadOnly=1&Period=202604&TPReturnITID=0&TPReturnPITID=80251983&HistoryInd=1",
|
||
|
|
"202605": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=169701876&TaxPayerID=31142387&ReadOnly=1&Period=202605&TPReturnITID=0&TPReturnPITID=80776676&HistoryInd=1",
|
||
|
|
"202606": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=170268751&TaxPayerID=31142387&ReadOnly=1&Period=202606&TPReturnITID=0&TPReturnPITID=81270492&HistoryInd=1",
|
||
|
|
"202607": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=170816319&TaxPayerID=31142387&ReadOnly=1&Period=202607&TPReturnITID=0&TPReturnPITID=81856371&HistoryInd=1",
|
||
|
|
"202608": "https://secure.sarsefiling.co.za/EFDotNet/LoadPage.aspx?PageCode=EMP2013LANDING&ReturnTypeID=36&ReturnGroupID=171605060&TaxPayerID=31142387&ReadOnly=1&Period=202608&TPReturnITID=0&TPReturnPITID=82341489&HistoryInd=1",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def run_ab(*args, timeout=30):
|
||
|
|
tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, encoding="utf-8", errors="replace", prefix="ab_", suffix=".out")
|
||
|
|
tmp_path = tmp.name
|
||
|
|
tmp.close()
|
||
|
|
try:
|
||
|
|
result = subprocess.run([AB, *args], stdout=open(tmp_path, "w", encoding="utf-8", errors="replace"), stderr=subprocess.DEVNULL, timeout=timeout)
|
||
|
|
with open(tmp_path, "r", encoding="utf-8", errors="replace") as f:
|
||
|
|
out = f.read().strip()
|
||
|
|
finally:
|
||
|
|
try: os.unlink(tmp_path)
|
||
|
|
except OSError: pass
|
||
|
|
if result.returncode != 0:
|
||
|
|
raise RuntimeError(f"agent-browser {args!r} failed: {out}")
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def eval_js(js):
|
||
|
|
b64 = base64.b64encode(js.encode("utf-8")).decode("ascii")
|
||
|
|
out = run_ab("eval", "-b", b64)
|
||
|
|
if out == "null" or not out:
|
||
|
|
return None
|
||
|
|
if out.startswith('"') and out.endswith('"'):
|
||
|
|
out = json.loads(out)
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def navigate_iframe_to(url):
|
||
|
|
js = f"document.querySelectorAll('iframe')[0].src = {json.dumps(url)}; 'ok'"
|
||
|
|
eval_js(js)
|
||
|
|
|
||
|
|
|
||
|
|
def click_emp201_link():
|
||
|
|
"""Click the EMP201 link inside the iframe; form loads on same page via JS."""
|
||
|
|
js = """(() => {
|
||
|
|
const ifr = document.querySelectorAll('iframe')[0];
|
||
|
|
const doc = ifr.contentDocument;
|
||
|
|
const links = Array.from(doc.querySelectorAll('a'));
|
||
|
|
// Find link with text 'EMP201' or href with BPEMP201Form
|
||
|
|
let target = links.find(a => a.href && a.href.includes('BPEMP201Form'));
|
||
|
|
if (!target) target = links.find(a => a.textContent.trim() === 'EMP201');
|
||
|
|
if (!target) return JSON.stringify({ clicked: false, reason: 'no link' });
|
||
|
|
target.click();
|
||
|
|
return JSON.stringify({ clicked: true, href: target.href });
|
||
|
|
})()"""
|
||
|
|
return eval_js(js)
|
||
|
|
|
||
|
|
|
||
|
|
def read_input_xml():
|
||
|
|
js = """(() => {
|
||
|
|
const ifr = document.querySelectorAll('iframe')[0];
|
||
|
|
const doc = ifr.contentDocument;
|
||
|
|
const all = Array.from(doc.querySelectorAll('input'));
|
||
|
|
const x = all.find(i => i.name === 'InputXML');
|
||
|
|
return x ? x.value : '';
|
||
|
|
})()"""
|
||
|
|
res = eval_js(js)
|
||
|
|
return res or ""
|
||
|
|
|
||
|
|
|
||
|
|
def parse_input_xml(xml_str):
|
||
|
|
xml_str = (xml_str.replace("<", "<").replace(">", ">")
|
||
|
|
.replace(""", '"').replace("&", "&"))
|
||
|
|
def get(tag):
|
||
|
|
m = re.search(rf"<{tag}>([^<]*)</{tag}>", xml_str)
|
||
|
|
return m.group(1) if m else ""
|
||
|
|
return {
|
||
|
|
"company_name": get("TRADING_NAME"),
|
||
|
|
"paye_no": get("PAYE_REF_NO"),
|
||
|
|
"payment_period": get("PAYMENT_PERIOD"),
|
||
|
|
"paye_payable": get("PAYE_AMT"),
|
||
|
|
"sdl_payable": get("SDL_AMT"),
|
||
|
|
"uif_payable": get("UIF_AMT"),
|
||
|
|
"total_payable": get("TOTAL_PAYMENT"),
|
||
|
|
"payment_ref_no": get("PAYMENT_REF_NO"),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
periods_wanted = ["202603", "202604", "202605", "202606", "202607", "202608"]
|
||
|
|
results = []
|
||
|
|
for period in periods_wanted:
|
||
|
|
if period not in PERIOD_URLS:
|
||
|
|
continue
|
||
|
|
href = PERIOD_URLS[period]
|
||
|
|
print(f"\n{period}: navigating to work page...", flush=True)
|
||
|
|
navigate_iframe_to(href)
|
||
|
|
time.sleep(3.5)
|
||
|
|
print(f" clicking EMP201 link...", flush=True)
|
||
|
|
click_emp201_link()
|
||
|
|
# Wait longer for form to load
|
||
|
|
for i in range(6):
|
||
|
|
time.sleep(2)
|
||
|
|
xml = read_input_xml()
|
||
|
|
if xml:
|
||
|
|
break
|
||
|
|
if not xml:
|
||
|
|
print(f" {period}: no InputXML after 12s", flush=True)
|
||
|
|
continue
|
||
|
|
fields = parse_input_xml(xml)
|
||
|
|
fields["period"] = period
|
||
|
|
results.append(fields)
|
||
|
|
print(f" {period}: PAYE={fields['paye_payable']} SDL={fields['sdl_payable']} UIF={fields['uif_payable']} Total={fields['total_payable']} Ref={fields['payment_ref_no']}", flush=True)
|
||
|
|
# Navigate back to work page (back to landing) so next iteration's navigate works
|
||
|
|
# The navigate_iframe_to sets a new URL, so we don't need to click Back
|
||
|
|
|
||
|
|
print("\n\n=== FINAL RESULTS ===", flush=True)
|
||
|
|
print(json.dumps(results, indent=2), flush=True)
|
||
|
|
out_path = r"C:\Users\SJ\Downloads\js_mining_emp201_results.json"
|
||
|
|
with open(out_path, "w", encoding="utf-8") as f:
|
||
|
|
json.dump(results, f, indent=2)
|
||
|
|
print(f"\nSaved to {out_path}", flush=True)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|