code wiki / _hdl_build / nx_media_import.nx
nx_media_import.nx source
↩ module page · 128 lines · 7123 B
1// nx_media_import.nx -- GENERAL POLITE media importer: pull images from ANY site (Shein/e-commerce/art/galleries)
2// into the family library, RESPECTING the host's rules. Operator 2026-07-03: "import politely images from places
3// like shein etc to get state of the art." The differentiator vs a crude scraper is POLITENESS BY CONSTRUCTION:
4// 1. fetch the host's /robots.txt and HONOR it (nx_robots_allowed) -- a Disallow => we DO NOT fetch (refuse).
5// 2. send an HONEST User-Agent identifying us + a contact/purpose.
6// 3. pace requests to the host's Crawl-delay (nx_robots_crawl_delay), default a courteous floor.
7// Then it reuses the PROVEN engine: mg_gallery_to_pages (Chrome-JA3 fetch that beats anti-bot CDNs + multi-strategy
8// nx_img_harvest + download-integrity + cbx.json manifest) and lands the result in the caller's PRIVATE reader area
9// (rl_add_personal) so it shows up in the zoom reader as a browsable image collection. Composes; adds only the
10// politeness gate. CLI: nx_media_import <page_url> <slug> <ukey> (forked by the daemon on an "Import" POST).
11// license_tier: ORIGINAL
12import "nx_syscalls.nx"
13import "nx_x509_trust_store.nx"
14import "nx_trust_store_load_from_certdata.nx"
15import "nx_https_fetch_follow.nx" // nx_https_fetch_follow_chrome
16import "nx_robots.nx" // nx_robots_allowed / nx_robots_crawl_delay
17import "nx_manga_get.nx" // mg_gallery_to_pages (the download engine) + MG constants
18import "nx_reader_library.nx" // rl_personal_slug_dir / rl_add_personal
19const MI_MAGIC_1000000: i64 = 1000000
20const MI_MAGIC_1024: i64 = 1024
21const MI_MAGIC_9309: i64 = 9309
22const MI_MAGIC_4194304: i64 = 4194304
23
24// Honest, identifying UA (RFC 9309 politeness: a real product token the host can allow/deny + a contact).
25const MI_UA: *u8 = "NishiMediaImporter/1.0 (+https://nishifamily.com; personal family library import)"
26const MI_UA_LEN: i64 = 78
27const MI_POLITE_FLOOR_MS: i64 = 1000 // courteous default spacing if the host sets no Crawl-delay
28const MI_ROBOTS_CAP: i64 = 262144
29
30func mi_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
31func mi_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
32
33// Parse "scheme://host[/path...]" -> host copied to host_out (NUL-term), path start index into url (or -1 => "/").
34// Returns host length, or -1 on malformed.
35func mi_split(url: *u8, host_out: *u8, path_idx_out: *i64) -> i64 {
36 var i: i64 = 0
37 // find "://"
38 var found: i64 = 0 - 1
39 while url[i] != (0 as u8) {
40 if (url[i] as i64)==58 { if (url[i+1] as i64)==47 { if (url[i+2] as i64)==47 { found = i + 3; } } }
41 if found >= 0 { break }
42 i = i + 1
43 }
44 if found < 0 { return 0 - 1 }
45 var h: i64 = found
46 var ho: i64 = 0
47 while url[h] != (0 as u8) {
48 let c: i64 = url[h] as i64
49 if c == 47 { break } // '/' ends host
50 host_out[ho] = url[h]; ho = ho + 1; h = h + 1
51 }
52 host_out[ho] = 0 as u8
53 if url[h] == (0 as u8) { path_idx_out[0] = 0 - 1 } else { path_idx_out[0] = h }
54 return ho
55}
56
57// courteous busy-wait pacing (ms). No sleep syscall dependency assumption -> nanosleep via syscall 35 if available,
58// else a bounded spin. We use nanosleep (clock is fine; determinism not required for a live network importer).
59func mi_pace_ms(ms: i64) -> i64 {
60 if ms <= 0 { return 0 }
61 let ts: *i64 = sys_mmap(16) as *i64
62 ts[0] = ms / 1000 // tv_sec
63 ts[1] = (ms % 1000) * MI_MAGIC_1000000 // tv_nsec
64 __syscall(35, ts as i64, 0, 0, 0, 0, 0) // nanosleep(req, NULL)
65 return 0
66}
67
68// The polite import. Returns pages written (>=0), or negative: -1 malformed url, -2 robots DISALLOW (refused), -3 no pages.
69func mi_import(page_url: *u8, ukey: *u8, slug: *u8, store: *TrustStore) -> i64 {
70 let host: *u8 = sys_mmap(512)
71 let pidx_p: *i64 = sys_mmap(8) as *i64
72 let hlen: i64 = mi_split(page_url, host, pidx_p)
73 if hlen < 0 { mi_w("MEDIA-IMPORT: malformed url\n" as *u8); return 0 - 1 }
74 // the request path (default "/")
75 var path: *u8 = "/" as *u8
76 var plen: i64 = 1
77 if pidx_p[0] >= 0 { path = ((page_url as i64) + pidx_p[0]) as *u8; plen = mi_slen(path) }
78
79 // ---- POLITENESS 1: fetch + honor robots.txt ----
80 let rob_url: *u8 = sys_mmap(MI_MAGIC_1024)
81 var o: i64 = 0
82 let pre: *u8 = "https://" as *u8; var pi: i64 = 0; while pre[pi]!=(0 as u8){ rob_url[o]=pre[pi]; o=o+1; pi=pi+1 }
83 var hi: i64 = 0; while host[hi]!=(0 as u8){ rob_url[o]=host[hi]; o=o+1; hi=hi+1 }
84 let rp: *u8 = "/robots.txt" as *u8; var ri: i64 = 0; while rp[ri]!=(0 as u8){ rob_url[o]=rp[ri]; o=o+1; ri=ri+1 }
85 rob_url[o] = 0 as u8
86
87 let robots: *u8 = sys_mmap(MI_ROBOTS_CAP)
88 let rstatus: *i64 = sys_mmap(8) as *i64
89 let rlen: i64 = nx_https_fetch_follow_chrome(rob_url, store, robots, MI_ROBOTS_CAP, 4, rstatus)
90 var rob_bytes: i64 = 0
91 if rlen > 0 { if rstatus[0] == 200 { rob_bytes = rlen } } // 404/other => treat as no robots.txt (full allow per RFC MI_MAGIC_9309)
92
93 if nx_robots_allowed(robots, rob_bytes, MI_UA, MI_UA_LEN, path, plen) != 1 {
94 mi_w("MEDIA-IMPORT: REFUSED by robots.txt (polite) -> " as *u8); mi_w(page_url); mi_w("\n" as *u8)
95 return 0 - 2
96 }
97 // pacing: host Crawl-delay (seconds) or the courteous floor
98 var pace_ms: i64 = MI_POLITE_FLOOR_MS
99 let cd: i64 = nx_robots_crawl_delay(robots, rob_bytes, MI_UA, MI_UA_LEN)
100 if cd > 0 { pace_ms = cd * 1000 }
101 mi_pace_ms(pace_ms) // pause before the first real request
102
103 // ---- reuse the PROVEN engine (Chrome fetch + img harvest + integrity + cbx) into the PRIVATE area ----
104 let dest: *u8 = sys_mmap(MI_MAGIC_1024)
105 rl_personal_slug_dir(ukey, slug, dest)
106 let title_out: *u8 = sys_mmap(512); title_out[0] = 0 as u8
107 let pages: i64 = mg_gallery_to_pages(page_url, dest, "" as *u8, store, title_out)
108 if pages <= 0 { mi_w("MEDIA-IMPORT: no images harvested\n" as *u8); return 0 - 3 }
109 var teff: *u8 = title_out
110 if title_out[0] == (0 as u8) { teff = slug }
111 rl_add_personal(ukey, slug, teff, mi_slen(teff))
112 mi_w("MEDIA-IMPORT OK slug=" as *u8); mi_w(slug); mi_w(" pages=" as *u8)
113 // print pages
114 let b: *u8=sys_mmap(24); var m: i64=pages; var k: i64=0; if m==0{b[0]=48 as u8;k=1} let t: *u8=sys_mmap(24); while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var z: i64=0; while z<k{b[z]=t[k-1-z];z=z+1} sys_write(1,b,k)
115 mi_w(" -> personal area (robots-honored, paced)\n" as *u8)
116 return pages
117}
118
119func main(argc: i64, argv: *i64) -> i64 {
120 if argc < 4 { mi_w("usage: nx_media_import <page_url> <slug> <ukey>\n" as *u8); return 2 }
121 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, MI_MAGIC_4194304)
122 if r <= 0 { mi_w("MEDIA-IMPORT: certdata load failed\n" as *u8); return 3 }
123 let store: *TrustStore = r as *TrustStore
124 let rc: i64 = mi_import(argv[1] as *u8, argv[3] as *u8, argv[2] as *u8, store)
125 if rc == 0 - 2 { return 4 } // robots refusal -> distinct exit for the daemon
126 if rc < 0 { return 1 }
127 return 0
128}