code wiki / _hdl_build / nx_manga_get.nx
nx_manga_get.nx source
↩ module page · 335 lines · 19197 B
1// nx_manga_get.nx -- R2a of the native manga DOWNLOAD ladder (census row URL): a generic gallery/chapter PAGE URL
2// -> ordered page images in a book dir + cbx.json, sovereign end-to-end (no python/gallery-dl/curl). Composes the
3// PROVEN organs: nx_https_fetch_follow (own TLS-1.3 GET) + nx_html_extract_imgs (extract every <img src>, RESOLVED
4// relative->absolute via nx_url_resolve) + nx_reader_library (land in the downloader's PRIVATE area). FORK-PER-IMAGE
5// so each image fetch's TLS mmaps die with the child (the researcher's proven per-fetch-process pattern) -> a
6// 200-page gallery never leaks. Writes reader/u_<ukey>/<slug>/page000.<ext>... + cbx.json (the reader/zoom viewer's
7// manifest), then rl_add_personal(ukey, slug, title). The daemon calls this (fork) on an "Add manga" POST; also a
8// CLI. LEGAL POSTURE: general capability; smoke on public-domain/legal pages; the operator points it where they like.
9// license_tier: ORIGINAL
10import "nx_syscalls.nx"
11import "nx_x509_trust_store.nx"
12import "nx_trust_store_load_from_certdata.nx"
13import "nx_https_fetch_follow.nx"
14import "nx_img_harvest.nx" // nx_img_harvest = s-class multi-strategy extractor (img/lazy/srcset/JSON) [+ nx_html_extract_imgs/nx_url_resolve transitively]
15import "nx_manga_sites.nx" // R2b per-site adapters: na_detect + na_nhentai_api/build + na_mangadex_api/build
16import "nx_reader_library.nx" // rl_personal_slug_dir / rl_add_personal (+ zs_* transitively)
17import "nx_img_dims.nx" // nx_img_long_edge = REAL pixel size from the header (junk/icon filter)
18const MG_MAGIC_2048: i64 = 2048
19const MG_MAGIC_65536: i64 = 65536
20const MG_MAGIC_2166136261: i64 = 2166136261
21const MG_MAGIC_16777619: i64 = 16777619
22const MG_MAGIC_1048576: i64 = 1048576
23const MG_MAGIC_4096: i64 = 4096
24const MG_MAGIC_4095: i64 = 4095
25const MG_MAGIC_4194304: i64 = 4194304
26
27const MG_MAX_IMGS: i64 = 512
28const MG_IMG_CAP: i64 = 16777216 // 16MB per image cap (fetch buffer, per child)
29const MG_HTML_CAP: i64 = 8388608 // 8MB page HTML cap
30const MG_MIN_BYTES: i64 = 1024 // floor: below this = tracker/spacer/blank, never a real manga page
31const MG_MIN_EDGE: i64 = 100 // reject nav icons/spacers/tracking-pixels: long edge < 100px is never content (real pages/photos are bigger)
32
33func mg_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
34func mg_putn(v: i64) -> i64 { if v==0 { sys_write(1,"0" as *u8,1); return 0 } var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} let d:*u8=sys_mmap(24); var k:i64=0; while m>0{d[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i:i64=k-1; while i>=0{let o:*u8=sys_mmap(1);o[0]=d[i];sys_write(1,o,1);i=i-1} return 0 }
35func mg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
36func mg_apps(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[o+i]=s[i]; i=i+1 } return o+i }
37func mg_num(dst: *u8, o: i64, v: i64) -> i64 { let t: *u8=sys_mmap(24); var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var w: i64=o; var i: i64=0; while i<k{dst[w]=t[k-1-i]; w=w+1; i=i+1} return w }
38
39// 3-digit zero-padded page filename: page<NNN><ext> into out
40func mg_page_name(idx: i64, ext: *u8, out: *u8) -> i64 {
41 var o: i64 = mg_apps(out, 0, "page" as *u8)
42 out[o] = (48 + ((idx / 100) % 10)) as u8; o = o + 1
43 out[o] = (48 + ((idx / 10) % 10)) as u8; o = o + 1
44 out[o] = (48 + (idx % 10)) as u8; o = o + 1
45 o = mg_apps(out, o, ext)
46 out[o] = 0 as u8
47 return o
48}
49
50func mg_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 }
51func mg_ext_ok(e: *u8) -> i64 {
52 if mg_streq(e, ".jpg" as *u8)==1 { return 1 }
53 if mg_streq(e, ".jpeg" as *u8)==1 { return 1 }
54 if mg_streq(e, ".png" as *u8)==1 { return 1 }
55 if mg_streq(e, ".webp" as *u8)==1 { return 1 }
56 if mg_streq(e, ".gif" as *u8)==1 { return 1 }
57 if mg_streq(e, ".avif" as *u8)==1 { return 1 }
58 return 0
59}
60func mg_default_jpg(out: *u8) -> i64 { out[0]=46 as u8; out[1]=106 as u8; out[2]=112 as u8; out[3]=103 as u8; out[4]=0 as u8; return 4 }
61// image extension from a URL (strips ?query#frag; lowercases). Defaults .jpg. Recognizes jpg/jpeg/png/webp/gif/avif.
62func mg_ext_from_url(url: *u8, out: *u8) -> i64 {
63 let n: i64 = mg_slen(url)
64 // end = first '?' or '#', else n
65 var end: i64 = n
66 var i: i64 = 0
67 while i < n { let c: i64 = url[i] as i64; if c == 63 { if end == n { end = i } } if c == 35 { if end == n { end = i } } i = i + 1 }
68 // last '.' before end
69 var dot: i64 = 0 - 1
70 var j: i64 = 0
71 while j < end { if url[j] == (46 as u8) { dot = j } j = j + 1 }
72 if dot < 0 { return mg_default_jpg(out) }
73 let el: i64 = end - dot
74 if el > 6 { return mg_default_jpg(out) }
75 var o: i64 = 0
76 var k: i64 = dot
77 while k < end { var c: i64 = url[k] as i64; if c >= 65 { if c <= 90 { c = c + 32 } } out[o] = c as u8; o = o + 1; k = k + 1 }
78 out[o] = 0 as u8
79 if mg_ext_ok(out) == 0 { return mg_default_jpg(out) }
80 return o
81}
82
83// the FIRST two bytes must be a real image magic (JPEG ffd8 / PNG 8950 / GIF 4749 / RIFF-webp 5249 / etc) --
84// the no-fake-green guard so an HTML error page saved as page000.jpg is rejected.
85func mg_img_magic_ok(b: *u8, n: i64) -> i64 {
86 if n < 4 { return 0 }
87 if b[0]==(0xff as u8) { if b[1]==(0xd8 as u8) { return 1 } } // JPEG
88 if b[0]==(0x89 as u8) { if b[1]==(0x50 as u8) { return 1 } } // PNG
89 if b[0]==(0x47 as u8) { if b[1]==(0x49 as u8) { return 1 } } // GIF
90 if b[0]==(0x52 as u8) { if b[1]==(0x49 as u8) { return 1 } } // RIFF (webp)
91 return 0
92}
93
94// ★ COMPLETE image check (integrity: a valid FULL file, not blank/empty/TRUNCATED/false). Requires the header
95// magic AND the format's end-of-file trailer AND >= MG_MIN_BYTES. A truncated download (valid header, cut off
96// before the trailer) is REJECTED -> never lands as a broken/blank page. This is the "actually downloads a valid
97// full file" guarantee: JPEG must end ff d9; PNG must end with the IEND chunk (49 45 4e 44 + crc); GIF must end
98// 3b; WEBP(RIFF) must have received at least its declared RIFF payload length.
99func mg_img_complete(b: *u8, n: i64) -> i64 {
100 if n < MG_MIN_BYTES { return 0 }
101 // JPEG: ff d8 ... ff d9
102 if b[0]==(0xff as u8) { if b[1]==(0xd8 as u8) {
103 if b[n-2]==(0xff as u8) { if b[n-1]==(0xd9 as u8) { return 1 } }
104 return 0
105 } }
106 // PNG: 89 50 4e 47 ... 49 45 4e 44 <4-byte crc> (last 8 bytes = IEND + crc)
107 if b[0]==(0x89 as u8) { if b[1]==(0x50 as u8) { if b[2]==(0x4e as u8) { if b[3]==(0x47 as u8) {
108 if b[n-8]==(0x49 as u8) { if b[n-7]==(0x45 as u8) { if b[n-6]==(0x4e as u8) { if b[n-5]==(0x44 as u8) { return 1 } } } }
109 return 0
110 } } } }
111 // GIF: 47 49 46 ... 3b
112 if b[0]==(0x47 as u8) { if b[1]==(0x49 as u8) { if b[2]==(0x46 as u8) {
113 if b[n-1]==(0x3b as u8) { return 1 }
114 return 0
115 } } }
116 // WEBP: RIFF <size LE> WEBP ... ; complete iff we received >= (size + 8) bytes
117 if b[0]==(0x52 as u8) { if b[1]==(0x49 as u8) { if b[2]==(0x46 as u8) { if b[3]==(0x46 as u8) {
118 if n >= 12 { if b[8]==(0x57 as u8) { if b[9]==(0x45 as u8) { if b[10]==(0x42 as u8) { if b[11]==(0x50 as u8) {
119 let riffsize: i64 = (b[4] as i64) | ((b[5] as i64) << 8) | ((b[6] as i64) << 16) | ((b[7] as i64) << 24)
120 if riffsize + 8 <= n { return 1 }
121 } } } } }
122 return 0
123 } } } }
124 return 0
125}
126
127// fetch ONE image URL and write it to <dir>/<pagename>. Runs in a FORKED CHILD (mmaps reclaimed on exit).
128// Returns via exit code: 0 = wrote a real image, 1 = fetch/magic/write fail. Parent reads the page file to confirm.
129func mg_fetch_one(url: *u8, store: *TrustStore, dir: *u8, pagename: *u8) -> i64 {
130 let buf: *u8 = sys_mmap(MG_IMG_CAP)
131 let status: *i64 = sys_mmap(8) as *i64
132 let n: i64 = nx_https_fetch_follow_chrome(url, store, buf, MG_IMG_CAP, 6, status)
133 if n <= 0 { return 1 }
134 if status[0] != 200 { return 1 } // reject 403/404/redirect-HTML error bodies up front
135 if mg_img_complete(buf, n) == 0 { return 1 } // valid FULL image only (header+trailer+size); truncated/blank REJECTED
136 if nx_img_long_edge(buf, n) < MG_MIN_EDGE { return 1 } // junk filter: measure REAL pixels, drop icons/spacers (unknown formats KEPT)
137 let path: *u8 = sys_mmap(MG_MAGIC_2048)
138 var o: i64 = mg_apps(path, 0, dir)
139 path[o] = 47 as u8; o = o + 1
140 o = mg_apps(path, o, pagename)
141 path[o] = 0 as u8
142 let fd: i64 = sys_openat_wr(path, 0x1a4)
143 if fd < 0 { return 1 }
144 sys_write(fd, buf, n)
145 sys_close(fd)
146 return 0
147}
148
149// content fingerprint of a written page file: FNV-1a-32(bytes) packed with the byte-size (63-bit, forced non-zero).
150// Two files match iff same content-hash AND same size -> a robust DEDUP key (no adversary; just avoid re-saving the
151// same image linked twice / at multiple srcset sizes). Streams in 64KB chunks (no huge buffer). 0 = file missing.
152func mg_file_fp(path: *u8) -> i64 {
153 let fd: i64 = sys_openat_rd(path)
154 if fd < 0 { return 0 }
155 let chunk: i64 = MG_MAGIC_65536
156 let buf: *u8 = sys_mmap(chunk)
157 var h: i64 = MG_MAGIC_2166136261 // FNV-1a-32 offset basis
158 var total: i64 = 0
159 var run: i64 = 1
160 while run == 1 {
161 let n: i64 = sys_read(fd, buf, chunk)
162 if n <= 0 { run = 0 } else {
163 var j: i64 = 0
164 while j < n { h = h ^ (buf[j] as i64 & 0xff); h = (h * MG_MAGIC_16777619) & 0xffffffff; j = j + 1 }
165 total = total + n
166 }
167 }
168 sys_close(fd)
169 sys_munmap(buf as *u8, chunk)
170 return ((h & 0xffffffff) | ((total & 0x7fffffff) << 32)) | 1
171}
172
173// write <dir>/cbx.json listing the pages (the reader/zoom viewer manifest). exts[i] = page i's extension ptr.
174func mg_write_cbx(dir: *u8, title: *u8, npages: i64, exts: *i64) -> i64 {
175 let jp: *u8 = sys_mmap(MG_MAGIC_2048)
176 var o: i64 = mg_apps(jp, 0, dir); o = mg_apps(jp, o, "/cbx.json" as *u8); jp[o] = 0 as u8
177 let fd: i64 = sys_openat_wr(jp, 0x1a4)
178 if fd < 0 { return 0 - 1 }
179 let hb: *u8 = sys_mmap(MG_MAGIC_65536)
180 var b: i64 = mg_apps(hb, 0, "{\"title\":\"" as *u8)
181 var t: i64 = 0; while title[t] != (0 as u8) { let c: i64 = title[t] as i64; if c >= 32 { if c != 34 { if c != 92 { hb[b] = title[t]; b = b + 1 } } } t = t + 1 }
182 b = mg_apps(hb, b, "\",\"format\":\"web-gallery\",\"pages\":[" as *u8)
183 let pn: *u8 = sys_mmap(64)
184 var i: i64 = 0
185 while i < npages {
186 if i > 0 { hb[b] = 44 as u8; b = b + 1 }
187 b = mg_apps(hb, b, "{\"idx\":" as *u8)
188 b = mg_num(hb, b, i)
189 b = mg_apps(hb, b, ",\"file\":\"" as *u8)
190 mg_page_name(i, exts[i] as *u8, pn)
191 b = mg_apps(hb, b, pn)
192 b = mg_apps(hb, b, "\"}" as *u8)
193 i = i + 1
194 }
195 b = mg_apps(hb, b, "],\"npages\":" as *u8)
196 b = mg_num(hb, b, npages)
197 b = mg_apps(hb, b, "}" as *u8)
198 sys_write(fd, hb, b)
199 sys_close(fd)
200 return npages
201}
202
203// write <dir>/dl.json = {"total":T,"done":D} -- the live progress file the /api/dlstatus endpoint reads so the UI
204// can show "downloading 12/45" (operator 2026-07-04: modern UI wants PROGRESS). Cheap (~30 bytes) per page.
205func mg_write_dl(dir: *u8, total: i64, done: i64) -> i64 {
206 let jp: *u8 = sys_mmap(MG_MAGIC_2048)
207 var o: i64 = mg_apps(jp, 0, dir); o = mg_apps(jp, o, "/dl.json" as *u8); jp[o] = 0 as u8
208 let fd: i64 = sys_openat_wr(jp, 0x1a4)
209 if fd < 0 { return 0 }
210 let b: *u8 = sys_mmap(128); var q: i64 = mg_apps(b, 0, "{\"total\":" as *u8)
211 q = mg_num(b, q, total); q = mg_apps(b, q, ",\"done\":" as *u8); q = mg_num(b, q, done); q = mg_apps(b, q, "}" as *u8)
212 sys_write(fd, b, q); sys_close(fd)
213 return 0
214}
215
216// ORCHESTRATE: page_url -> dest_dir/pageNNN + cbx.json. Returns pages written (>=0), or negative error
217// (-1 html fetch, -2 no images). store must be loaded by the caller (inherited by the fork children).
218// title_out (cap 512) is filled with the EFFECTIVE title: the page's own <title>/og:title if found, else the
219// caller's fallback `title` -- so the operator gets the real title automatically ("just focus on reading").
220func mg_gallery_to_pages(page_url: *u8, dest_dir: *u8, title: *u8, store: *TrustStore, title_out: *u8) -> i64 {
221 let url_buf: *u8 = sys_mmap(MG_MAGIC_1048576)
222 let offs: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64
223 let lens: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64
224 let status: *i64 = sys_mmap(8) as *i64
225 let pn: i64 = mg_slen(page_url)
226 var nimg: i64 = 0
227 // default the effective title to the caller's fallback; page-based paths overwrite it with the real title
228 var ti: i64 = 0; while title[ti] != (0 as u8) { title_out[ti] = title[ti]; ti = ti + 1 } title_out[ti] = 0 as u8
229 // ---- R2b: PER-SITE ADAPTER dispatch. nhentai = parse the gallery PAGE (its v1 API is deprecated -> 403);
230 // mangadex = the at-home API (public, stable). Else generic multi-strategy harvest. ----
231 let site: i64 = na_detect(page_url, pn)
232 if site == 1 {
233 // nhentai: fetch the gallery PAGE, grab the title, derive full-res page URLs from its thumbnails
234 let html: *u8 = sys_mmap(MG_HTML_CAP)
235 let hn: i64 = nx_https_fetch_follow_chrome(page_url, store, html, MG_HTML_CAP, 6, status)
236 if hn > 0 { if status[0] == 200 {
237 if na_html_title(html, hn, title_out, 500) <= 0 { var t2: i64 = 0; while title[t2]!=(0 as u8){ title_out[t2]=title[t2]; t2=t2+1 } title_out[t2]=0 as u8 }
238 let c: i64 = na_nhentai_from_page(html, hn, url_buf, MG_MAGIC_1048576, offs, lens, MG_MAX_IMGS)
239 if c > 0 { nimg = c }
240 } }
241 } else { if site == 2 {
242 let api: *u8 = sys_mmap(256)
243 if na_mangadex_api(page_url, pn, api) > 0 {
244 let jb: *u8 = sys_mmap(MG_HTML_CAP)
245 let jn: i64 = nx_https_fetch_follow_chrome(api, store, jb, MG_HTML_CAP, 6, status)
246 if jn > 0 { if status[0] == 200 { let c: i64 = na_mangadex_build(jb, jn, url_buf, MG_MAGIC_1048576, offs, lens, MG_MAX_IMGS); if c > 0 { nimg = c } } }
247 }
248 // SERIES + auto-title: one metadata call -> "<Series> - Ch.<N>" (so the operator never has to name it).
249 if nimg > 0 {
250 let mapi: *u8 = sys_mmap(512)
251 if na_mangadex_meta_api(page_url, pn, mapi) > 0 {
252 let mb: *u8 = sys_mmap(MG_HTML_CAP)
253 let mn: i64 = nx_https_fetch_follow_chrome(mapi, store, mb, MG_HTML_CAP, 6, status)
254 if mn > 0 { if status[0] == 200 { na_mangadex_title(mb, mn, title_out, 500) } }
255 }
256 }
257 } }
258 // GENERIC (or adapter miss): fetch the page HTML + multi-strategy harvest (img/lazy/srcset/JS-JSON) + title
259 if nimg <= 0 {
260 let html: *u8 = sys_mmap(MG_HTML_CAP)
261 let hn: i64 = nx_https_fetch_follow_chrome(page_url, store, html, MG_HTML_CAP, 6, status)
262 if hn <= 0 { return 0 - 1 }
263 if na_html_title(html, hn, title_out, 500) <= 0 { var t3: i64 = 0; while title[t3]!=(0 as u8){ title_out[t3]=title[t3]; t3=t3+1 } title_out[t3]=0 as u8 }
264 nimg = nx_img_harvest(html, hn, page_url, pn, url_buf, MG_MAGIC_1048576, offs, lens, MG_MAX_IMGS)
265 }
266 if nimg <= 0 { return 0 - 2 }
267 __syscall(258, 0 - 100, dest_dir as i64, 0x1ed, 0, 0, 0) // mkdir dest (idempotent)
268 mg_write_dl(dest_dir, nimg, 0) // progress: total known, 0 done -> UI can show a real bar immediately
269 // 3. fork-fetch each image -> pageNNN.<ext>; keep only the ones that land as a real image
270 let exts: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64
271 let seen: *i64 = sys_mmap(8 * MG_MAX_IMGS) as *i64 // content fingerprints of KEPT pages (dedup set)
272 var written: i64 = 0
273 var ndup: i64 = 0
274 var i: i64 = 0
275 while i < nimg {
276 let url: *u8 = ((url_buf as i64) + offs[i]) as *u8
277 // NUL-terminate a private copy of this URL (url_buf is packed)
278 let u: *u8 = sys_mmap(MG_MAGIC_4096)
279 var k: i64 = 0
280 while k < lens[i] { if k < MG_MAGIC_4095 { u[k] = url[k] } k = k + 1 }
281 u[lens[i]] = 0 as u8
282 let ext: *u8 = sys_mmap(8)
283 mg_ext_from_url(u, ext)
284 let pn: *u8 = sys_mmap(64)
285 mg_page_name(written, ext, pn)
286 // fork the fetch (child mmaps reclaimed on exit)
287 let pid: i64 = sys_fork()
288 if pid == 0 { let rc: i64 = mg_fetch_one(u, store, dest_dir, pn); sys_exit(rc) }
289 let st: *i64 = sys_mmap(8) as *i64
290 sys_wait4(pid, st, 0)
291 // confirm the page file exists (child wrote it) before counting it
292 let pp: *u8 = sys_mmap(MG_MAGIC_2048); var po: i64 = mg_apps(pp, 0, dest_dir); pp[po]=47 as u8; po=po+1; po = mg_apps(pp, po, pn); pp[po]=0 as u8
293 // DEDUP: fingerprint the landed page; if it matches an already-kept page, delete it + reuse the slot.
294 let fp: i64 = mg_file_fp(pp)
295 if fp != 0 {
296 var dup: i64 = 0
297 var s: i64 = 0
298 while s < written { if seen[s] == fp { dup = 1; s = written } else { s = s + 1 } }
299 if dup == 1 { __syscall(263, 0 - 100, pp as i64, 0, 0, 0, 0); ndup = ndup + 1 } // unlinkat AT_FDCWD: drop the byte-identical duplicate
300 else { seen[written] = fp; exts[written] = ext as i64; written = written + 1 }
301 }
302 mg_write_dl(dest_dir, nimg, written) // progress heartbeat -> /api/dlstatus -> live "N/total" in the UI
303 i = i + 1
304 }
305 if ndup > 0 { mg_puts("MANGA-GET: deduped " as *u8); mg_putn(ndup); mg_puts(" byte-identical duplicate image(s)\n" as *u8) }
306 if written <= 0 {
307 __syscall(263, 0 - 100, dest_dir as i64, 0x200, 0, 0, 0) // unlinkat AT_REMOVEDIR: remove the empty book dir (no sprawl on total failure)
308 return 0 - 2
309 }
310 mg_write_cbx(dest_dir, title_out, written, exts)
311 return written
312}
313
314func main(argc: i64, argv: *i64) -> i64 {
315 if argc < 4 { mg_puts("usage: nx_manga_get <page_url> <slug> <ukey> [title]\n" as *u8); sys_exit(2); return 2 }
316 let page_url: *u8 = argv[1] as *u8
317 let slug: *u8 = argv[2] as *u8
318 let ukey: *u8 = argv[3] as *u8
319 var title: *u8 = slug
320 if argc > 4 { title = argv[4] as *u8 }
321
322 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, MG_MAGIC_4194304)
323 if r <= 0 { mg_puts("MANGA-GET: certdata load failed\n" as *u8); sys_exit(1); return 1 }
324 let store: *TrustStore = r as *TrustStore
325
326 let dest: *u8 = sys_mmap(MG_MAGIC_2048)
327 rl_personal_slug_dir(ukey, slug, dest) // reader/u_<ukey>_<slug>
328
329 let title_eff: *u8 = sys_mmap(512)
330 let np: i64 = mg_gallery_to_pages(page_url, dest, title, store, title_eff)
331 if np < 0 { mg_puts("MANGA-GET-FAIL rc=" as *u8); mg_putn(np); mg_puts("\n" as *u8); sys_exit(1); return 1 }
332 rl_add_personal(ukey, slug, title_eff, mg_slen(title_eff))
333 mg_puts("MANGA-GET OK slug=" as *u8); mg_puts(slug); mg_puts(" title=" as *u8); mg_puts(title_eff); mg_puts(" pages=" as *u8); mg_putn(np); mg_puts(" -> personal area\n" as *u8)
334 sys_exit(0); return 0
335}