app
Adom Chip Fetcher
Public Made by Adomby adom
Your whole parts library — manufacturer-grade chip CAD (symbol, footprint, 3D) one tap from your EDA tool.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
#!/bin/bash
# cf-ul-fetch.sh <MPN> <UL-deep-link>
#
# Drive the Ultra Librarian "Choose CAD Formats & Download" flow with the
# proven fast-path pattern. Goes through reCAPTCHA, lets the AI/user solve
# any image grids, and submits the moment the token populates — no
# round-trip between detect and Submit.
#
# Use the UL deep-link from the manufacturer's product page (e.g. on TI:
# scrape `vendor.ultralibrarian.com/.../embedded/?gpn=...&package=...&sid=...`).
# Do NOT guess `?vdrPN=` — that often routes to the wrong part.
#
# Example:
# bash cf-ul-fetch.sh TPS25751SRSMR \
# 'https://vendor.ultralibrarian.com/TI/embedded/?gpn=TPS25751&package=RSM&pin=32&sid=lit2ddb1769f694931cc606e8&c=1'
set -u
MPN="${1:?usage: cf-ul-fetch.sh <MPN> <UL-URL>}"
UL_URL="${2:?usage: cf-ul-fetch.sh <MPN> <UL-URL>}"
SESSION="${SESSION:-chip-fetcher}"
ACTIVITY="http://127.0.0.1:8786/api/activity"
post_act() { curl -s -X POST "$ACTIVITY" -H 'Content-Type: application/json' -d "{\"mpn\":\"$MPN\",\"stage\":\"$1\"}" >/dev/null; }
clear_act() { curl -s -X POST "$ACTIVITY" -H 'Content-Type: application/json' -d '{}' >/dev/null; }
post_act "navigating UL embedded"
adom-desktop browser_navigate "{\"sessionId\":\"$SESSION\",\"url\":\"$UL_URL\"}" >/dev/null 2>&1
sleep 6
# Verify part is correct (UL silently routes to wrong parts when gpn doesn't match)
TITLE=$(adom-desktop browser_eval "{\"sessionId\":\"$SESSION\",\"expr\":\"document.body.textContent.replace(/\\\\s+/g,' ').slice(0,200)\"}" 2>&1 \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('result',''))")
echo " loaded: $TITLE"
case "${TITLE,,}" in
*"$(echo $MPN | tr '[:upper:]' '[:lower:]' | head -c 8)"*) echo " ✓ part match" ;;
*) echo " ⚠ part mismatch — UL may have routed to a different chip; check the UL URL"; exit 1 ;;
esac
post_act "opening Choose CAD Formats"
RECT=$(adom-desktop browser_eval "{\"sessionId\":\"$SESSION\",\"expr\":\"(()=>{const el=Array.from(document.querySelectorAll('a')).find(a=>/choose cad/i.test(a.textContent)); if(!el)return null; const r=el.getBoundingClientRect(); return JSON.stringify({x:Math.round(r.left+r.width/2),y:Math.round(r.top+r.height/2)});})()\"}" 2>&1 \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('result',''))")
X=$(echo "$RECT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('x',0))" 2>/dev/null)
Y=$(echo "$RECT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('y',0))" 2>/dev/null)
adom-desktop browser_input_dispatch "{\"sessionId\":\"$SESSION\",\"type\":\"click\",\"x\":$X,\"y\":$Y}" >/dev/null 2>&1
sleep 3
post_act "pre-setting form (STEP+KiCAD v6+ +Altium+Fusion360+mm+T&C)"
adom-desktop browser_eval '{"sessionId":"'$SESSION'","expr":"(()=>{const cbs=Array.from(document.querySelectorAll(\"input[type=checkbox]\")); const want=[/^STEP$/i,/kicad v6/i,/^altium designer/i,/^fusion.?360/i,/agree|terms/i]; for(const re of want){const c=cbs.find(c=>re.test((c.parentElement?.textContent||c.closest(\"label\")?.textContent||\"\").trim())); if(c&&!c.checked) c.click();} const sels=Array.from(document.querySelectorAll(\"select\")); const u=sels.find(s=>Array.from(s.options).some(o=>/metric|mm/i.test(o.textContent))); if(u){const m=Array.from(u.options).find(o=>/metric|mm/i.test(o.textContent)); u.value=m.value;u.dispatchEvent(new Event(\"change\",{bubbles:true}));} return cbs.filter(c=>c.checked).length+\" checked\";})()"}' >/dev/null 2>&1
# Mark a unix-second checkpoint BEFORE the captcha. desktop_pull_glob below
# uses this as `since`, so only files written AFTER the checkpoint match.
BEFORE_TS=$(date +%s)
post_act "clicking reCAPTCHA — solve image grid in pup window if it appears"
RRECT=$(adom-desktop browser_eval '{"sessionId":"'$SESSION'","expr":"(()=>{const f=document.querySelector(\"iframe[src*=recaptcha]\"); if(!f)return null; const r=f.getBoundingClientRect(); return JSON.stringify({x:r.x|0,y:r.y|0});})()"}' 2>&1 \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('result',''))")
IX=$(echo "$RRECT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('x',0)+30)")
IY=$(echo "$RRECT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('y',0)+40)")
adom-desktop browser_input_dispatch "{\"sessionId\":\"$SESSION\",\"type\":\"click\",\"x\":$IX,\"y\":$IY}" >/dev/null 2>&1
# === THE WINNING PATTERN ===
# Atomic eval: in ONE browser_eval call, check token and click Submit if good.
# Eliminates the round-trip between "token detected" and "Submit clicked"
# that was costing us the captcha lifetime in earlier attempts.
post_act "watching for token → atomic submit"
for i in $(seq 1 90); do
sleep 1
RES=$(adom-desktop browser_eval "{\"sessionId\":\"$SESSION\",\"expr\":\"(()=>{const t=(document.querySelector('[name=g-recaptcha-response]')?.value||''); if(t.length>20){const a=Array.from(document.querySelectorAll('a, button, input[type=submit]')).find(e=>/^submit\$/i.test((e.textContent||e.value||'').trim())); if(a){a.click();return 'SUBMITTED_'+a.tagName;} return 'NO_BTN';} return 'WAIT';})()\"}" 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('result',''))")
case "$RES" in
SUBMITTED*) echo " $RES on attempt $i"; break ;;
esac
done
post_act "watching Downloads for ul_*.zip"
cd /home/adom/project/chip-fetcher
PULL=$(adom-desktop desktop_pull_glob "{\"path\":\"%USERPROFILE%\\\\Downloads\",\"glob\":\"ul_*.zip\",\"since\":$BEFORE_TS,\"wait\":true,\"timeoutMs\":60000,\"saveTo\":\"$(pwd)/incoming/\"}" 2>/dev/null)
ZIP_PATH=$(echo "$PULL" | python3 -c "import sys,json; d=json.load(sys.stdin); f=(d.get('files') or [{}])[0]; print(f.get('path',''))")
if [ -z "$ZIP_PATH" ] || [ ! -f "$ZIP_PATH" ]; then
post_act "TIMEOUT — no zip landed"
echo " TIMEOUT: no ul_*.zip in Downloads — $PULL"
clear_act
exit 1
fi
echo " FOUND: $ZIP_PATH"
post_act "importing"
./target/release/chip-fetcher import "$ZIP_PATH" --mpn "$MPN" 2>&1 | tail -10
post_act "imported"
sleep 2
clear_act