code wiki / _hdl_build / nx_img_harvest.nx

nx_img_harvest.nx source

↩ module page · 195 lines · 9945 B

1// nx_img_harvest.nx -- S-CLASS-EXCEED image-URL harvester (operator 2026-07-03: "no we want to get s class 2// exceed" -- the generic <img src>-only extractor missed LAZY-LOADED + JS/JSON-embedded pages, which is how 3// modern manga readers actually ship their page lists; gallery-dl's edge is exactly this). MULTI-STRATEGY, raw 4// scan of the page bytes so it is agnostic to WHERE the URL lives: 5// * <img src=..> (the old floor) 6// * lazy attrs data-src / data-original / data-lazy-src / data-url (quoted values are just strings) 7// * srcset="a 1x, b 2x" (each comma/space token checked -> highest set of candidates) 8// * JS / JSON page arrays "pages":["https:\/\/cdn\/01.jpg", ...] window.__data / __NEXT_DATA__ / chapter.images 9// Mechanism: scan for every QUOTED region (" or '), tokenize its content on space/comma, UNESCAPE JSON "\/"->"/", 10// and if a token LOOKS like an image URL (path ends .jpg/.jpeg/.png/.webp/.gif/.avif before ?/#; NOT data:/.svg/ 11// sprite/favicon UI-junk) resolve it (absolute passes through; relative via the proven nx_url_resolve) + DEDUP. 12// Order = appearance order (matches reading order for HTML galleries + JSON arrays). Returns the count; fills the 13// same offs/lens/url_buf interface nx_html_extract_imgs uses, so nx_manga_get swaps it in with no other change. 14// The completeness magic-guard in nx_manga_get still fetch-verifies each (valid FULL file), so a stray non-page 15// URL that slips the heuristic simply fails the fetch -- never a blank/false page. license_tier: ORIGINAL 16import "nx_syscalls.nx" 17import "nx_html_extract_imgs.nx" // nx_url_resolve + NX_URL_RESOLVE_OK (proven relative->absolute resolver) 18const K_MAGIC_8192: i64 = 8192 19 20// lowercase a byte 21func ih_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 22 23// does tok[0..tn) (already the raw token, may contain ?query) name an image by extension? (path = up to ?/#) 24// returns 1/0. Rejects data: URIs, .svg, and obvious UI junk (favicon/sprite) even if they end in an image ext. 25func ih_looks_img(tok: *u8, tn: i64) -> i64 { 26 if tn < 5 { return 0 } 27 // reject data: URIs 28 if tn >= 5 { if ih_lc(tok[0] as i64)==100 { if ih_lc(tok[1] as i64)==97 { if ih_lc(tok[2] as i64)==116 { if ih_lc(tok[3] as i64)==97 { if tok[4]==(58 as u8) { return 0 } } } } } } // "data:" 29 // path end = first ? or # 30 var end: i64 = tn 31 var i: i64 = 0 32 while i < tn { let c: i64 = tok[i] as i64; if c == 63 { if end == tn { end = i } } if c == 35 { if end == tn { end = i } } i = i + 1 } 33 if end < 5 { return 0 } 34 // last '.' 35 var dot: i64 = 0 - 1 36 var j: i64 = 0 37 while j < end { if tok[j] == (46 as u8) { dot = j } j = j + 1 } 38 if dot < 0 { return 0 } 39 let el: i64 = end - dot 40 if el < 4 { return 0 } 41 if el > 5 { return 0 } 42 // build lowercased ext 43 let e: *u8 = sys_mmap(8) 44 var k: i64 = 0 45 while dot + k < end { if k < 7 { e[k] = ih_lc(tok[dot + k] as i64) as u8 } k = k + 1 } 46 e[k] = 0 as u8 47 var okext: i64 = 0 48 if ih_streq(e, ".jpg" as *u8)==1 { okext = 1 } 49 if ih_streq(e, ".jpeg" as *u8)==1 { okext = 1 } 50 if ih_streq(e, ".png" as *u8)==1 { okext = 1 } 51 if ih_streq(e, ".webp" as *u8)==1 { okext = 1 } 52 if ih_streq(e, ".gif" as *u8)==1 { okext = 1 } 53 if ih_streq(e, ".avif" as *u8)==1 { okext = 1 } 54 if okext == 0 { return 0 } 55 // UI-junk denylist by path substring (favicon/sprite/logo) -- unambiguous non-page assets 56 if ih_has_ci(tok, end, "favicon" as *u8)==1 { return 0 } 57 if ih_has_ci(tok, end, "sprite" as *u8)==1 { return 0 } 58 return 1 59} 60func ih_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]==(0 as u8){return 1} return 0 } 61// case-insensitive substring within hay[0..hn) 62func ih_has_ci(hay: *u8, hn: i64, needle: *u8) -> i64 { 63 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 } 64 if nl == 0 { return 0 } 65 var i: i64 = 0 66 while i + nl <= hn { 67 var m: i64 = 1; var jx: i64 = 0 68 while jx < nl { if ih_lc(hay[i+jx] as i64) != ih_lc(needle[jx] as i64) { m = 0; jx = nl } else { jx = jx + 1 } } 69 if m == 1 { return 1 } 70 i = i + 1 71 } 72 return 0 73} 74 75// copy tok[0..tn) into out, converting JSON escape "\/"->"/" and stopping at a backslash-u or control; NUL-term. 76func ih_unescape(tok: *u8, tn: i64, out: *u8, cap: i64) -> i64 { 77 var o: i64 = 0 78 var i: i64 = 0 79 while i < tn { 80 if o >= cap - 1 { i = tn } else { 81 let c: i64 = tok[i] as i64 82 if c == 92 { if i + 1 < tn { if tok[i+1] == (47 as u8) { out[o] = 47 as u8; o = o + 1; i = i + 2 } else { out[o] = tok[i] as u8; o = o + 1; i = i + 1 } } else { i = tn } } 83 else { out[o] = tok[i]; o = o + 1; i = i + 1 } 84 } 85 } 86 out[o] = 0 as u8 87 return o 88} 89 90// already-collected? linear scan of url_buf via offs/lens (n small) 91func ih_seen(url_buf: *u8, offs: *i64, lens: *i64, count: i64, cand: *u8, cn: i64) -> i64 { 92 var i: i64 = 0 93 while i < count { 94 if lens[i] == cn { 95 var eq: i64 = 1; var k: i64 = 0 96 let bp: *u8 = ((url_buf as i64) + offs[i]) as *u8 97 while k < cn { if bp[k] != cand[k] { eq = 0; k = cn } else { k = k + 1 } } 98 if eq == 1 { return 1 } 99 } 100 i = i + 1 101 } 102 return 0 103} 104 105// resolve a candidate (unescaped) -> resolved into out; returns length or 0 on fail. Absolute http(s) passes 106// through; protocol-relative //h/p gets https:; else nx_url_resolve against base. 107func ih_resolve(base: *u8, blen: i64, cand: *u8, cn: i64, out: *u8, cap: i64) -> i64 { 108 // http:// or https:// 109 if cn >= 7 { if ih_lc(cand[0] as i64)==104 { if ih_lc(cand[1] as i64)==116 { if ih_lc(cand[2] as i64)==116 { if ih_lc(cand[3] as i64)==112 { 110 var s: i64 = 4 111 if ih_lc(cand[4] as i64)==115 { s = 5 } 112 if cand[s]==(58 as u8) { if cand[s+1]==(47 as u8) { if cand[s+2]==(47 as u8) { 113 var o: i64 = 0; while o < cn { if o < cap - 1 { out[o] = cand[o] } o = o + 1 } out[cn] = 0 as u8; return cn 114 } } } 115 } } } } } 116 // protocol-relative //host/path -> https: 117 if cn >= 2 { if cand[0]==(47 as u8) { if cand[1]==(47 as u8) { 118 let pfx: *u8 = "https:" as *u8 119 var o: i64 = 0; while o < 6 { out[o] = pfx[o]; o = o + 1 } 120 var q: i64 = 0; while q < cn { if o < cap - 1 { out[o] = cand[q]; o = o + 1 } q = q + 1 } 121 out[o] = 0 as u8; return o 122 } } } 123 // relative -> proven resolver 124 let olp: *i64 = sys_mmap(8) as *i64 125 let rc: i64 = nx_url_resolve(base, blen, cand, cn, out, cap, olp) 126 if rc == NX_URL_RESOLVE_OK { return olp[0] } 127 return 0 128} 129 130// consider one raw token (a whitespace/comma-delimited piece of a quoted region): if it looks like an image URL, 131// unescape + resolve + dedup + append. Returns the NEW count. Shared by the quoted-region loop. 132func ih_consider(tok: *u8, tn: i64, base_url: *u8, base_url_len: i64, 133 url_buf: *u8, url_buf_cap: i64, offs: *i64, lens: *i64, max_imgs: i64, 134 count: i64, buf_pos_p: *i64, unesc: *u8, resolved: *u8) -> i64 { 135 if count >= max_imgs { return count } 136 if tn < 5 { return count } 137 let un: i64 = ih_unescape(tok, tn, unesc, K_MAGIC_8192) 138 if ih_looks_img(unesc, un) == 0 { return count } 139 let rn: i64 = ih_resolve(base_url, base_url_len, unesc, un, resolved, K_MAGIC_8192) 140 if rn <= 0 { return count } 141 if ih_seen(url_buf, offs, lens, count, resolved, rn) == 1 { return count } 142 if buf_pos_p[0] + rn >= url_buf_cap { return count } 143 var k: i64 = 0 144 while k < rn { url_buf[buf_pos_p[0] + k] = resolved[k]; k = k + 1 } 145 offs[count] = buf_pos_p[0] 146 lens[count] = rn 147 buf_pos_p[0] = buf_pos_p[0] + rn 148 return count + 1 149} 150 151// THE HARVEST. See header. Returns count; fills offs/lens (into url_buf) up to max_imgs. 152func nx_img_harvest(html: *u8, hlen: i64, base_url: *u8, base_url_len: i64, 153 url_buf: *u8, url_buf_cap: i64, offs: *i64, lens: *i64, max_imgs: i64) -> i64 { 154 var count: i64 = 0 155 let bpp: *i64 = sys_mmap(8) as *i64; bpp[0] = 0 156 let tokbuf: *u8 = sys_mmap(K_MAGIC_8192) 157 let unesc: *u8 = sys_mmap(K_MAGIC_8192) 158 let resolved: *u8 = sys_mmap(K_MAGIC_8192) 159 var i: i64 = 0 160 while i < hlen { 161 if count >= max_imgs { i = hlen } else { 162 let ch: i64 = html[i] as i64 163 var q: i64 = 0 164 if ch == 34 { q = 1 } 165 if ch == 39 { q = 1 } 166 if q == 1 { 167 // read the quoted region [i+1, close) 168 var s: i64 = i + 1 169 var e: i64 = s 170 var go: i64 = 1 171 while go == 1 { if e >= hlen { go = 0 } else { if (html[e] as i64) == ch { go = 0 } else { e = e + 1 } } } 172 // tokenize [s,e) on space / comma / tab / newline into tokbuf; consider each token 173 var ts: i64 = s 174 var p: i64 = s 175 while p <= e { 176 var brk: i64 = 0 177 if p == e { brk = 1 } else { let c2: i64 = html[p] as i64; if c2 == 32 { brk = 1 } if c2 == 9 { brk = 1 } if c2 == 10 { brk = 1 } if c2 == 13 { brk = 1 } if c2 == 44 { brk = 1 } } 178 if brk == 1 { 179 let tl: i64 = p - ts 180 if tl > 0 { if tl < K_MAGIC_8192 { 181 var w: i64 = 0 182 while w < tl { tokbuf[w] = html[ts + w]; w = w + 1 } 183 tokbuf[tl] = 0 as u8 184 count = ih_consider(tokbuf, tl, base_url, base_url_len, url_buf, url_buf_cap, offs, lens, max_imgs, count, bpp, unesc, resolved) 185 } } 186 ts = p + 1 187 } 188 p = p + 1 189 } 190 if e < hlen { i = e + 1 } else { i = hlen } 191 } else { i = i + 1 } 192 } 193 } 194 return count 195}