<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title type="html">Pharos</title>
<subtitle type="text">In reverse chronological order</subtitle>
<id>gopher://tilde.pink/0/~bencollver/fossil/pharos/atom.xml</id>
<link href="gopher://tilde.pink/0/~bencollver/fossil/pharos/atom.xml" rel="self" />
<link href="gopher://tilde.pink/1/~bencollver/fossil/pharos/" />
<updated>2026-03-13 16:01:14</updated>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/19efc38ff7/</id>
<title>Fix error in top level gophermap: rep...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2026-03-13 16:01:14

  Fix error in top level gophermap: replace space with tab. This
  was introduced in commit [95f091eb5b]

  INSERTED    DELETED
         1          1 src/gophermap.m4
         1          1 TOTAL over 1 changed file

Index: src/gophermap.m4
==================================================================
--- src/gophermap.m4
+++ src/gophermap.m4
@@ -21,6 +21,6 @@
 1Video	__DOCPATH__/video/	__SERVER__	__PORT__
 1Audio	__DOCPATH__/audio/	__SERVER__	__PORT__
 1Software	__DOCPATH__/software/	__SERVER__	__PORT__
 1Images	__DOCPATH__/images/	__SERVER__	__PORT__
 0About PHAROS	__DOCPATH__/about.txt	__SERVER__	__PORT__
-1Source Code	/~bencollver/fossil/pharos	tilde.pink 70
+1Source Code	/~bencollver/fossil/pharos	tilde.pink	70

]]></content>
<updated>2026-03-13 16:01:14</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/b9a6b6867b/</id>
<title>Remove stray digit 1 in search pagina...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2026-03-13 15:52:34

  Remove stray digit 1 in search pagination. This was the "item
  type" digit. It should have been removed when i added the item()
  helper function in commit [ce71322300]

  INSERTED    DELETED
         1          1 src/search.m4
         1          1 TOTAL over 1 changed file

Index: src/search.m4
==================================================================
--- src/search.m4
+++ src/search.m4
@@ -228,11 +228,11 @@
         item(out, "1", label, sel, server, port)
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
-        label = sprintf("1[>>] Page %d", page + 1)
+        label = sprintf("[>>] Page %d", page + 1)
         sel = sprintf("%s/search?%s/page%d/rows%d/%s", cgipath,
             search, page + 1, rows, sort_param)
         item(out, "1", label, sel, server, port)
     }
 

]]></content>
<updated>2026-03-13 15:52:34</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/bc5e705008/</id>
<title>The description was printed out of or...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2026-03-13 15:42:05

  The description was printed out of order on the details page.
  This was introduced in commit [ce71322300] when i converted to
  gophernicus. Fix print_html() and print_ref_full() to use out
  parameter.

  INSERTED    DELETED
         2          6 src/web.awk
         2          6 TOTAL over 1 changed file

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -110,11 +110,11 @@
             }
             if (type == TYPE_TEXT) {
                 print
             }
         } else {
-            print_ref_full($0, base, proto, root)
+            print_ref_full(out, $0, base, proto, root)
         }
     }
     close(cmd)
 
     unlink(curlcfg)
@@ -121,12 +121,10 @@
 
     return
 }
 
 function print_html(out, html,    cmd, marker, work) {
-    out = ""
-
     work = gettemp()
     gsub(/\\n/, "<br>", html)
     print html >work
     close(work)
     cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work,
@@ -150,15 +148,13 @@
 }
 
 # Print the webdump references section, converting relative URLs
 # to full URLs
 
-function print_ref_full(str, base, proto, root,     link, out,
+function print_ref_full(out, str, base, proto, root,     link,
     prefix, relative)
 {
-    out = ""
-
     if (match(str, /^ [0-9]+\. /)) {
         prefix = substr(str, 0, RLENGTH)
         link = substr(str, RLENGTH + 1)
         # convert relative links to full URLs
         if (link !~ /^[a-z]+:/) {

]]></content>
<updated>2026-03-13 15:42:05</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/63de0a047c/</id>
<title>Remove duplicate argument "out" from ...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2026-02-21 21:37:49

Remove duplicate argument "out" from function print_html()

  INSERTED    DELETED
         1          1 src/web.awk
         1          1 TOTAL over 1 changed file

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -120,11 +120,11 @@
     unlink(curlcfg)
 
     return
 }
 
-function print_html(out, html,    cmd, marker, out, work) {
+function print_html(out, html,    cmd, marker, work) {
     out = ""
 
     work = gettemp()
     gsub(/\\n/, "<br>", html)
     print html >work

]]></content>
<updated>2026-02-21 21:37:49</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/a61deffc58/</id>
<title>Replace https PHAROS source code link...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-12 15:26:11

Replace https PHAROS source code link with gopher item

  INSERTED    DELETED
         1          1 src/gophermap.m4
         1          1 TOTAL over 1 changed file

Index: src/gophermap.m4
==================================================================
--- src/gophermap.m4
+++ src/gophermap.m4
@@ -21,6 +21,6 @@
 1Video	__DOCPATH__/video/	__SERVER__	__PORT__
 1Audio	__DOCPATH__/audio/	__SERVER__	__PORT__
 1Software	__DOCPATH__/software/	__SERVER__	__PORT__
 1Images	__DOCPATH__/images/	__SERVER__	__PORT__
 0About PHAROS	__DOCPATH__/about.txt	__SERVER__	__PORT__
-hSource Code	URL:https://chiselapp.com/user/bencollver/repository/pharos	__SERVER__	__PORT__
+1Source Code	/~bencollver/fossil/pharos	tilde.pink 70

]]></content>
<updated>2025-11-12 15:26:11</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/93dd35263c/</id>
<title>Restore about.txt, accidentally delet...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 16:41:29

Restore about.txt, accidentally deleted in [95f091eb5b]

  INSERTED    DELETED
         1          0 make.sh
        36          0 src/about.txt
        37          0 TOTAL over 2 changed files

Index: make.sh
==================================================================
--- make.sh
+++ make.sh
@@ -51,9 +51,10 @@
 
 build_cgi ${SRC}/raw.m4 ${CGI}/debug
 build_cgi ${SRC}/raw.m4 ${CGI}/text
 build_cgi ${SRC}/download.m4 ${CGI}/direct
 build_cgi ${SRC}/raw.m4 ${CGI}/links
+cp ${SRC}/about.txt ${DST}/
 cp ${SRC}/wizard/gophermap  ${CGI}/wizard/
 
 cp pass.tsv ${DST}/pass.tsv
 exit 0

ADDED   src/about.txt
Index: src/about.txt
==================================================================
--- /dev/null
+++ src/about.txt
@@ -0,0 +1,36 @@
+# About PHAROS
+
+Proxy Internet Archive to gopher.
+
+# Search
+
+Example: To find Indian music, excluding commercial samples.
+
+    description:(indian) AND -collection:samples_only AND 
+         mediatype:audio AND subject:music
+
+# Fields
+
+* date:
+  YYYY-MM-DD or [YYYY-MM-DD TO YYYY-MM-DD]
+* mediatype:
+  audio, collection, data, image, movies, software, texts, or web
+
+# Media type key for search results
+
+* [aud] = audio or etree
+* [col] = collection
+* [dat] = data
+* [img] = image
+* [mov] = movies
+* [bin] = software
+* [txt] = texts
+* [web] = web
+
+See also: <https://archive.org/advancedsearch.php>
+
+# Limits
+
+This service does not work in the lagrange browser.
+Lagrange URI encodes the search string and also
+mangles items that are not URI encoded.

]]></content>
<updated>2025-11-09 16:41:29</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/1775faf7c6/</id>
<title>Update sort.m4 to use info() and item()</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 08:10:45

Update sort.m4 to use info() and item()

  INSERTED    DELETED
         8          5 src/sort.m4
         8          5 TOTAL over 1 changed file

Index: src/sort.m4
==================================================================
--- src/sort.m4
+++ src/sort.m4
@@ -5,12 +5,15 @@
 #
 # Change search sort order
 
 include(src/config.awk)
 incl(src/cgi.awk)
+incl(src/util.awk)
 
-function main(     i, lbl, opt) {
+function main(     i, lbl, opt, out, sel) {
+    out = ""
+
     lbl[1]  = "Default [^]"
     opt[1]  = "nav_order asc"
     lbl[2]  = "Default [v]"
     opt[2]  = "nav_order desc"
     lbl[3]  = "Weekly views [^]"
@@ -42,15 +45,15 @@
     lbl[16] = "Creator [v]"
     opt[16] = "creator desc"
     lbl[17] = "Random"
     opt[17] = "random asc"
 
-    printf "# Sort by\r\n"
-    printf "\r\n"
+    info(out, "# Sort by")
+    info(out, "")
     for (i = 1; i < 18; i++) {
-        printf "1%s\t%s/search?%s/sort%s\t%s\t%s\r\n",
-            lbl[i], cgipath, search, opt[i], server, port
+        sel = sprintf("%s/search?%s/sort%s", cgipath, search, opt[i])
+        item(out, "1", lbl[i], sel, server, port)
     }
     exit 0
 }
 
 BEGIN {

]]></content>
<updated>2025-11-09 08:10:45</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/ce71322300/</id>
<title>Scripts in the gophernicus CGI direct...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 08:04:54

  Scripts in the gophernicus CGI directory need to output network
  style EOL. Change \n to \n\r. Prefer printf over print. Add
  info() and item() convenience functions and use them. Fix
  parameter handling in wizard/step2

  INSERTED    DELETED
        29         31 src/account.m4
        17         17 src/cgi.awk
        64         62 src/details.m4
        15         12 src/download.m4
        24         24 src/list.m4
        19         17 src/lists.m4
        10          7 src/listsort.m4
        32         28 src/search.m4
         3          3 src/sort.m4
         1          1 src/sqlite.awk
        39         16 src/util.awk
        32         25 src/web.awk
        12          9 src/wizard/step1.m4
        15         11 src/wizard/step2.m4
        14          9 src/wizard/step3.m4
       326        272 TOTAL over 15 changed files

Index: src/account.m4
==================================================================
--- src/account.m4
+++ src/account.m4
@@ -12,12 +12,12 @@
 incl(src/sqlite.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 
 function main(     acct, cmd, col, cols, count, descr, dir, email,
-     iaout, id, item_server, item_size, output, prefix, signature,
-     str, thumb, title, type, url)
+    iaout, id, item_server, item_size, out, sel, signature, str,
+    thumb, title, type, url)
 {
     count = split(search, parts, "/")
     acct = parts[1]
     email = parts[2]
 
@@ -26,11 +26,11 @@
     if (length(str) > 0) {
         print str
         return
     }
 
-    output = cache_begin()
+    out = cache_begin()
     iaout = gettemp()
 
     url = api_endpoint "/metadata/" acct
     api_request(url, "GET", iaout)
     
@@ -75,48 +75,46 @@
         }
     }
     close(cmd)
 
     if (length(id) == 0) {
-        print_not_found(output, url)
+        print_not_found(out, url)
         cache_end()
         unlink(iaout)
         return
     }
 
-    print "Account: " acct >>output
+    info(out, "Account: " acct)
     if (length(thumb) > 0) {
         url = sprintf("http://%s%s/%s", item_server, dir, thumb)
-        printf "IThumbnail\t%s/raw/%s\t%s\t%s\n", cgipath, url,
-            server, port >>output
+        sel = cgipath "/raw/" url
+        item(out, "I", "Thumbnail", sel, server, port)
     }
-    print_html(output, descr)
+    print_html(out, descr)
 
     if (length(email) > 0) {
-        prefix = "/sortaddeddate desc"
-        printf "1Uploads\t%s\tsearch?uploader:%s%s\t%s\t%s\n", cgipath,
-            email, prefix, server, port >>output
-    }
-
-    prefix = "/sortaddeddate desc"
-    printf "1Items\t%s/search?anyfield:%s%s\t%s\t%s\n", cgipath,
-        acct, prefix, server, port >>output
-
-    printf "1Lists\t%s/lists?%s\t%s\t%s\n", cgipath, acct, server,
-        port >>output
-
-    print "" >>output
-    printf "%-20s %s\n", "Identifier:", id >>output
-    if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size >>output
-    }
-    printf "%-20s %s\n", "Media Type:", type >>output
-    
-    print "" >>output
-    printf "hAccount web page\tURL:%s/details?%s\t%s\t%s\n",
-        api_ssl_endpoint, uri_encode(id), server, port >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+        sel = cgipath "/search?uploader:" email "/sortaddeddate desc"
+        item(out, "1", "Uploads", sel, server, port)
+    }
+
+    sel = cgipath "/search?anyfield:" acct "/sortaddeddate desc"
+    item(out, "1", "Items", sel, server, port)
+
+    sel = cgipath "/lists?" acct
+    item(out, "1", "Lists", sel, server, port)
+
+    info(out, "")
+    info(out, sprintf("%-20s %s\r\n", "Identifier:", id))
+    if (item_size > 0) {
+        info(out, sprintf("%-20s %d\r\n", "Item Size:", item_size))
+    }
+    info(out, sprintf("%-20s %s\r\n", "Media Type:", type))
+    
+    info(out, "")
+    sel = sprintf("URL:%s/details?%s", api_ssl_endpoint, uri_encode(id))
+    item(out, "h", "Account web page", sel, server, port)
+    item(out, "1", "PHAROS", docpath, server, port)
 
     cache_end()
     unlink(iaout)
     exit 0
 }

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -1,21 +1,21 @@
 function block_msg(ip, extra) {
-    print "Access Denied"
-    print "============="
-    print ""
-    print "This service only allows access from vetted client IP addresses."
-    print "The intent is to thwart abusive bots and crawlers."
-    print ""
-    print "To gain access:"
-    print ""
-    print "* Email your client IP address (" ip ") to:"
-    print "    " contact
-    print ""
-    print "* Include the word \"friendly\" in your message."
-    print ""
-    print "Service admin will reply when granted."
-    print extra
+    printf "Access Denied\r\n"
+    printf "=============\r\n"
+    printf "\r\n"
+    printf "This service only allows access from vetted client IP addresses.\r\n"
+    printf "The intent is to thwart abusive bots and crawlers.\r\n"
+    printf "\r\n"
+    printf "To gain access:\r\n"
+    printf "\r\n"
+    printf "* Email your client IP address (" ip ") to:\r\n"
+    printf "    %s\r\n", contact
+    printf "\r\n"
+    printf "* Include the word \"friendly\" in your message.\r\n"
+    printf "\r\n"
+    printf "Service admin will reply when granted.\r\n"
+    printf "%s\r\n", extra
     return
 }
 
 function cgi_init(     extra, ip, item) {
     ip = ENVIRON["REMOTE_ADDR"]
@@ -65,12 +65,12 @@
         }
         close(pass_list)
         if (blocked) {
             extra = ""
             if (topdir == "details") {
-                extra = "\nIn the meanwhile, see:\n\n" \
-                    sprintf("hWeb page\tURL:%s/details/%s\t%s\t%s\n",
+                extra = "\r\nIn the meanwhile, see:\r\n\r\n" \
+                    sprintf("hWeb page\tURL:%s/details/%s\t%s\t%s\r\n",
                     api_ssl_endpoint,
                     uri_encode(search),
                     server,
                     port)
             }

Index: src/details.m4
==================================================================
--- src/details.m4
+++ src/details.m4
@@ -13,11 +13,11 @@
 incl(src/util.awk)
 incl(src/web.awk)
 
 function main(     add_date, col, cols, cmd, creator, descr, dir, i,
     iaout, id, item_id, item_server, item_size, label, language,
-    license, output, pub_date, scanner, signature, str, thumb,
+    license, out, pub_date, scanner, sel, signature, str, thumb,
     title, topic, topics, type, uploader_account, uploader_email,
     uploader_name, url)
 {
     item_id = search
 
@@ -27,11 +27,11 @@
         print str
         return
     }
 
     iaout = gettemp()
-    output = cache_begin()
+    out = cache_begin()
 
     url = api_endpoint "/metadata/" item_id
     api_request(url, "GET", iaout)
     
     # format search results as a gopher directory (menu)
@@ -112,70 +112,71 @@
         }
     }
     close(cmd)
 
     if (length(id) == 0) {
-        print_not_found(output, url)
+        print_not_found(out, url)
         cache_end()
         unlink(iaout)
         return
     }
 
-    print shorten(title, 70) >>output
+    info(out, shorten(title, 70))
     if (creators == 1) {
         label = "by " shorten(creator[1], 70)
-        printf "1%s\t%ssearch?creator:(%s)\t%s\t%s\n", label,
-            cgipath, creator[1], server, port >>output
+        sel = sprintf("%s/search?creator:(%s)", cgipath, creator[1])
+        item(out, "1", label, sel, server, port)
     } else if (creators > 1) {
-        printf "\nby:\n" >>output
+        info(out, "")
+        info(out, "by:")
         for (i = 1; i <= creators; i++) {
             label = shorten(creator[i], 70)
-            printf "1%s\t%s/search?creator:(%s)\t%s\t%s\n", label,
-                cgipath, creator[i], server, port >>output
+            sel = sprintf("%s/search?creator:(%s)", cgipath, creator[i])
+            item(out, "1", label, sel, server, port)
         }
-        printf "\n" >>output
+        info(out, "")
     }
     if (length(thumb) > 0) {
         url = sprintf("http://%s%s/%s", item_server, dir, thumb)
-        printf "IThumbnail\t%s/raw?%s\t%s\t%s\n", cgipath, url,
-            server, port >>output
+        sel = cgipath "/raw?" url
+        item(out, "I", "Thumbnail", sel, server, port)
     }
 
-    printf "1Download\t%s/download?%s\t%s\t%s\n", cgipath, item_id,
-        server, port >>output
-    printf "hWeb page\tURL:%s/details/%s\t%s\t%s\n",
-        api_ssl_endpoint, uri_encode(id), server, port >>output
-    print "" >>output
-
-    print_html(output, descr)
-
-    print "" >>output
+    sel = cgipath "/download?" item_id
+    item(out, "1", "Download", sel, server, port)
+    sel = "URL:" api_ssl_endpoint "/details/" uri_encode(id)
+    item(out, "h", "Web page", sel, server, port)
+    info(out, "")
+
+    print_html(out, descr)
+
+    info(out, "")
     if (length(add_date) > 0) {
-        printf "%-20s %s\n", "Date Added:", add_date >>output
+        info(out, sprintf("%-20s %s", "Date Added:", add_date))
     }
     if (pub_date != add_date) {
-        printf "%-20s %s\n", "Date Published:", pub_date >>output
+        info(out, sprintf("%-20s %s", "Date Published:", pub_date))
     }
-    printf "%-20s %s\n", "Identifier:", id >>output
+    info(out, sprintf("%-20s %s", "Identifier:", id))
     if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size >>output
+        info(out, sprintf("%-20s %d", "Item Size:", item_size))
     }
     if (length(language) > 0) {
-        printf "%-20s %s\n", "Language:", language >>output
+        info(out, sprintf("%-20s %s", "Language:", language))
     }
     if (length(license) > 0) {
-        printf "%-20s %s\n", "License:", license >>output
+        info(out, sprintf("%-20s %s", "License:", license))
     }
-    printf "%-20s %s\n", "Media Type:", type >>output
+    info(out, sprintf("%-20s %s", "Media Type:", type))
     
     if (topics > 0) {
-        print "" >>output
-        print "# Topics" >>output
+        info(out, "")
+        info(out, "# Topics")
         for (i = 1; i <= topics; i++) {
             label = shorten(topic[i], 40)
-            printf "1%s\t%s/search?subject:(%s)\t%s\t%s\n", label,
-                cgipath, topic[i], server, port >>output
+            sel = sprintf("%s/search?subject:(%s)", cgipath, topic[i])
+            item(out, "1", label, sel, server, port)
         }
     }
 
     # scrape uploader name from item web page HTML
     url = api_ssl_endpoint "/details/" item_id
@@ -190,40 +191,41 @@
         }
     }
     close(iaout)
 
     if (cols > 0) {
-        print "" >>output
-        print "# Collections" >>output
-        for (i = 1; i <= cols; i++) {
-            label = shorten(col[i], 40)
-            printf "1%s\t%s/search?collection:(%s)\t%s\t%s\n",
-                label, cgipath, col[i], server, port >>output
-        }
-    }
-
-    print "" >>output
-    print "# Uploaded by" >>output
-    if (length(uploader_account) > 0) {
-        label = shorten(uploader_account, 70)
-        printf "1%s\t%s/account?%s/%s\t%s\t%s\n", label, cgipath,
-            uploader_account, uploader_email, server, port >>output
-    } else if (length(uploader_email) > 0) {
-        label = shorten(uploader_email, 70)
-        printf "1%s\t%s/search?uploader:%s\t%s\t%s\n", label,
-            cgipath, uploader_email, server, port >>output
-    } else {
-        printf "%s\n", uploader_name >>output
-    }
-
-    print "" >>output
-    print "# Similar items" >>output
-    printf "1View similar items\t%s/search?similar:%s\t%s\t%s\n",
-        cgipath, item_id, server, port >>output
-
-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+        info(out, "")
+        info(out, "# Collections")
+        for (i = 1; i <= cols; i++) {
+            label = shorten(col[i], 40)
+            sel = sprintf("%s/search?collection:(%s)", cgipath, col[i])
+            item(out, "1", label, sel, server, port)
+        }
+    }
+
+    info(out, "")
+    info(out, "# Uploaded by")
+    if (length(uploader_account) > 0) {
+        label = shorten(uploader_account, 70)
+        sel = sprintf("%s/account?%s/%s", cgipath, uploader_account,
+            uploader_email)
+        item(out, "1", label, sel, server, port)
+    } else if (length(uploader_email) > 0) {
+        label = shorten(uploader_email, 70)
+        sel = cgipath "/search?uploader:" uploader_email
+        item(out, "1", label, sel, server, port)
+    } else {
+        info(out, uploader_name)
+    }
+
+    info(out, "")
+    info(out, "# Similar items")
+    sel = cgipath "/search?similar:" item_id
+    item(out, "1", "View similar items", sel, server, port)
+
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)
 
     cache_end()
     unlink(iaout)
     return
 }

Index: src/download.m4
==================================================================
--- src/download.m4
+++ src/download.m4
@@ -9,12 +9,15 @@
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(     cmd, files, file_size, format, iaout, is_archive,
-      is_proxy, item_server, label, mtime, name, source, url)
+      is_proxy, item_server, label, mtime, name, out, sel, source,
+      url)
 {
+    out = ""
+
     if (topdir == "download") {
         is_proxy = 1
     } else {
         # topdir == "direct"
         is_proxy = 0
@@ -64,32 +67,32 @@
             strftime("%Y-%m-%d %H:%M", mtime[i]),
             human_size(file_size[i]))
         url = sprintf("http://%s%s/%s", item_server, dir, name[i])
         if (is_proxy) {
             if (max_bin_size > 0 && file_size[i] > max_bin_size * size_mb) {
-                printf "h%s\tURL:%s\t%s\t%s\n", label, uri_encode(url),
-                    server, port
+                sel = "URL:" uri_encode(url)
+                item(out, "h", label, sel, server, port)
             } else {
-                printf "1%s\t%s/links?%s\t%s\t%s\n", label, cgipath,
-                    url, server, port
+                sel = cgipath "/links?" url
+                item(out, "1", label, sel, server, port)
             }
         } else {
-            printf "h%s\tURL:%s\t%s\t%s\n", label, uri_encode(url),
-                server, port
+            sel = "URL:" uri_encode(url)
+            item(out, "h", label, sel, server, port)
         }
         is_archive = detect_archive(url)
         if (is_archive) {
             url = sprintf("http://%s/view_archive.php?archive=%s/%s",
                 item_server, dir, name[i])
-            printf "h%s (View Contents)\tURL:%s\t%s\t%s\n",
-                shorten_left(name[i], 40), uri_encode(url),
-                server, port
+            label = shorten_left(name[i], 40) " (View Contents)"
+            sel = "URL:" uri_encode(url)
+            item(out, "h", label, sel, server, port)
         }
     }
 
-    printf "1Downloads via http\t%s/direct?%s\t%s\t%s\n", cgipath,
-        search, server, port
+    sel = cgipath "/direct?" search
+    item(out, "1", "Downloads via http", sel, server, port)
 
     unlink(iaout)
     exit 0
 }
 

Index: src/list.m4
==================================================================
--- src/list.m4
+++ src/list.m4
@@ -12,12 +12,12 @@
 incl(src/sqlite.awk)
 incl(src/util.awk)
 
 function main(     acct, client_url, cmd, count, creator, iaout, id,
     is_private, items, label, list_id, name, name_slug, numfound,
-    order, order_name, order_names, order_param, output, page, pages,
-    prefix, rows, sort_param, signature, str, title, type, url,
+    order, order_name, order_names, order_param, out, page, pages,
+    rows, sel, sort_param, signature, str, title, type, url,
     user_query)
 {
     order_names["creator"] = "creatorSorter"
     order_names["date"] = "date"
     order_names["title"] = "titleSorter"
@@ -57,11 +57,11 @@
     if (length(str) > 0) {
         print str
         return
     }
 
-    output = cache_begin()
+    out = cache_begin()
     iaout = gettemp()
 
     url = api_ssl_endpoint "/services/users/" acct "/lists/" list_id
     api_request(url, "GET", iaout)
 
@@ -131,14 +131,14 @@
         pages++
     }
 
     # format as a gopher directory (menu)
 
-    print acct "'s Lists" >>output
-    print "" >>output
-    printf "# List: %s, page %d of %d\n", name, page, pages >>output
-    print "" >>output
+    info(out, acct "'s Lists")
+    info(out, "")
+    info(out, sprintf("# List: %s, page %d of %d", name, page, pages))
+    info(out, "")
 
     cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
     FS = "\t"
     count = 0
     creator = ""
@@ -170,12 +170,12 @@
                         shorten(title, 40), shorten(creator, 18))
                 } else {
                     label = sprintf("[%s] %s", mediatype[type],
                         shorten(title, 58))
                 }
-                printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath, id,
-                    server, port >>output
+                sel = cgipath "/details?" id
+                item(out, "1", label, sel, server, port)
                 count++
             }
             creator = ""
             descr = ""
             id = ""
@@ -182,39 +182,39 @@
             type = ""
         }
     }
     close(cmd)
 
-    print "" >>output
+    info(out, "")
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
-        prefix = sprintf("/page%d/rows%d/%s", page - 1, rows, sort_param)
-        printf "1[<<] Page %d\t%s/list?%s/%d%s\t%s\t%s\n",
-            page - 1, cgipath, acct, list_id, prefix, server,
-            port >>output
+        label = sprintf("[<<] Page %d", page - 1)
+        sel = sprintf("%s/list?%s/%d/page%d/rows%d/%s", cgipath, acct,
+            list_id, page - 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
-        prefix = sprintf("/page%d/rows%d/%s", page + 1, rows, sort_param)
-        printf "1[>>] Page %d\t%s/list?%s/%d%s\t%s\t%s\n",
-            page + 1, cgipath, acct, list_id, prefix, server,
-            port >>output
+        label = sprintf("[>>] Page %d", page + 1)
+        sel = sprintf("%s/list?%s/%d/page%d/rows%d/%s", cgipath,
+            acct, list_id, page + 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
     }
 
     # only show "sort" if there's more than one item to sort
     if (numfound > 1) {
-        printf "1[^v] Sort\t%s/listsort?%s/%d\t%s\t%s\n", cgipath,
-            acct, list_id, server, port >>output
+        sel = sprintf("%s/listsort?%s/%d", cgipath, acct, list_id)
+        item(out, "1", "[^v] Sort", sel, server, port)
     }
 
-    printf "1Account %s\t%s/account?%s\t%s\t%s\n", acct, cgipath,
-        acct, server, port >>output
+    sel = cgipath "/account?" acct
+    item(out, "1", "Account " acct, sel, server, port)
  
-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)
 
     cache_end()
     unlink(iaout)
     exit 0
 }

Index: src/lists.m4
==================================================================
--- src/lists.m4
+++ src/lists.m4
@@ -10,22 +10,22 @@
 incl(src/cache.awk)
 incl(src/cgi.awk)
 incl(src/sqlite.awk)
 incl(src/util.awk)
 
-function main(     cmd, count, fields, iaout, i, id, is_private, item,
-    item_count, item_id, label, name, output, record, records,
-    signature, str, url)
+function main(     cmd, count, fields, iaout, i, id, is_private,
+    item, item_count, item_id, label, labelstr, name, out, record,
+    records, sel, signature, str, url)
 {
     signature = sprintf("%s/lists", search)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return
     }
 
-    output = cache_begin()
+    out = cache_begin()
     iaout = gettemp()
 
     url = api_ssl_endpoint "/services/users/" search "/lists"
     api_request(url, "GET", iaout)
 
@@ -45,14 +45,15 @@
 
     while ((cmd | getline) > 0) {
         if ($1 == ".value[]" && $2 == "o") {
             # add information for previous list
             if (!is_private && length(name) > 0 && item_count > 0) {
-                label = shorten_left(name, 50)
-                item = sprintf("1%4d Items: %-50s\t%s/list?%s/%d\t%s\t%s", 
-                    item_count, label, cgipath, search, id, server, port)
-                record = label "\v" id "\v" item
+                labelstr = shorten_left(name, 50)
+                label = sprintf("%4d Items: %-50s", item_count, labelstr)
+                sel = sprintf("%s/list?%s/%d", cgipath, search, id)
+                item = item_str("1", label, sel, server, port)
+                record = labelstr "\v" id "\v" item
                 count++
                 records[count] = record
             }
         } else if ($1 == ".value[].list_name" && $2 == "s") {
             name = $3
@@ -71,35 +72,36 @@
     }
     close(cmd)
 
     # add information for previous list
     if (!is_private && length(name) > 0 && item_count > 0) {
-        label = shorten_left(name, 50)
-        item = sprintf("1%4d Items: %-50s\t%s/list?%s/%d\t%s\t%s",
-            item_count, label, cgipath, search, id, server, port)
-        record = label "\v" id "\v" item
+        labelstr = shorten_left(name, 50)
+        label = sprintf("%4d Items: %-50s", item_count, labelstr)
+        sel = sprintf("%s/list?%s/%d", cgipath, search, id)
+        item = item_str("1", label, sel, server, port)
+        record = labelstr "\v" id "\v" item
         count++
         records[count] = record
     }
 
     # sort lists by label and id
     if (count > 0) {
         hsort(records, count)
     }
 
-    print search "'s Lists" >>output
-    print "" >>output
+    info(out, search "'s Lists")
+    info(out, "")
 
     for (i = 1; i <= count; i++) {
         record = records[i]
         split(record, fields, /\v/)
         item = fields[3]
-        print item >>output
+        printf "%s\r\n", item >>out
     }
 
-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)
 
     cache_end()
     unlink(iaout)
     return
 }

Index: src/listsort.m4
==================================================================
--- src/listsort.m4
+++ src/listsort.m4
@@ -5,12 +5,15 @@
 #
 # Change list sort order
 
 include(src/config.awk)
 incl(src/cgi.awk)
+incl(src/util.awk)
 
-function main(     acct, i, lbl, list_id, opt) {
+function main(     acct, i, lbl, list_id, opt, out, sel) {
+    out = ""
+
     lbl[1] = "Relevance"
     opt[1] = ""
     lbl[2] = "Weekly views [^]"
     opt[2] = "week asc"
     lbl[3] = "Weekly views [v]"
@@ -30,19 +33,19 @@
 
     split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
 
-    print "# Sort by"
-    print ""
+    info(out, "# Sort by")
     for (i = 1; i < 10; i++) {
         if (length(opt[i]) == 0) {
-            printf "1%s\t%s/list?%s/%d\t%s\t%s\n", lbl[i], cgipath,
-                acct, list_id, server, port
+            sel = sprintf("%s/list?%s/%d", cgipath, acct, list_id)
+            item(out, "1", lbl[i], sel, server, port)
         } else {
-            printf "1%s\t%s/list?%s/%d/sort%s\t%s\t%s\n", lbl[i],
-                cgipath, acct, list_id, opt[i], server, port
+            sel = sprintf("%s/list?%s/%d/sort%s", cgipath,
+                acct, list_id, opt[i])
+            item(out, "1", lbl[i], sel, server, port)
         }
     }
     exit 0
 }
 

Index: src/search.m4
==================================================================
--- src/search.m4
+++ src/search.m4
@@ -8,15 +8,17 @@
 include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
-function main(search,     cmd, count, creator, descr, field, fields, i,
-    iaout, id, item, items, jsout, label, numfound, order,
-    order_names, page, prefix, rows, searchstr, sort_param, str,
+function main(search,     cmd, count, creator, descr, field, fields,
+    i, iaout, id, item, items, jsout, label, numfound, order,
+    order_names, out, page, rows, searchstr, sel, sort_param, str,
     title, type, url)
 {
+    out = ""
+
     order_names["addeddate"] = "addeddate"
     order_names["collection_size"] = "collection_size"
     order_names["createddate"] = "createddate"
     order_names["creator"] = "creatorSorter"
     order_names["date"] = "date"
@@ -148,25 +150,25 @@
 
     if (search ~ /^@/) {
         numfound++
     }
     if (numfound == 0) {
-        print "Your search did not match any items in the Archive."
-        print "Try different keywords or a more general search."
-        print ""
-        printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port
+        info(out, "Your search did not match any items in the Archive.")
+        info(out, "Try different keywords or a more general search.")
+        info(out, "")
+        item(out, "1", "PHAROS", docpath, server, port)
         unlink(jsout)
         unlink(iaout)
         return
     } else {
         pages = int(numfound / rows)
         if (numfound % rows != 0) {
             pages++
         }
-        printf "# %s search results, page %d of %d\n", numfound,
-            page, pages
-        print ""
+        info(out, sprintf("# %s search results, page %d of %d",
+            numfound, page, pages))
+        info(out, "")
     }
 
     # format search results as a gopher directory (menu)
     FS = "\t"
     creator = ""
@@ -175,12 +177,12 @@
     title = ""
     type = ""
     count = 0
 
     if (search ~ /^@/) {
-        printf "1Account %s\t%s/account?%s\t%s\t%s\n", search, cgipath,
-           search, server, port
+        sel = cgipath "/account?" search
+        item(out, "1", "Account " search, sel, server, port)
     }
 
     while ((getline <jsout) > 0) {
         if ($1 == ".response.docs[].creator" && $2 == "s") {
             creator = $3
@@ -200,53 +202,55 @@
             } else {
                 label = sprintf("[%s] %s", mediatype[type],
                     shorten(title, 58))
             }
             if (type == "collection") {
-                printf "1%s\t%s/search?collection:(%s)\t%s\t%s\n",
-                    label, cgipath, id, server, port
+                sel = sprintf("%s/search?collection:(%s)", cgipath, id)
+                item(out, "1", label, sel, server, port)
             } else {
-                printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath,
-                   id, server, port
+                sel = cgipath "/details?" id
+                item(out, "1", label, sel, server, port)
             }
             creator = ""
             descr = ""
             id = ""
             type = ""
         }
     }
     close(jsout)
 
-    print ""
+    info(out, "")
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
-        prefix = sprintf("/page%d/rows%d/%s", page - 1, rows, sort_param)
-        printf "1[<<] Page %d\t%s/search?%s%s\t%s\t%s\n",
-            page - 1, cgipath, search, prefix, server, port
+        label = sprintf("[<<] Page %d", page - 1)
+        sel = sprintf("%s/search?%s/page%d/rows%d/%s", cgipath,
+            search, page - 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
-        prefix = sprintf("/page%d/rows%d/%s", page + 1, rows, sort_param)
-        printf "1[>>] Page %d\t%s/search?%s%s\t%s\t%s\n",
-            page + 1, cgipath, search, prefix, server, port
+        label = sprintf("1[>>] Page %d", page + 1)
+        sel = sprintf("%s/search?%s/page%d/rows%d/%s", cgipath,
+            search, page + 1, rows, sort_param)
+        item(out, "1", label, sel, server, port)
     }
 
     # only show "sort" if there's more than one item to sort
     if (count > 1) {
-        printf "1[^v] Sort\t%s/sort?%s\t%s\t%s\n", cgipath,
-            search, server, port
+        sel = cgipath "/sort?" search
+        item(out, "1", "[^v] Sort", sel, server, port)
     }
 
     # only show "search within list" if there's multiple pages of results
     if (numfound > rows) {
-        printf "1[\\/] Filter results\t%s/wizard/step1?%s\t%s\t%s\n",
-            cgipath, search, server, port
+        sel = cgipath "/wizard/step1?" search
+        item(out, "1", "[\\/] Filter results", sel, server, port)
     }
 
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port
+    item(out, "1", "PHAROS", docpath, server, port)
 
     unlink(jsout)
     unlink(iaout)
     exit 0
 }

Index: src/sort.m4
==================================================================
--- src/sort.m4
+++ src/sort.m4
@@ -42,14 +42,14 @@
     lbl[16] = "Creator [v]"
     opt[16] = "creator desc"
     lbl[17] = "Random"
     opt[17] = "random asc"
 
-    print "# Sort by"
-    print ""
+    printf "# Sort by\r\n"
+    printf "\r\n"
     for (i = 1; i < 18; i++) {
-        printf "1%s\t%s/search?%s/sort%s\t%s\t%s\n",
+        printf "1%s\t%s/search?%s/sort%s\t%s\t%s\r\n",
             lbl[i], cgipath, search, opt[i], server, port
     }
     exit 0
 }
 

Index: src/sqlite.awk
==================================================================
--- src/sqlite.awk
+++ src/sqlite.awk
@@ -7,11 +7,11 @@
     printf "%s;\n", query >>sqlcfg
     close(sqlcfg)
     cmd = sprintf("%s -batch -line -safe -init \"%s\" \"%s\" .quit 2>&1",
         cmd_sqlite, sqlcfg, db)
     while ((cmd | getline) > 0) {
-        retval = retval $0 "\n"
+        retval = retval $0 "\r\n"
     }
     close(cmd)
     unlink(sqlcfg)
     return retval
 }

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -42,15 +42,15 @@
     while ((cmd_mktemp | getline) > 0) {
         retval = $0
     }
     result = close(cmd_mktemp)
     if (result != 0) {
-        print "Error: mktemp failed exit status: " result
+        info("", "Error: mktemp failed exit status: " result)
         exit
     }
     if (length(retval) == 0) {
-        print "Error: mktemp failed, no tmpfile"
+        info("", "Error: mktemp failed, no tmpfile")
         exit
     }
     return retval
 }
 
@@ -110,30 +110,53 @@
         retval = sprintf("%dB", bytes)
     }
     return retval
 }
 
-function print_not_found(output, url) {
-    print "Item cannot be found" >>output
-    print "" >>output
-    print "Items may be taken down for various reasons," >>output
-    print "including by decision of the uploader or" >>output
-    print "due to a violation of the Terms of Use." >>output
-    print "" >>output
-    printf "hMetadata\tURL:%s\t%s\t%s\n", url, server, port >>output
-    print "" >>output
+function info(out, str) {
+    if (length(out) == 0) {
+        printf "i%s\t\t\t\r\n", str
+    } else {
+        printf "i%s\t\t\t\r\n", str >>out
+    }
+    return
+}
+
+function item(out, type, label, sel, host, port,     line) {
+    line = item_str(type, label, sel, host, port)
+    if (length(out) == 0) {
+        printf "%s\r\n", line
+    } else {
+        printf "%s\r\n", line >>out
+    }
+    return
+}
+
+function item_str(type, label, sel, host, port) {
+    retval = sprintf("%s%s\t%s\t%s\t%s", type, label, sel, host, port)
+    return retval
+}
+
+function print_not_found(out, url) {
+    info(out, "Item cannot be found")
+    info(out, "")
+    info(out, "Items may be taken down for various reasons,")
+    info(out, "including by decision of the uploader or")
+    info(out, "due to a violation of the Terms of Use.")
+    info(out, "")
+    item(out, "h", "Metadata", "URL:" url, server, port)
+    info(out, "")
     url = api_ssl_endpoint "/about/terms.php"
-    printf "0Terms of Use\t%s/text?%s\t%s\t%s\n", cgipath,
-        url, server, port >>output
-    print "" >>output
-    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+    item(out, "0", "Terms of Use", cgipath "/text?" url, server, port)
+    info(out, "")
+    item(out, "1", "PHAROS", docpath, server, port)
     return
 }
 
 function read_file(name,    retval) {
     while ((getline <name) > 0) {
-        retval = retval $0 "\n"
+        retval = retval $0 "\r\n"
     }
     close(name)
     return retval
 }
 

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -1,13 +1,16 @@
-function dump(search, type,     base, cmd, curlcfg, is_html, is_image,
-    label, limit, link, marker, parts, proto, relative, root, url)
+function dump(search, type,     base, cmd, curlcfg, is_html,
+    is_image, label, limit, link, marker, out, parts, proto,
+    relative, root, sel, url)
 {
+    out = ""
+
     url = search
     gsub(/%3F/, "?", url)
 
     if (url !~ /^(http|https):\/\/[[:alnum:].-]+(:[0-9]+)*(\/[[:alnum:].,?@~=%%:\/+&_() -]*)*$/) {
-        printf "Error: Unacceptable URL \"%s\"\r\n", url
+        info(out, sprintf("Error: Unacceptable URL \"%s\"", url))
         return
     }
 
     if (type == TYPE_HEADERS || type == TYPE_RAW) {
         limit = max_bin_size
@@ -72,35 +75,34 @@
     # line numbers larger than the marker are referenced links
 
     marker = 999999
 
     if (type == TYPE_LINKS) {
+        sel = cgipath "/raw?" search
+
         is_html = detect_html(url)
         is_image = detect_image(url)
-        printf "9Binary download\t%s/raw?%s\t%s\t%s\n", cgipath,
-            search, server, port
+        item(out, "9", "Binary download", sel, server, port)
         if (is_image) {
-            printf "IImage view\t%s/raw?%s\t%s\t%s\n", cgipath,
-                search, server, port
+            item(out, "I", "Image view", sel, server, port)
         }
         if (is_html) {
             label = "Source"
         } else {
             label = "Text view"
         }
-        printf "0%s\t%s/raw?%s\t%s\t%s\n", label, cgipath, search,
-            server, port
+        item(out, "0", label, sel, server, port)
         if (is_html) {
             label = "HTML view"
         } else {
             label = "Strings"
         }
-        printf "0%s\t%s/text?%s\t%s\t%s\n", label, cgipath,
-            search, server, port
-        printf "0Headers\t%s/debug?%s\t%s\t%s\n", cgipath,
-            search, server, port
-        print ""
+        sel = cgipath "/text?" search
+        item(out, "0", label, sel, server, port)
+        sel = cgipath "/debug?" search
+        item(out, "0", "Headers", sel, server, port)
+        info(out, "")
     }
 
     while ((cmd | getline) > 0) {
         if (NR < marker) {
             if ($0 ~ /^References$/) {
@@ -118,11 +120,13 @@
     unlink(curlcfg)
 
     return
 }
 
-function print_html(output, html,    cmd, marker, work) {
+function print_html(out, html,    cmd, marker, out, work) {
+    out = ""
+
     work = gettemp()
     gsub(/\\n/, "<br>", html)
     print html >work
     close(work)
     cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work,
@@ -133,13 +137,13 @@
         gsub(/\\t/, "        ")
         if (NR < marker) {
             if ($0 ~ /^References$/) {
                 marker = NR
             }
-            print >>output
+            info(out, $0)
         } else {
-            print_ref_pharos(output, $0)
+            print_ref_pharos(out, $0)
         }
     }
     close(cmd)
     unlink(work)
     return
@@ -146,11 +150,15 @@
 }
 
 # Print the webdump references section, converting relative URLs
 # to full URLs
 
-function print_ref_full(str, base, proto, root,     link, prefix, relative) {
+function print_ref_full(str, base, proto, root,     link, out,
+    prefix, relative)
+{
+    out = ""
+
     if (match(str, /^ [0-9]+\. /)) {
         prefix = substr(str, 0, RLENGTH)
         link = substr(str, RLENGTH + 1)
         # convert relative links to full URLs
         if (link !~ /^[a-z]+:/) {
@@ -162,22 +170,22 @@
                 link = root relative
             } else {
                 link = base "/" relative
             }
         }
-        print prefix link
+        info(out, prefix link)
     } else {
-        print str
+        info(out, str)
     }
     return
 }
 
 
 # Print the webdump references section, translating archive.org URLs to
 # pharos URLs
 
-function print_ref_pharos(output, str,     id, label, link, prefix,
+function print_ref_pharos(out, str,     id, label, link, prefix,
     relative, token)
 {
     if (match(str, /^ [0-9]+\. /)) {
         prefix = substr(str, 0, RLENGTH)
         link = substr(str, RLENGTH + 1)
@@ -190,17 +198,16 @@
                 id = substr(id, 1, RSTART - 1)
             }
         }
         if (length(id) > 0) {
             label = prefix id
-            printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath,
-                id, server, port >>output
+            item(out, "1", label, cgipath "/details?" id, server, port)
         } else {
-             print str >>output
+            info(out, str)
         }
     } else {
-        print str >>output
+        info(out, str)
     }
     return
 }
 
 function web_init() {

Index: src/wizard/step1.m4
==================================================================
--- src/wizard/step1.m4
+++ src/wizard/step1.m4
@@ -5,12 +5,15 @@
 #
 # Select field to filter/search by
 
 include(src/config.awk)
 incl(src/cgi.awk)
+incl(src/util.awk)
 
-function main(     i, lbl, opt) {
+function main(     i, lbl, opt, out, sel) {
+    out = ""
+
     lbl[1]  = "Any field contains"
     opt[1]  = "anyfield"
     lbl[2]  = "Any field does not contain"
     opt[2]  = "-anyfield"
     lbl[3]  = "Title contains"
@@ -38,22 +41,22 @@
     lbl[14] = "Language contains"
     opt[14] = "language"
     lbl[15] = "Always available"
     opt[15] = "-access-restricted-item"
 
-    print "# Search wizard: Select field"
-    print ""
+    info(out, "# Search wizard: Select field")
+    info(out, "")
     for (i = 1; i < 16; i++) {
         if (opt[i] ~ /mediatype$/) {
-            printf "1%s\t%s/wizard/step2?%s/%s\t%s\t%]\n",
-                lbl[i], cgipath, search, opt[i], server, port
+            sel = sprintf("%s/wizard/step2?%s/%s", cgipath, search, opt[i])
+            item(out, "1", lbl[i], sel, server, port)
         } else if (lbl[i] == "Always available") {
-            printf "1%s\t%s/wizard/step3?%s/%s/true\t%s\t%s\n",
-                lbl[i], cgipath, search, opt[i], server, port
+            sel = sprintf("%s/wizard/step3?%s/%s", cgipath, search, opt[i])
+            item(out, "1", lbl[i], sel, server, port)
         } else {
-            printf "7%s\t%s/wizard/step3?%s/%s\t%s\t%s\n",
-                lbl[i], cgipath, search, opt[i], server, port
+            sel = sprintf("%s/wizard/step3?%s/%s", cgipath, search, opt[i])
+            item(out, "7", lbl[i], sel, server, port)
         }
     }
     exit 0
 }
 

Index: src/wizard/step2.m4
==================================================================
--- src/wizard/step2.m4
+++ src/wizard/step2.m4
@@ -5,21 +5,25 @@
 #
 # Select mediatype to filter/search by
 
 include(src/config.awk)
 incl(src/cgi.awk)
+incl(src/util.awk)
+
+function main(     field, newsearch, out, searchstr, sel) {
+    out = ""
 
-function main(     field, newsearch, searchstr) {
-    field = parts[4]
-    searchstr = parts[5]
+    split(search, parts, "/")
+    searchstr = parts[1]
+    field = parts[2]
 
     if (field == "mediatype") {
-        print "# Mediatype is:"
+        info(out, "# Mediatype is:")
     } else {
-        print "# Mediatype does not contain:"
+        info(out, "# Mediatype does not contain:")
     }
-    print ""
+    info(out, "")
 
     lbl[1] = "audio"
     lbl[2] = "collection"
     lbl[3] = "data"
     lbl[4] = "etree"
@@ -32,20 +36,20 @@
         if (length(searchstr) == 0) {
             newsearch = sprintf("%s:(%s)", field, lbl[i])
         } else {
             newsearch = sprintf("%s AND %s:(%s)", searchstr, field, lbl[i])
         }
-        printf "1%s\t%s/search?%s\t%s\t%s\n", lbl[i], cgipath,
-            newsearch, server, port
+        sel = cgipath "/search?" newsearch
+        item(out, "1", lbl[i], sel, server, port)
     }
-    print "# Progress:"
-    print ""
-    print "* Field: Mediatype"
+    info(out, "# Progress:")
+    info(out, "")
+    info(out, "* Field: Mediatype")
     exit 0
 }
 
 BEGIN {
     config_init()
 
     cgi_init()
     main()
 }

Index: src/wizard/step3.m4
==================================================================
--- src/wizard/step3.m4
+++ src/wizard/step3.m4
@@ -5,12 +5,17 @@
 #
 # Apply new search terms
 
 include(src/config.awk)
 incl(src/cgi.awk)
+incl(src/util.awk)
 
-function main(     field, label, newsearch, op, searchstr, value) {
+function main(     field, label, newsearch, op, out, searchstr,
+    sel, value)
+{
+    out = ""
+
     split(search, parts, "/")
     searchstr = parts[1]
     field = parts[2]
     value = searchreq
 
@@ -24,22 +29,22 @@
     if (length(searchstr) == 0) {
         newsearch = sprintf("%s:(%s)", field, value)
     } else {
         newsearch = sprintf("%s AND %s:(%s)", searchstr, field, value)
     }
-    print ""
-    printf "1Apply search criteria\t%s/search?%s\t%s\t%s\n",
-        cgipath, newsearch, server, port
-    print ""
-    print "# Progress:"
-    print ""
-    printf "* Field %s %s %s\n", label, op, value
-    printf "* New search: %s\n", newsearch
+    info(out, "")
+    sel = cgipath "/search?" newsearch
+    item(out, "1", "Apply search criteria", sel, server, port)
+    info(out, "")
+    info(out, "# Progress:")
+    info(out, "")
+    info(out, sprintf("* Field %s %s %s", label, op, value))
+    info(out, "* New search: " newsearch)
     exit 0
 }
 
 BEGIN {
     config_init()
 
     cgi_init()
     main()
 }

]]></content>
<updated>2025-11-09 08:04:54</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/d227c24798/</id>
<title>Some items have an uploader name with...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 05:17:04

  Some items have an uploader name with no id nor email.  In that
  case, show a name without any search for other items by the same
  uploader.

  INSERTED    DELETED
        13          6 src/details.m4
        13          6 TOTAL over 1 changed file

Index: src/details.m4
==================================================================
--- src/details.m4
+++ src/details.m4
@@ -14,11 +14,12 @@
 incl(src/web.awk)
 
 function main(     add_date, col, cols, cmd, creator, descr, dir, i,
     iaout, id, item_id, item_server, item_size, label, language,
     license, output, pub_date, scanner, signature, str, thumb,
-    title, topic, topics, type, uploader_account, uploader_email, url)
+    title, topic, topics, type, uploader_account, uploader_email,
+    uploader_name, url)
 {
     item_id = search
 
     signature = "details/" item_id
     str = cache_init(signature)
@@ -55,10 +56,12 @@
     topics = 0
     delete topic
     type = ""
     uploader_account = ""
     uploader_email = ""
+    uploader_name = "Unknown"
+
     while ((cmd | getline) > 0) {
         if ($1 == ".dir" && $2 == "s") {
             dir = $3
         } else if ($1 == ".files[].name" && $2 == "s") {
             if ($3 == "__ia_thumb.jpg") {
@@ -176,14 +179,16 @@
 
     # scrape uploader name from item web page HTML
     url = api_ssl_endpoint "/details/" item_id
     api_request(url, "GET", iaout)
     while ((getline <iaout) > 0) {
-        if (/item-upload-info__uploader-name/ &&
-            match($0, /\/details\/[^"]*"/))
-        {
-            uploader_account = substr($0, RSTART + 9, RLENGTH - 10)
+        if (match($0, /item-upload-info__uploader-name/)) {
+            if (match($0, /\/details\/[^"]*"/)) {
+                uploader_account = substr($0, RSTART + 9, RLENGTH - 10)
+            } else if (match($0, />[^<]*<\/span>/)) {
+                uploader_name = substr($0, RSTART + 1, RLENGTH - 8)
+            }
         }
     }
     close(iaout)
 
     if (cols > 0) {
@@ -200,14 +205,16 @@
     print "# Uploaded by" >>output
     if (length(uploader_account) > 0) {
         label = shorten(uploader_account, 70)
         printf "1%s\t%s/account?%s/%s\t%s\t%s\n", label, cgipath,
             uploader_account, uploader_email, server, port >>output
-    } else {
+    } else if (length(uploader_email) > 0) {
         label = shorten(uploader_email, 70)
         printf "1%s\t%s/search?uploader:%s\t%s\t%s\n", label,
             cgipath, uploader_email, server, port >>output
+    } else {
+        printf "%s\n", uploader_name >>output
     }
 
     print "" >>output
     print "# Similar items" >>output
     printf "1View similar items\t%s/search?similar:%s\t%s\t%s\n",

]]></content>
<updated>2025-11-09 05:17:04</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/bb37a3f41a/</id>
<title>Update audio, books, images, software...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 01:49:45

  Update audio, books, images, software, and video gophermaps from
  geomyidae to gophernicus format.

  INSERTED    DELETED
        20         20 src/audio/gophermap.m4
        21         21 src/books/gophermap.m4
        13         13 src/images/gophermap.m4
        28         27 src/software/gophermap.m4
        22         22 src/video/gophermap.m4
       104        103 TOTAL over 5 changed files

Index: src/audio/gophermap.m4
==================================================================
--- src/audio/gophermap.m4
+++ src/audio/gophermap.m4
@@ -1,21 +1,21 @@
 include(config.m4)dnl
-# Audio
-
-[1|All Audio|__CGIPATH__/search/sortweek desc/%09mediatype:audio|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:audio|__SERVER__|__PORT__]
-[1|Live Music Archive|__CGIPATH__/search/sortweek desc/%09collection:etree AND mediatype:collection|__SERVER__|__PORT__]
-[1|Librivox Free Audiobooks|__CGIPATH__/search/sortweek desc/%09collection:librivoxaudio|__SERVER__|__PORT__]
-[1|Grateful Dead|__CGIPATH__/search/sortweek desc/%09collection:GratefulDead|__SERVER__|__PORT__]
-[1|Netlabels|__CGIPATH__/search/sortweek desc/%09collection:netlabels AND mediatype:collection|__SERVER__|__PORT__]
-[1|Old Time Radio|__CGIPATH__/search/sortweek desc/%09collection:oldtimeradio|__SERVER__|__PORT__]
-[1|78 RPMs & Cylinder Recordings|__CGIPATH__/search/sortweek desc/%09collection:78rpm AND mediatype:collection|__SERVER__|__PORT__]
-[1|Audio Books & Poetry|__CGIPATH__/search/sortweek desc/%09collection:audio_bookspoetry|__SERVER__|__PORT__]
-[1|Computers, Technology & Science|__CGIPATH__/search/sortweek desc/%09collection:audio_tech|__SERVER__|__PORT__]
-[1|Music, Arts & Culture|__CGIPATH__/search/sortweek desc/%09collection:audio_music AND mediatype:collection|__SERVER__|__PORT__]
-[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:audio_news|__SERVER__|__PORT__]
-[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:audio_religion|__SERVER__|__PORT__]
-[1|Podcasts|__CGIPATH__/search/sortweek desc/%09collection:podcasts|__SERVER__|__PORT__]
-[1|Radio News Archive|__CGIPATH__/search/sortweek desc/%09collection:radio|__SERVER__|__PORT__]
-[1|Long Playing Records|__CGIPATH__/search/sortweek desc/%09collection:album_recordings|__SERVER__|__PORT__]
-[1|Various Cassette Tapes|__CGIPATH__/search/sortweek desc/%09collection:cassettetapes|__SERVER__|__PORT__]
-[1|Audiophile CD Collection|__CGIPATH__/search/sortweek desc/%09collection:acdc AND -collection:samples_only|__SERVER__|__PORT__]
+i# Audio			
+i			
+1All Audio	__CGIPATH__/search?mediatype:audio	__SERVER__	__PORT__
+1This Just In	__CGIPATH__/search?mediatype:audio/sortaddeddate desc	__SERVER__	__PORT__
+1Live Music Archive	__CGIPATH__/search?collection:etree AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Librivox Free Audiobooks	__CGIPATH__/search?collection:librivoxaudio/sortweek desc	__SERVER__	__PORT__
+1Grateful Dead	__CGIPATH__/search?collection:GratefulDead/sortweek desc	__SERVER__	__PORT__
+1Netlabels	__CGIPATH__/search?collection:netlabels AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Old Time Radio	__CGIPATH__/search?collection:oldtimeradio/sortweek desc	__SERVER__	__PORT__
+178 RPMs & Cylinder Recordings	__CGIPATH__/search?collection:78rpm AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Audio Books & Poetry	__CGIPATH__/search?collection:audio_bookspoetry/sortweek desc	__SERVER__	__PORT__
+1Computers, Technology & Science	__CGIPATH__/search?collection:audio_tech/sortweek desc	__SERVER__	__PORT__
+1Music, Arts & Culture	__CGIPATH__/search?collection:audio_music AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1News & Public Affairs	__CGIPATH__/search?collection:audio_news/sortweek desc	__SERVER__	__PORT__
+1Spirituality & Religion	__CGIPATH__/search?collection:audio_religion/sortweek desc	__SERVER__	__PORT__
+1Podcasts	__CGIPATH__/search?collection:podcasts/sortweek desc	__SERVER__	__PORT__
+1Radio News Archive	__CGIPATH__/search?collection:radio/sortweek desc	__SERVER__	__PORT__
+1Long Playing Records	__CGIPATH__/search?collection:album_recordings/sortweek desc	__SERVER__	__PORT__
+1Various Cassette Tapes	__CGIPATH__/search?collection:cassettetapes/sortweek desc	__SERVER__	__PORT__
+1Audiophile CD Collection	__CGIPATH__/search?collection:acdc AND -collection:samples_only/sortweek desc	__SERVER__	__PORT__

Index: src/books/gophermap.m4
==================================================================
--- src/books/gophermap.m4
+++ src/books/gophermap.m4
@@ -1,22 +1,22 @@
 include(config.m4)dnl
-# Books
-
-[1|All Texts|__CGIPATH__/search/sortweek desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Smithsonian Libraries|__CGIPATH__/search/sortweek desc/%09collection:smithsonian|__SERVER__|__PORT__]
-[1|FEDLINK|__CGIPATH__/search/sortweek desc/%09collection:fedlink AND mediatype:collection|__SERVER__|__PORT__]
-[1|Genealogy|__CGIPATH__/search/sortweek desc/%09collection:genealogy AND mediatype:collection|__SERVER__|__PORT__]
-[1|Lincoln Collection|__CGIPATH__/search/sortweek desc/%09collection:lincolncollection|__SERVER__|__PORT__]
-[1|American Libraries|__CGIPATH__/search/sortweek desc/%09collection:americana AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Canadian Libraries|__CGIPATH__/search/sortweek desc/%09collection:toronto AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Universal Library|__CGIPATH__/search/sortweek desc/%09collection:universallibrary|__SERVER__|__PORT__]
-[1|Project Gutenberg|__CGIPATH__/search/sortweek desc/%09collection:gutenberg|__SERVER__|__PORT__]
-[1|Children's Library|__CGIPATH__/search/sortweek desc/%09collection:iacl AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Biodiversity Heritage Library|__CGIPATH__/search/sortweek desc/%09collection:biodiversity AND mediatype:collection|__SERVER__|__PORT__]
-[1|Books By Language|__CGIPATH__/search/sortweek desc/%09collection:booksbylanguage AND mediatype:collection|__SERVER__|__PORT__]
-[1|Additional Collections|__CGIPATH__/search/sortweek desc/%09collection:additional_collections AND mediatype:collection|__SERVER__|__PORT__]
-[1|The Magazine Rack|__CGIPATH__/search/sortweek desc/%09collection:magazine_rack AND mediatype:collection|__SERVER__|__PORT__]
-[1|The Pulp Magazine Archive|__CGIPATH__/search/sortweek desc/%09collection:pulpmagazinearchive|__SERVER__|__PORT__]
-[1|Newspapers|__CGIPATH__/search/sortweek desc/%09collection:newspapers AND mediatype:collection|__SERVER__|__PORT__]
-[1|Comic Books & Graphic Novels|__CGIPATH__/search/sortweek desc/%09collection:comics AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Zines|__CGIPATH__/search/sortweek desc/%09collection:zines AND -access-restricted-item:true|__SERVER__|__PORT__]
+i# Books			
+i			
+1All Texts	__CGIPATH__/search?mediatype:texts AND -access-restricted-item:true/sortweek desc	__SERVER__	__PORT__
+1This Just In	__CGIPATH__/search?mediatype:texts AND -access-restricted-item:true/sortaddeddate desc	__SERVER__	__PORT__
+1Smithsonian Libraries	__CGIPATH__/search?collection:smithsonian/sortweek desc	__SERVER__	__PORT__
+1FEDLINK	__CGIPATH__/search?collection:fedlink AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Genealogy	__CGIPATH__/search?collection:genealogy AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Lincoln Collection	__CGIPATH__/search?collection:lincolncollection/sortweek desc	__SERVER__	__PORT__
+1American Libraries	__CGIPATH__/search?collection:americana AND mediatype:collection AND -access-restricted-item:true/sortweek desc	__SERVER__	__PORT__
+1Canadian Libraries	__CGIPATH__/search?collection:toronto AND mediatype:collection AND -access-restricted-item:true/sortweek desc	__SERVER__	__PORT__
+1Universal Library	__CGIPATH__/search?collection:universallibrary/sortweek desc	__SERVER__	__PORT__
+1Project Gutenberg	__CGIPATH__/search?collection:gutenberg/sortweek desc	__SERVER__	__PORT__
+1Children's Library	__CGIPATH__/search?collection:iacl AND -access-restricted-item:true/sortweek desc	__SERVER__	__PORT__
+1Biodiversity Heritage Library	__CGIPATH__/search?collection:biodiversity AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Books By Language	__CGIPATH__/search?collection:booksbylanguage AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Additional Collections	__CGIPATH__/search?collection:additional_collections AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1The Magazine Rack	__CGIPATH__/search?collection:magazine_rack AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1The Pulp Magazine Archive	__CGIPATH__/search?collection:pulpmagazinearchive/sortweek desc	__SERVER__	__PORT__
+1Newspapers	__CGIPATH__/search?collection:newspapers AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Comic Books & Graphic Novels	__CGIPATH__/search?collection:comics AND -access-restricted-item:true/sortweek desc	__SERVER__	__PORT__
+1Zines	__CGIPATH__/search?collection:zines AND -access-restricted-item:true/sortweek desc	__SERVER__	__PORT__

Index: src/images/gophermap.m4
==================================================================
--- src/images/gophermap.m4
+++ src/images/gophermap.m4
@@ -1,14 +1,14 @@
 include(config.m4)dnl
-# Images
-
-[1|All Images|__CGIPATH__/search/sortweek desc/%09mediatype:image|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:image|__SERVER__|__PORT__]
-[1|Metropolitan Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:metropolitanmuseumofart-gallery|__SERVER__|__PORT__]
-[1|Cleveland Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:clevelandart|__SERVER__|__PORT__]
-[1|Flickr Commons|__CGIPATH__/search/sortweek desc/%09collection:flickrcommons|__SERVER__|__PORT__]
-[1|Occupy Wallstreet Flickr|__CGIPATH__/search/sortweek desc/%09collection:flickr-ows|__SERVER__|__PORT__]
-[1|Cover Art|__CGIPATH__/search/sortweek desc/%09collection:coverartarchive|__SERVER__|__PORT__]
-[1|USGS Maps|__CGIPATH__/search/sortweek desc/%09collection:maps_usgs|__SERVER__|__PORT__]
-[1|NASA Images|__CGIPATH__/search/sortweek desc/%09collection:nasa|__SERVER__|__PORT__]
-[1|Solar System Collection|__CGIPATH__/search/sortweek desc/%09collection:solarsystemcollection|__SERVER__|__PORT__]
-[1|Ames Research Center|__CGIPATH__/search/sortweek desc/%09collection:amesresearchcenterimagelibrary|__SERVER__|__PORT__]
+i# Images			
+i			
+1All Images	__CGIPATH__/search?mediatype:image/sortweek desc	__SERVER__	__PORT__
+1This Just In	__CGIPATH__/search?mediatype:image/sortaddeddate desc	__SERVER__	__PORT__
+1Metropolitan Museum of Art	__CGIPATH__/search?collection:metropolitanmuseumofart-gallery/sortweek desc	__SERVER__	__PORT__
+1Cleveland Museum of Art	__CGIPATH__/search?collection:clevelandart/sortweek desc	__SERVER__	__PORT__
+1Flickr Commons	__CGIPATH__/search?collection:flickrcommons/sortweek desc	__SERVER__	__PORT__
+1Occupy Wallstreet Flickr	__CGIPATH__/search?collection:flickr-ows/sortweek desc	__SERVER__	__PORT__
+1Cover Art	__CGIPATH__/search?collection:coverartarchive/sortweek desc	__SERVER__	__PORT__
+1USGS Maps	__CGIPATH__/search?collection:maps_usgs/sortweek desc	__SERVER__	__PORT__
+1NASA Images	__CGIPATH__/search?collection:nasa/sortweek desc	__SERVER__	__PORT__
+1Solar System Collection	__CGIPATH__/search?collection:solarsystemcollection/sortweek desc	__SERVER__	__PORT__
+1Ames Research Center	__CGIPATH__/search?collection:amesresearchcenterimagelibrary/sortweek desc	__SERVER__	__PORT__

Index: src/software/gophermap.m4
==================================================================
--- src/software/gophermap.m4
+++ src/software/gophermap.m4
@@ -1,28 +1,29 @@
 include(config.m4)dnl
-
-[1|All Software|__CGIPATH__/search/sortweek desc/%09mediatype:software|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:software|__SERVER__|__PORT__]
-[1|Internet Arcade|__CGIPATH__/search/sortweek desc/%09collection:internetarcade|__SERVER__|__PORT__]
-[1|Console Living Room|__CGIPATH__/search/sortweek desc/%09collection:consolelivingroom|__SERVER__|__PORT__]
-[1|Old School Emulation|__CGIPATH__/search/sortweek desc/%09collection:tosec|__SERVER__|__PORT__]
-[1|MS-DOS Games|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_games|__SERVER__|__PORT__]
-[1|Historical Software|__CGIPATH__/search/sortweek desc/%09collection:historicalsoftware|__SERVER__|__PORT__]
-[1|Classic PC Games|__CGIPATH__/search/sortweek desc/%09collection:classicpcgames|__SERVER__|__PORT__]
-[1|Software Library|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary AND mediatype:collection|__SERVER__|__PORT__]
-[1|Kodi Archive & Support Files|__CGIPATH__/search/sortweek desc/%09collection:kodi_archive|__SERVER__|__PORT__]
-[1|Vintage Software|__CGIPATH__/search/sortweek desc/%09collection:vintagesoftware|__SERVER__|__PORT__]
-[1|APK|__CGIPATH__/search/sortweek desc/%09collection:apkarchive|__SERVER__|__PORT__]
-[1|MS-DOS|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos|__SERVER__|__PORT__]
-[1|CD-ROM Software|__CGIPATH__/search/sortweek desc/%09collection:cd-roms|__SERVER__|__PORT__]
-[1|CD-ROM Software Library|__CGIPATH__/search/sortweek desc/%09collection:cdromsoftware|__SERVER__|__PORT__]
-[1|Software Sites|__CGIPATH__/search/sortweek desc/%09collection:softwaresites|__SERVER__|__PORT__]
-[1|Tucows Software Library|__CGIPATH__/search/sortweek desc/%09collection:tucows|__SERVER__|__PORT__]
-[1|Shareware CD-ROMs|__CGIPATH__/search/sortweek desc/%09collection:cdbbsarchive|__SERVER__|__PORT__]
-[1|Software Capsules Compilation|__CGIPATH__/search/sortweek desc/%09collection:softwarecapsules|__SERVER__|__PORT__]
-[1|CD-ROM Images|__CGIPATH__/search/sortweek desc/%09collection:cdromimages|__SERVER__|__PORT__]
-[1|Underground CD-ROM CompilationsLibrary|__CGIPATH__/search/sortweek desc/%09collection:undergroundcds|__SERVER__|__PORT__]
-[1|ZX Spectrum|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_zx_spectrum|__SERVER__|__PORT__]
-[1|DOOM Level CD|__CGIPATH__/search/sortweek desc/%09collection:doom-cds|__SERVER__|__PORT__]
-[1|Floppy Disks of Software|__CGIPATH__/search/sortweek desc/%09collection:floppysoftware|__SERVER__|__PORT__]
-[1|The Good Old Days IBM PC Floppy Collection|__CGIPATH__/search/sortweek desc/%09collection:TGODFloppyCollection|__SERVER__|__PORT__]
-[1|MS-DOS: The Frostbyte Shareware Collection|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_frostbyte|__SERVER__|__PORT__]
+i# Software
+i			
+1All Software	__CGIPATH__/search?mediatype:software	__SERVER__	__PORT__
+1This Just In	__CGIPATH__/search?mediatype:software/sortaddeddate desc	__SERVER__	__PORT__
+1Internet Arcade	__CGIPATH__/search?collection:internetarcade/sortweek desc	__SERVER__	__PORT__
+1Console Living Room	__CGIPATH__/search?collection:consolelivingroom/sortweek desc	__SERVER__	__PORT__
+1Old School Emulation	__CGIPATH__/search?collection:tosec/sortweek desc	__SERVER__	__PORT__
+1MS-DOS Games	__CGIPATH__/search?collection:softwarelibrary_msdos_games/sortweek desc	__SERVER__	__PORT__
+1Historical Software	__CGIPATH__/search?collection:historicalsoftware/sortweek desc	__SERVER__	__PORT__
+1Classic PC Games	__CGIPATH__/search?collection:classicpcgames/sortweek desc	__SERVER__	__PORT__
+1Software Library	__CGIPATH__/search?collection:softwarelibrary AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Kodi Archive & Support Files	__CGIPATH__/search?collection:kodi_archive/sortweek desc	__SERVER__	__PORT__
+1Vintage Software	__CGIPATH__/search?collection:vintagesoftware/sortweek desc	__SERVER__	__PORT__
+1APK	__CGIPATH__/search?collection:apkarchive/sortweek desc	__SERVER__	__PORT__
+1MS-DOS	__CGIPATH__/search?collection:softwarelibrary_msdos/sortweek desc	__SERVER__	__PORT__
+1CD-ROM Software	__CGIPATH__/search?collection:cd-roms/sortweek desc	__SERVER__	__PORT__
+1CD-ROM Software Library	__CGIPATH__/search?collection:cdromsoftware/sortweek desc	__SERVER__	__PORT__
+1Software Sites	__CGIPATH__/search?collection:softwaresites/sortweek desc	__SERVER__	__PORT__
+1Tucows Software Library	__CGIPATH__/search?collection:tucows/sortweek desc	__SERVER__	__PORT__
+1Shareware CD-ROMs	__CGIPATH__/search?collection:cdbbsarchive/sortweek desc	__SERVER__	__PORT__
+1Software Capsules Compilation	__CGIPATH__/search?collection:softwarecapsules/sortweek desc	__SERVER__	__PORT__
+1CD-ROM Images	__CGIPATH__/search?collection:cdromimages/sortweek desc	__SERVER__	__PORT__
+1Underground CD-ROM CompilationsLibrary	__CGIPATH__/search?collection:undergroundcds/sortweek desc	__SERVER__	__PORT__
+1ZX Spectrum	__CGIPATH__/search?collection:softwarelibrary_zx_spectrum/sortweek desc	__SERVER__	__PORT__
+1DOOM Level CD	__CGIPATH__/search?collection:doom-cds/sortweek desc	__SERVER__	__PORT__
+1Floppy Disks of Software	__CGIPATH__/search?collection:floppysoftware/sortweek desc	__SERVER__	__PORT__
+1The Good Old Days IBM PC Floppy Collection	__CGIPATH__/search?collection:TGODFloppyCollection/sortweek desc	__SERVER__	__PORT__
+1MS-DOS: The Frostbyte Shareware Collection	__CGIPATH__/search?collection:softwarelibrary_msdos_frostbyte/sortweek desc	__SERVER__	__PORT__

Index: src/video/gophermap.m4
==================================================================
--- src/video/gophermap.m4
+++ src/video/gophermap.m4
@@ -1,23 +1,23 @@
 include(config.m4)dnl
-# Video
-
-[1|All Video|__CGIPATH__/search/sortweek desc/%09mediatype:movies|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:movies|__SERVER__|__PORT__]
-[1|Prelinger Archives|__CGIPATH__/search/sortweek desc/%09collection:prelinger|__SERVER__|__PORT__]
-[1|Democracy Now!|__CGIPATH__/search/sortweek desc/%09collection:democracy_now_vid|__SERVER__|__PORT__]
-[1|Occupy Wall Street|__CGIPATH__/search/sortweek desc/%09collection:occupywallstreet|__SERVER__|__PORT__]
-[1|TV NSA Clip Library|__CGIPATH__/search/sortweek desc/%09collection:nsa|__SERVER__|__PORT__]
-[1|Animation And Cartoons|__CGIPATH__/search/sortweek desc/%09collection:animationandcartoons|__SERVER__|__PORT__]
-[1|Arts & Music|__CGIPATH__/search/sortweek desc/%09collection:artsandmusicvideos|__SERVER__|__PORT__]
-[1|Computers & Technology|__CGIPATH__/search/sortweek desc/%09collection:computersandtechvideos|__SERVER__|__PORT__]
-[1|Cultural & Academic Films|__CGIPATH__/search/sortweek desc/%09collection:culturalandacademicfilms|__SERVER__|__PORT__]
-[1|Ephemeral Films|__CGIPATH__/search/sortweek desc/%09collection:ephemera|__SERVER__|__PORT__]
-[1|Movies|__CGIPATH__/search/sortweek desc/%09collection:moviesandfilms|__SERVER__|__PORT__]
-[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:newsandpublicaffairs|__SERVER__|__PORT__]
-[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:spiritualityandreligion|__SERVER__|__PORT__]
-[1|Sports|__CGIPATH__/search/sortweek desc/%09collection:sports|__SERVER__|__PORT__]
-[1|Television|__CGIPATH__/search/sortweek desc/%09collection:television AND mediatype:collection|__SERVER__|__PORT__]
-[1|VHS Vault|__CGIPATH__/search/sortweek desc/%09collection:vhsvault|__SERVER__|__PORT__]
-[1|Video Games|__CGIPATH__/search/sortweek desc/%09collection:gamevideos AND mediatype:collection|__SERVER__|__PORT__]
-[1|Vlogs|__CGIPATH__/search/sortweek desc/%09collection:vlogs|__SERVER__|__PORT__]
-[1|Youth Media|__CGIPATH__/search/sortweek desc/%09collection:youth_media|__SERVER__|__PORT__]
+i# Video			
+i			
+1All Video	__CGIPATH__/search?mediatype:movies/sortweek desc	__SERVER__	__PORT__
+1This Just In	__CGIPATH__/search?mediatype:movies/sortaddeddate desc	__SERVER__	__PORT__
+1Prelinger Archives	__CGIPATH__/search?collection:prelinger/sortweek desc	__SERVER__	__PORT__
+1Democracy Now!	__CGIPATH__/search?collection:democracy_now_vid/sortweek desc	__SERVER__	__PORT__
+1Occupy Wall Street	__CGIPATH__/search?collection:occupywallstreet/sortweek desc	__SERVER__	__PORT__
+1TV NSA Clip Library	__CGIPATH__/search?collection:nsa/sortweek desc	__SERVER__	__PORT__
+1Animation And Cartoons	__CGIPATH__/search?collection:animationandcartoons/sortweek desc	__SERVER__	__PORT__
+1Arts & Music	__CGIPATH__/search?collection:artsandmusicvideos/sortweek desc	__SERVER__	__PORT__
+1Computers & Technology	__CGIPATH__/search?collection:computersandtechvideos/sortweek desc	__SERVER__	__PORT__
+1Cultural & Academic Films	__CGIPATH__/search?collection:culturalandacademicfilms/sortweek desc	__SERVER__	__PORT__
+1Ephemeral Films	__CGIPATH__/search?collection:ephemera/sortweek desc	__SERVER__	__PORT__
+1Movies	__CGIPATH__/search?collection:moviesandfilms/sortweek desc	__SERVER__	__PORT__
+1News & Public Affairs	__CGIPATH__/search?collection:newsandpublicaffairs/sortweek desc	__SERVER__	__PORT__
+1Spirituality & Religion	__CGIPATH__/search?collection:spiritualityandreligion/sortweek desc	__SERVER__	__PORT__
+1Sports	__CGIPATH__/search?collection:sports/sortweek desc	__SERVER__	__PORT__
+1Television	__CGIPATH__/search?collection:television AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1VHS Vault	__CGIPATH__/search?collection:vhsvault/sortweek desc	__SERVER__	__PORT__
+1Video Games	__CGIPATH__/search?collection:gamevideos AND mediatype:collection/sortweek desc	__SERVER__	__PORT__
+1Vlogs	__CGIPATH__/search?collection:vlogs/sortweek desc	__SERVER__	__PORT__
+1Youth Media	__CGIPATH__/search?collection:youth_media/sortweek desc	__SERVER__	__PORT__

]]></content>
<updated>2025-11-09 01:49:45</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/115c28f054/</id>
<title>Fix empty-dirs setting to avoid colli...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 01:45:28

Fix empty-dirs setting to avoid collisions with make.sh

  INSERTED    DELETED
         1          1 .fossil-settings/empty-dirs
         1          1 TOTAL over 1 changed file

Index: .fossil-settings/empty-dirs
==================================================================
--- .fossil-settings/empty-dirs
+++ .fossil-settings/empty-dirs
@@ -1,1 +1,1 @@
-cgi/wizard/step1,cgi/wizard/step2,cgi/wizard/step3,gopher/audio,gopher/books,gopher/images,gopher/software,gopher/video
+cgi/wizard,gopher/audio,gopher/books,gopher/images,gopher/software,gopher/video

]]></content>
<updated>2025-11-09 01:45:28</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/3a0e244b15/</id>
<title>Configure empty-dirs setting</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 01:43:04

Configure empty-dirs setting

  INSERTED    DELETED
         1          0 .fossil-settings/empty-dirs
         1          0 TOTAL over 1 changed file

ADDED   .fossil-settings/empty-dirs
Index: .fossil-settings/empty-dirs
==================================================================
--- /dev/null
+++ .fossil-settings/empty-dirs
@@ -0,0 +1,1 @@
+cgi/wizard/step1,cgi/wizard/step2,cgi/wizard/step3,gopher/audio,gopher/books,gopher/images,gopher/software,gopher/video

]]></content>
<updated>2025-11-09 01:43:04</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/093031141d/</id>
<title>Remove unnecessary arguments from bui...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 01:30:25

  Remove unnecessary arguments from build_cgi() and
  build_gophermap()

  INSERTED    DELETED
        10         11 make.sh
        10         11 TOTAL over 1 changed file

Index: make.sh
==================================================================
--- make.sh
+++ make.sh
@@ -2,19 +2,18 @@
 SRC=src
 DST=gopher
 CGI=cgi
 
 build_cgi() {
-    build_gophermap $1 $2 $3
-    chmod a+rx $3
+    build_gophermap $1 $2
+    chmod a+rx $2
     return
 }
 
 build_gophermap() {
-    dir=$1
-    in=$2
-    out=$3
+    in=$1
+    out=$2
     echo "m4 $in >$out ..."
     m4 $in >$out
     return
 }
 
@@ -25,11 +24,11 @@
     books/gophermap    \
     images/gophermap   \
     software/gophermap \
     video/gophermap
 do
-    build_gophermap $SRC ${SRC}/${f}.m4 ${DST}/$f
+    build_gophermap ${SRC}/${f}.m4 ${DST}/$f
 done
 
 # CGI
 
 for f in account \
@@ -43,18 +42,18 @@
     sort         \
     wizard/step1 \
     wizard/step2 \
     wizard/step3
 do
-    build_cgi $SRC ${SRC}/${f}.m4 ${CGI}/$f
+    build_cgi ${SRC}/${f}.m4 ${CGI}/$f
 done
 
 # special cases
 
-build_cgi $SRC ${SRC}/raw.m4 ${CGI}/debug
-build_cgi $SRC ${SRC}/raw.m4 ${CGI}/text
-build_cgi $SRC ${SRC}/download.m4 ${CGI}/direct
-build_cgi $SRC ${SRC}/raw.m4 ${CGI}/links
+build_cgi ${SRC}/raw.m4 ${CGI}/debug
+build_cgi ${SRC}/raw.m4 ${CGI}/text
+build_cgi ${SRC}/download.m4 ${CGI}/direct
+build_cgi ${SRC}/raw.m4 ${CGI}/links
 cp ${SRC}/wizard/gophermap  ${CGI}/wizard/
 
 cp pass.tsv ${DST}/pass.tsv
 exit 0

]]></content>
<updated>2025-11-09 01:30:25</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/db8f1b59d9/</id>
<title>Fix typo in example config.m4  s/__DO...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 01:27:39

Fix typo in example config.m4  s/__DOGPATH__/__DOCPATH__/

  INSERTED    DELETED
         1          1 config.m4
         1          1 TOTAL over 1 changed file

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -7,11 +7,11 @@
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
 define(__AWK_EXT__, 0)dnl
 define(__CACHE_DB__, /home/user/pharos/db/cache.dat)dnl
 define(__CACHE_ENABLED__, 0)dnl
 define(__CGIPATH__, /cgi)dnl
-define(__DOGPATH__, /~user/pharos)dnl
+define(__DOCPATH__, /~user/pharos)dnl
 define(__CMD_AWK__, /usr/bin/awk)dnl
 define(__CMD_CURL__, /usr/bin/curl)dnl
 define(__CMD_ENV__, /usr/bin/env)dnl
 define(__CMD_JSON2TSV__, /usr/local/bin/json2tsv)dnl
 define(__CMD_MKTEMP__, /bin/mktemp)dnl

]]></content>
<updated>2025-11-09 01:27:39</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/95f091eb5b/</id>
<title>Update pharos to version 14. Modify i...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-09 01:26:03

  Update pharos to version 14. Modify it to run under gophernicus
  rather than geomyidae.

  INSERTED    DELETED
         2         25 clean.sh
         3          3 config.m4
        53         35 make.sh
         0         36 pharos/about.txt
 pharos/account/index.dcgi
 pharos/audio/index.gph
 pharos/books/index.gph
 pharos/debug/index.cgi
 pharos/details/index.dcgi
 pharos/direct/index.dcgi
 pharos/download/index.dcgi
 pharos/images/index.gph
 pharos/index.gph
 pharos/links/index.dcgi
 pharos/list/index.dcgi
 pharos/lists/index.dcgi
 pharos/listsort/index.dcgi
 pharos/raw/index.cgi
 pharos/search/index.dcgi
 pharos/software/index.gph
 pharos/sort/index.dcgi
 pharos/text/index.cgi
 pharos/video/index.gph
         0         10 pharos/wizard/index.gph
 pharos/wizard/step1/index.dcgi
 pharos/wizard/step2/index.dcgi
 pharos/wizard/step3/index.dcgi
        18         15 readme.txt
       130          0 src/account.m4
         0        126 src/account/index.dcgi.m4
        21          0 src/audio/gophermap.m4
         0         21 src/audio/index.gph.m4
        22          0 src/books/gophermap.m4
         0         22 src/books/index.gph.m4
        26         54 src/cgi.awk
         1          1 src/config.awk
       230          0 src/details.m4
         0        230 src/details/index.dcgi.m4
       103          0 src/download.m4
         0        106 src/download/index.dcgi.m4
        26          0 src/gophermap.m4
        14          0 src/images/gophermap.m4
         0         14 src/images/index.gph.m4
         0         26 src/index.gph.m4
       229          0 src/list.m4
         0        220 src/list/index.dcgi.m4
       113          0 src/lists.m4
         0        111 src/lists/index.dcgi.m4
        54          0 src/listsort.m4
         0         54 src/listsort/index.dcgi.m4
        46          0 src/raw.m4
         0         46 src/raw/index.cgi.m4
       261          0 src/search.m4
         0        255 src/search/index.dcgi.m4
        28          0 src/software/gophermap.m4
         0         28 src/software/index.gph.m4
        61          0 src/sort.m4
         0         61 src/sort/index.dcgi.m4
         3          3 src/util.awk
        23          0 src/video/gophermap.m4
         0         23 src/video/index.gph.m4
         9         10 src/web.awk
        10          0 src/wizard/gophermap
        65          0 src/wizard/step1.m4
         0         67 src/wizard/step1/index.dcgi.m4
        51          0 src/wizard/step2.m4
         0         51 src/wizard/step2/index.dcgi.m4
        45          0 src/wizard/step3.m4
         0         44 src/wizard/step3/index.dcgi.m4
      1647       1697 TOTAL over 47 changed files

Index: clean.sh
==================================================================
--- clean.sh
+++ clean.sh
@@ -1,27 +1,4 @@
 #!/bin/sh
-set -x
-cd pharos
-rm -f index.gph             \
-    account/index.dcgi      \
-    audio/index.gph         \
-    books/index.gph         \
-    images/index.gph        \
-    debug/index.cgi         \
-    details/index.dcgi      \
-    direct/index.dcgi       \
-    download/index.dcgi     \
-    links/index.dcgi        \
-    list/index.dcgi         \
-    lists/index.dcgi        \
-    raw/index.cgi           \
-    search/index.dcgi       \
-    software/index.gph      \
-    sort/index.dcgi         \
-    text/index.cgi          \
-    video/index.gph         \
-    wizard/step1/index.dcgi \
-    wizard/step2/index.dcgi \
-    wizard/step3/index.dcgi
-set +x
-cd -
+find cgi -type f -exec rm {} \;
+find gopher -type f -exec rm {} \;
 exit 0

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -1,16 +1,17 @@
 dnl Set configuration variables
 dnl
-define(__PHAROS_VERSION__, 13)dnl
+define(__PHAROS_VERSION__, 14)dnl
 dnl
 define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
 define(__API_ENDPOINT__, http://archive.org)dnl
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
 define(__AWK_EXT__, 0)dnl
 define(__CACHE_DB__, /home/user/pharos/db/cache.dat)dnl
 define(__CACHE_ENABLED__, 0)dnl
-define(__CGIPATH__, /~user/pharos)dnl
+define(__CGIPATH__, /cgi)dnl
+define(__DOGPATH__, /~user/pharos)dnl
 define(__CMD_AWK__, /usr/bin/awk)dnl
 define(__CMD_CURL__, /usr/bin/curl)dnl
 define(__CMD_ENV__, /usr/bin/env)dnl
 define(__CMD_JSON2TSV__, /usr/local/bin/json2tsv)dnl
 define(__CMD_MKTEMP__, /bin/mktemp)dnl
@@ -18,11 +19,10 @@
 define(__CMD_SQLITE__, /usr/bin/sqlite3)dnl
 define(__CMD_STRINGS__, /bin/busybox strings)dnl
 define(__CMD_WEBDUMP__, /usr/local/bin/webdump)dnl
 define(__CMD_XARGS__, /usr/bin/xargs)dnl
 define(__CONTACT__, admin@example.com)dnl
-define(__GEOMYIDAE_VERSION__, 0.96)dnl
 define(__MAX_BIN_SIZE__, 10)dnl
 define(__MAX_TXT_SIZE__, 1)dnl
 define(__PASS_ENABLED__, 0)dnl
 define(__PASS_LIST__, /home/user/pharos/pass.tsv)dnl
 define(__SERVER__, server)dnl

Index: make.sh
==================================================================
--- make.sh
+++ make.sh
@@ -1,42 +1,60 @@
 #!/bin/sh
-DESTDIR=pharos
 SRC=src
+DST=gopher
+CGI=cgi
+
+build_cgi() {
+    build_gophermap $1 $2 $3
+    chmod a+rx $3
+    return
+}
 
-build() {
+build_gophermap() {
     dir=$1
     in=$2
     out=$3
-    echo "m4 $in >$out && chmod a+rx $out"
-    m4 $in >$out && chmod a+rx $out
-}
-
-# PLAIN
-for f in index.gph              \
-    account/index.dcgi      \
-    audio/index.gph         \
-    books/index.gph         \
-    images/index.gph        \
-    details/index.dcgi      \
-    download/index.dcgi     \
-    list/index.dcgi         \
-    lists/index.dcgi        \
-    listsort/index.dcgi     \
-    raw/index.cgi           \
-    search/index.dcgi       \
-    software/index.gph      \
-    sort/index.dcgi         \
-    video/index.gph         \
-    wizard/step1/index.dcgi \
-    wizard/step2/index.dcgi \
-    wizard/step3/index.dcgi
-do
-    build $SRC ${SRC}/${f}.m4 ${DESTDIR}/$f
-done
-
-# WEIRD
-build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/debug/index.cgi
-build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/text/index.cgi
-build $SRC ${SRC}/download/index.dcgi.m4 ${DESTDIR}/direct/index.dcgi
-build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/links/index.dcgi
-cp pass.tsv ${DESTDIR}/pass.tsv
+    echo "m4 $in >$out ..."
+    m4 $in >$out
+    return
+}
+
+# gophermaps
+
+for f in gophermap     \
+    audio/gophermap    \
+    books/gophermap    \
+    images/gophermap   \
+    software/gophermap \
+    video/gophermap
+do
+    build_gophermap $SRC ${SRC}/${f}.m4 ${DST}/$f
+done
+
+# CGI
+
+for f in account \
+    details      \
+    download     \
+    list         \
+    lists        \
+    listsort     \
+    raw          \
+    search       \
+    sort         \
+    wizard/step1 \
+    wizard/step2 \
+    wizard/step3
+do
+    build_cgi $SRC ${SRC}/${f}.m4 ${CGI}/$f
+done
+
+# special cases
+
+build_cgi $SRC ${SRC}/raw.m4 ${CGI}/debug
+build_cgi $SRC ${SRC}/raw.m4 ${CGI}/text
+build_cgi $SRC ${SRC}/download.m4 ${CGI}/direct
+build_cgi $SRC ${SRC}/raw.m4 ${CGI}/links
+cp ${SRC}/wizard/gophermap  ${CGI}/wizard/
+
+cp pass.tsv ${DST}/pass.tsv
 exit 0

DELETED pharos/about.txt
Index: pharos/about.txt
==================================================================
--- pharos/about.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-# About PHAROS
-
-Proxy Internet Archive to gopher.
-
-# Search
-
-Example: To find Indian music, excluding commercial samples.
-
-    description:(indian) AND -collection:samples_only AND 
-         mediatype:audio AND subject:music
-
-# Fields
-
-* date:
-  YYYY-MM-DD or [YYYY-MM-DD TO YYYY-MM-DD]
-* mediatype:
-  audio, collection, data, image, movies, software, texts, or web
-
-# Media type key for search results
-
-* [aud] = audio or etree
-* [col] = collection
-* [dat] = data
-* [img] = image
-* [mov] = movies
-* [bin] = software
-* [txt] = texts
-* [web] = web
-
-See also: <https://archive.org/advancedsearch.php>
-
-# Limits
-
-This service does not work in the lagrange browser.
-Lagrange URI encodes the search string and also
-mangles items that are not URI encoded.

DELETED pharos/account/index.dcgi
Index: pharos/account/index.dcgi
==================================================================
--- pharos/account/index.dcgi
+++ /dev/null

DELETED pharos/audio/index.gph
Index: pharos/audio/index.gph
==================================================================
--- pharos/audio/index.gph
+++ /dev/null

DELETED pharos/books/index.gph
Index: pharos/books/index.gph
==================================================================
--- pharos/books/index.gph
+++ /dev/null

DELETED pharos/debug/index.cgi
Index: pharos/debug/index.cgi
==================================================================
--- pharos/debug/index.cgi
+++ /dev/null

DELETED pharos/details/index.dcgi
Index: pharos/details/index.dcgi
==================================================================
--- pharos/details/index.dcgi
+++ /dev/null

DELETED pharos/direct/index.dcgi
Index: pharos/direct/index.dcgi
==================================================================
--- pharos/direct/index.dcgi
+++ /dev/null

DELETED pharos/download/index.dcgi
Index: pharos/download/index.dcgi
==================================================================
--- pharos/download/index.dcgi
+++ /dev/null

DELETED pharos/images/index.gph
Index: pharos/images/index.gph
==================================================================
--- pharos/images/index.gph
+++ /dev/null

DELETED pharos/index.gph
Index: pharos/index.gph
==================================================================
--- pharos/index.gph
+++ /dev/null

DELETED pharos/links/index.dcgi
Index: pharos/links/index.dcgi
==================================================================
--- pharos/links/index.dcgi
+++ /dev/null

DELETED pharos/list/index.dcgi
Index: pharos/list/index.dcgi
==================================================================
--- pharos/list/index.dcgi
+++ /dev/null

DELETED pharos/lists/index.dcgi
Index: pharos/lists/index.dcgi
==================================================================
--- pharos/lists/index.dcgi
+++ /dev/null

DELETED pharos/listsort/index.dcgi
Index: pharos/listsort/index.dcgi
==================================================================
--- pharos/listsort/index.dcgi
+++ /dev/null

DELETED pharos/raw/index.cgi
Index: pharos/raw/index.cgi
==================================================================
--- pharos/raw/index.cgi
+++ /dev/null

DELETED pharos/search/index.dcgi
Index: pharos/search/index.dcgi
==================================================================
--- pharos/search/index.dcgi
+++ /dev/null

DELETED pharos/software/index.gph
Index: pharos/software/index.gph
==================================================================
--- pharos/software/index.gph
+++ /dev/null

DELETED pharos/sort/index.dcgi
Index: pharos/sort/index.dcgi
==================================================================
--- pharos/sort/index.dcgi
+++ /dev/null

DELETED pharos/text/index.cgi
Index: pharos/text/index.cgi
==================================================================
--- pharos/text/index.cgi
+++ /dev/null

DELETED pharos/video/index.gph
Index: pharos/video/index.gph
==================================================================
--- pharos/video/index.gph
+++ /dev/null

DELETED pharos/wizard/index.gph
Index: pharos/wizard/index.gph
==================================================================
--- pharos/wizard/index.gph
+++ /dev/null
@@ -1,10 +0,0 @@
-             ,    _
-            /|   | |
-          _/_\_  >_<
-         .-\-/.   |
-        /  | | \_ |
-        \ \| |\__(/
-        /(`---')  |
-       / /     \  |
-    _.'  \'-'  /  |
-    `----'`=-='   '    hjw

DELETED pharos/wizard/step1/index.dcgi
Index: pharos/wizard/step1/index.dcgi
==================================================================
--- pharos/wizard/step1/index.dcgi
+++ /dev/null

DELETED pharos/wizard/step2/index.dcgi
Index: pharos/wizard/step2/index.dcgi
==================================================================
--- pharos/wizard/step2/index.dcgi
+++ /dev/null

DELETED pharos/wizard/step3/index.dcgi
Index: pharos/wizard/step3/index.dcgi
==================================================================
--- pharos/wizard/step3/index.dcgi
+++ /dev/null

Index: readme.txt
==================================================================
--- readme.txt
+++ readme.txt
@@ -1,47 +1,50 @@
 PHAROS by Ben Collver <bencollver@tilde.pink>
 =============================================
+
 * Description
 * Requirements
 * Configuration
 * Installation
 
 Description
 ===========
+
 Pharos is a gopher front-end to the Internet Archive written in AWK.
 It is named after the light-house island near Alexandria.
 
-For a description and screenshots from an earlier version, see my
-post linked below.
-
-<gopher://tilde.pink/1/~bencollver/log/
-2024-07-30-pharos-gopher-frontend-to-internet-archive/>
-
-<https://gopher.tildeverse.org/tilde.pink/1/~bencollver/log/
-2024-07-30-pharos-gopher-frontend-to-internet-archive/>
-
 Requirements
 ============
-* Runs under the Geomyidae gopher server.
-  <gopher://bitreich.org/1/scm/geomyidae/log.gph>
+
+* Runs under the Gophernicus gopher server.
+  <gopher://gopher.gophernicus.org/>
 * Written in AWK.  Tested using busybox awk.
 * Commands used:
   * env, mktemp, rm, strings, xargs
   * curl <https://curl.se/>
   * json2tsv <gopher://codemadness.org/1/phlog/json2tsv/>
   * webdump <gopher://codemadness.org/1/phlog/webdump/>
 
 Configuration
 =============
+
 To set configuration variables, edit config.m4
 
 AWK_EXT allows use of non-standard features in gawk and mawk
 CACHE_ENABLED caches content in sqlite to reduce API calls
 
 Installation
 ============
-Installation depends on m4.
+
+Installation depends on m4.  I use m4 to achieve a portable module
+system in any major awk.  Gawk has @include() but that isn't
+portable.  The make.sh script process the source code and generates
+the CGI files based on the settings in config.m4.
+
+    $ sh clean.sh
+    $ sh make.sh
 
-$ sh clean.sh
-$ sh make.sh
+Copy cgi/* into the CGI directory.
+Copy gopher/* into a gopher directory.
 
-Copy pharos/ into place
+This separation between CGI and gophermaps is necessary because
+dynamic gophermaps not binary-safe in gophernicus.

ADDED   src/account.m4
Index: src/account.m4
==================================================================
--- /dev/null
+++ src/account.m4
@@ -0,0 +1,130 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/account
+#
+# Show details for an account
+
+include(src/config.awk)
+incl(src/api.awk)
+incl(src/cache.awk)
+incl(src/cgi.awk)
+incl(src/sqlite.awk)
+incl(src/util.awk)
+incl(src/web.awk)
+
+function main(     acct, cmd, col, cols, count, descr, dir, email,
+     iaout, id, item_server, item_size, output, prefix, signature,
+     str, thumb, title, type, url)
+{
+    count = split(search, parts, "/")
+    acct = parts[1]
+    email = parts[2]
+
+    signature = sprintf("account/%s/%s", acct, email)
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
+
+    output = cache_begin()
+    iaout = gettemp()
+
+    url = api_endpoint "/metadata/" acct
+    api_request(url, "GET", iaout)
+    
+    # format search results as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    cols = 0
+    delete col
+    descr = ""
+    dir = ""
+    id = ""
+    item_server = ""
+    item_size = 0
+    title = ""
+    thumb = ""
+    type = ""
+    while ((cmd | getline) > 0) {
+        if ($1 == ".dir" && $2 == "s") {
+            dir = $3
+        } else if ($1 == ".files[].name" && $2 == "s") {
+            if ($3 == "__ia_thumb.jpg") {
+                thumb = $3
+            }
+        } else if ($1 == ".item_size" && $2 == "n") {
+            item_size = $3
+        } else if ($1 == ".metadata.collection" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.collection[]" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.description" && $2 == "s") {
+            descr = $3
+        } else if ($1 == ".metadata.identifier" && $2 == "s") {
+            id = $3
+        } else if ($1 == ".metadata.mediatype" && $2 == "s") {
+            type = $3
+        } else if ($1 == ".metadata.title" && $2 == "s") {
+            title = $3
+        } else if ($1 == ".server" && $2 == "s") {
+            item_server = $3
+        }
+    }
+    close(cmd)
+
+    if (length(id) == 0) {
+        print_not_found(output, url)
+        cache_end()
+        unlink(iaout)
+        return
+    }
+
+    print "Account: " acct >>output
+    if (length(thumb) > 0) {
+        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
+        printf "IThumbnail\t%s/raw/%s\t%s\t%s\n", cgipath, url,
+            server, port >>output
+    }
+    print_html(output, descr)
+
+    if (length(email) > 0) {
+        prefix = "/sortaddeddate desc"
+        printf "1Uploads\t%s\tsearch?uploader:%s%s\t%s\t%s\n", cgipath,
+            email, prefix, server, port >>output
+    }
+
+    prefix = "/sortaddeddate desc"
+    printf "1Items\t%s/search?anyfield:%s%s\t%s\t%s\n", cgipath,
+        acct, prefix, server, port >>output
+
+    printf "1Lists\t%s/lists?%s\t%s\t%s\n", cgipath, acct, server,
+        port >>output
+
+    print "" >>output
+    printf "%-20s %s\n", "Identifier:", id >>output
+    if (item_size > 0) {
+        printf "%-20s %d\n", "Item Size:", item_size >>output
+    }
+    printf "%-20s %s\n", "Media Type:", type >>output
+    
+    print "" >>output
+    printf "hAccount web page\tURL:%s/details?%s\t%s\t%s\n",
+        api_ssl_endpoint, uri_encode(id), server, port >>output
+    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+
+    cache_end()
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    main()
+}

DELETED src/account/index.dcgi.m4
Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ /dev/null
@@ -1,126 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# account/index.dcgi
-#
-# Show details for an account
-
-include(src/config.awk)
-incl(src/api.awk)
-incl(src/cache.awk)
-incl(src/cgi.awk)
-incl(src/sqlite.awk)
-incl(src/util.awk)
-incl(src/web.awk)
-
-function main(     acct, cmd, col, cols, descr, dir, email, iaout, id,
-    item_server, item_size, output, signature, str, thumb, title,
-    type, url)
-{
-    acct = parts[3]
-    email = search
-
-    signature = sprintf("account/%s/%s", acct, email)
-    str = cache_init(signature)
-    if (length(str) > 0) {
-        print str
-        return
-    }
-
-    output = cache_begin()
-    iaout = gettemp()
-
-    url = api_endpoint "/metadata/" acct
-    api_request(url, "GET", iaout)
-    
-    # format search results as a gopher directory (menu)
-    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
-    FS = "\t"
-    cols = 0
-    delete col
-    descr = ""
-    dir = ""
-    id = ""
-    item_server = ""
-    item_size = 0
-    title = ""
-    thumb = ""
-    type = ""
-    while ((cmd | getline) > 0) {
-        if ($1 == ".dir" && $2 == "s") {
-            dir = $3
-        } else if ($1 == ".files[].name" && $2 == "s") {
-            if ($3 == "__ia_thumb.jpg") {
-                thumb = $3
-            }
-        } else if ($1 == ".item_size" && $2 == "n") {
-            item_size = $3
-        } else if ($1 == ".metadata.collection" && $2 == "s") {
-            cols++
-            col[cols] = $3
-        } else if ($1 == ".metadata.collection[]" && $2 == "s") {
-            cols++
-            col[cols] = $3
-        } else if ($1 == ".metadata.description" && $2 == "s") {
-            descr = $3
-        } else if ($1 == ".metadata.identifier" && $2 == "s") {
-            id = $3
-        } else if ($1 == ".metadata.mediatype" && $2 == "s") {
-            type = $3
-        } else if ($1 == ".metadata.title" && $2 == "s") {
-            title = $3
-        } else if ($1 == ".server" && $2 == "s") {
-            item_server = $3
-        }
-    }
-    close(cmd)
-
-    if (length(id) == 0) {
-        print_not_found(output, url)
-        cache_end()
-        unlink(iaout)
-        return
-    }
-
-    print "Account: " acct >>output
-    if (length(thumb) > 0) {
-        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
-        printf "[I|Thumbnail|%s/raw/%%09%s|%s|%s]\n", cgipath, url,
-            server, port >>output
-    }
-    print_html(output, descr)
-
-    if (length(email) > 0) {
-        printf "[1|Uploads|%s/search/sortaddeddate desc%%09uploader:%s|" \
-            "%s|%s]\n",
-            cgipath, email, server, port >>output
-    }
-    printf "[1|Items|%s/search/sortaddeddate desc%%09anyfield:%s|%s|%s]\n",
-        cgipath, acct, server, port >>output
-    printf "[1|Lists|%s/lists/%%09%s|%s|%s]\n", cgipath, acct,
-        server, port >>output
-
-    print "" >>output
-    printf "%-20s %s\n", "Identifier:", id >>output
-    if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size >>output
-    }
-    printf "%-20s %s\n", "Media Type:", type >>output
-    
-    print "" >>output
-    printf "[h|Account web page|URL:%s/details/%s|%s|%s]\n",
-        api_ssl_endpoint, uri_encode(id), server, port >>output
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
-
-    cache_end()
-    unlink(iaout)
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    uri_encode_init()
-    main()
-}

ADDED   src/audio/gophermap.m4
Index: src/audio/gophermap.m4
==================================================================
--- /dev/null
+++ src/audio/gophermap.m4
@@ -0,0 +1,21 @@
+include(config.m4)dnl
+# Audio
+
+[1|All Audio|__CGIPATH__/search/sortweek desc/%09mediatype:audio|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:audio|__SERVER__|__PORT__]
+[1|Live Music Archive|__CGIPATH__/search/sortweek desc/%09collection:etree AND mediatype:collection|__SERVER__|__PORT__]
+[1|Librivox Free Audiobooks|__CGIPATH__/search/sortweek desc/%09collection:librivoxaudio|__SERVER__|__PORT__]
+[1|Grateful Dead|__CGIPATH__/search/sortweek desc/%09collection:GratefulDead|__SERVER__|__PORT__]
+[1|Netlabels|__CGIPATH__/search/sortweek desc/%09collection:netlabels AND mediatype:collection|__SERVER__|__PORT__]
+[1|Old Time Radio|__CGIPATH__/search/sortweek desc/%09collection:oldtimeradio|__SERVER__|__PORT__]
+[1|78 RPMs & Cylinder Recordings|__CGIPATH__/search/sortweek desc/%09collection:78rpm AND mediatype:collection|__SERVER__|__PORT__]
+[1|Audio Books & Poetry|__CGIPATH__/search/sortweek desc/%09collection:audio_bookspoetry|__SERVER__|__PORT__]
+[1|Computers, Technology & Science|__CGIPATH__/search/sortweek desc/%09collection:audio_tech|__SERVER__|__PORT__]
+[1|Music, Arts & Culture|__CGIPATH__/search/sortweek desc/%09collection:audio_music AND mediatype:collection|__SERVER__|__PORT__]
+[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:audio_news|__SERVER__|__PORT__]
+[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:audio_religion|__SERVER__|__PORT__]
+[1|Podcasts|__CGIPATH__/search/sortweek desc/%09collection:podcasts|__SERVER__|__PORT__]
+[1|Radio News Archive|__CGIPATH__/search/sortweek desc/%09collection:radio|__SERVER__|__PORT__]
+[1|Long Playing Records|__CGIPATH__/search/sortweek desc/%09collection:album_recordings|__SERVER__|__PORT__]
+[1|Various Cassette Tapes|__CGIPATH__/search/sortweek desc/%09collection:cassettetapes|__SERVER__|__PORT__]
+[1|Audiophile CD Collection|__CGIPATH__/search/sortweek desc/%09collection:acdc AND -collection:samples_only|__SERVER__|__PORT__]

DELETED src/audio/index.gph.m4
Index: src/audio/index.gph.m4
==================================================================
--- src/audio/index.gph.m4
+++ /dev/null
@@ -1,21 +0,0 @@
-include(config.m4)dnl
-# Audio
-
-[1|All Audio|__CGIPATH__/search/sortweek desc/%09mediatype:audio|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:audio|__SERVER__|__PORT__]
-[1|Live Music Archive|__CGIPATH__/search/sortweek desc/%09collection:etree AND mediatype:collection|__SERVER__|__PORT__]
-[1|Librivox Free Audiobooks|__CGIPATH__/search/sortweek desc/%09collection:librivoxaudio|__SERVER__|__PORT__]
-[1|Grateful Dead|__CGIPATH__/search/sortweek desc/%09collection:GratefulDead|__SERVER__|__PORT__]
-[1|Netlabels|__CGIPATH__/search/sortweek desc/%09collection:netlabels AND mediatype:collection|__SERVER__|__PORT__]
-[1|Old Time Radio|__CGIPATH__/search/sortweek desc/%09collection:oldtimeradio|__SERVER__|__PORT__]
-[1|78 RPMs & Cylinder Recordings|__CGIPATH__/search/sortweek desc/%09collection:78rpm AND mediatype:collection|__SERVER__|__PORT__]
-[1|Audio Books & Poetry|__CGIPATH__/search/sortweek desc/%09collection:audio_bookspoetry|__SERVER__|__PORT__]
-[1|Computers, Technology & Science|__CGIPATH__/search/sortweek desc/%09collection:audio_tech|__SERVER__|__PORT__]
-[1|Music, Arts & Culture|__CGIPATH__/search/sortweek desc/%09collection:audio_music AND mediatype:collection|__SERVER__|__PORT__]
-[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:audio_news|__SERVER__|__PORT__]
-[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:audio_religion|__SERVER__|__PORT__]
-[1|Podcasts|__CGIPATH__/search/sortweek desc/%09collection:podcasts|__SERVER__|__PORT__]
-[1|Radio News Archive|__CGIPATH__/search/sortweek desc/%09collection:radio|__SERVER__|__PORT__]
-[1|Long Playing Records|__CGIPATH__/search/sortweek desc/%09collection:album_recordings|__SERVER__|__PORT__]
-[1|Various Cassette Tapes|__CGIPATH__/search/sortweek desc/%09collection:cassettetapes|__SERVER__|__PORT__]
-[1|Audiophile CD Collection|__CGIPATH__/search/sortweek desc/%09collection:acdc AND -collection:samples_only|__SERVER__|__PORT__]

ADDED   src/books/gophermap.m4
Index: src/books/gophermap.m4
==================================================================
--- /dev/null
+++ src/books/gophermap.m4
@@ -0,0 +1,22 @@
+include(config.m4)dnl
+# Books
+
+[1|All Texts|__CGIPATH__/search/sortweek desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Smithsonian Libraries|__CGIPATH__/search/sortweek desc/%09collection:smithsonian|__SERVER__|__PORT__]
+[1|FEDLINK|__CGIPATH__/search/sortweek desc/%09collection:fedlink AND mediatype:collection|__SERVER__|__PORT__]
+[1|Genealogy|__CGIPATH__/search/sortweek desc/%09collection:genealogy AND mediatype:collection|__SERVER__|__PORT__]
+[1|Lincoln Collection|__CGIPATH__/search/sortweek desc/%09collection:lincolncollection|__SERVER__|__PORT__]
+[1|American Libraries|__CGIPATH__/search/sortweek desc/%09collection:americana AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Canadian Libraries|__CGIPATH__/search/sortweek desc/%09collection:toronto AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Universal Library|__CGIPATH__/search/sortweek desc/%09collection:universallibrary|__SERVER__|__PORT__]
+[1|Project Gutenberg|__CGIPATH__/search/sortweek desc/%09collection:gutenberg|__SERVER__|__PORT__]
+[1|Children's Library|__CGIPATH__/search/sortweek desc/%09collection:iacl AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Biodiversity Heritage Library|__CGIPATH__/search/sortweek desc/%09collection:biodiversity AND mediatype:collection|__SERVER__|__PORT__]
+[1|Books By Language|__CGIPATH__/search/sortweek desc/%09collection:booksbylanguage AND mediatype:collection|__SERVER__|__PORT__]
+[1|Additional Collections|__CGIPATH__/search/sortweek desc/%09collection:additional_collections AND mediatype:collection|__SERVER__|__PORT__]
+[1|The Magazine Rack|__CGIPATH__/search/sortweek desc/%09collection:magazine_rack AND mediatype:collection|__SERVER__|__PORT__]
+[1|The Pulp Magazine Archive|__CGIPATH__/search/sortweek desc/%09collection:pulpmagazinearchive|__SERVER__|__PORT__]
+[1|Newspapers|__CGIPATH__/search/sortweek desc/%09collection:newspapers AND mediatype:collection|__SERVER__|__PORT__]
+[1|Comic Books & Graphic Novels|__CGIPATH__/search/sortweek desc/%09collection:comics AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Zines|__CGIPATH__/search/sortweek desc/%09collection:zines AND -access-restricted-item:true|__SERVER__|__PORT__]

DELETED src/books/index.gph.m4
Index: src/books/index.gph.m4
==================================================================
--- src/books/index.gph.m4
+++ /dev/null
@@ -1,22 +0,0 @@
-include(config.m4)dnl
-# Books
-
-[1|All Texts|__CGIPATH__/search/sortweek desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Smithsonian Libraries|__CGIPATH__/search/sortweek desc/%09collection:smithsonian|__SERVER__|__PORT__]
-[1|FEDLINK|__CGIPATH__/search/sortweek desc/%09collection:fedlink AND mediatype:collection|__SERVER__|__PORT__]
-[1|Genealogy|__CGIPATH__/search/sortweek desc/%09collection:genealogy AND mediatype:collection|__SERVER__|__PORT__]
-[1|Lincoln Collection|__CGIPATH__/search/sortweek desc/%09collection:lincolncollection|__SERVER__|__PORT__]
-[1|American Libraries|__CGIPATH__/search/sortweek desc/%09collection:americana AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Canadian Libraries|__CGIPATH__/search/sortweek desc/%09collection:toronto AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Universal Library|__CGIPATH__/search/sortweek desc/%09collection:universallibrary|__SERVER__|__PORT__]
-[1|Project Gutenberg|__CGIPATH__/search/sortweek desc/%09collection:gutenberg|__SERVER__|__PORT__]
-[1|Children's Library|__CGIPATH__/search/sortweek desc/%09collection:iacl AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Biodiversity Heritage Library|__CGIPATH__/search/sortweek desc/%09collection:biodiversity AND mediatype:collection|__SERVER__|__PORT__]
-[1|Books By Language|__CGIPATH__/search/sortweek desc/%09collection:booksbylanguage AND mediatype:collection|__SERVER__|__PORT__]
-[1|Additional Collections|__CGIPATH__/search/sortweek desc/%09collection:additional_collections AND mediatype:collection|__SERVER__|__PORT__]
-[1|The Magazine Rack|__CGIPATH__/search/sortweek desc/%09collection:magazine_rack AND mediatype:collection|__SERVER__|__PORT__]
-[1|The Pulp Magazine Archive|__CGIPATH__/search/sortweek desc/%09collection:pulpmagazinearchive|__SERVER__|__PORT__]
-[1|Newspapers|__CGIPATH__/search/sortweek desc/%09collection:newspapers AND mediatype:collection|__SERVER__|__PORT__]
-[1|Comic Books & Graphic Novels|__CGIPATH__/search/sortweek desc/%09collection:comics AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|Zines|__CGIPATH__/search/sortweek desc/%09collection:zines AND -access-restricted-item:true|__SERVER__|__PORT__]

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -15,60 +15,38 @@
     print "Service admin will reply when granted."
     print extra
     return
 }
 
-function cgi_init(     extra, ip) {
+function cgi_init(     extra, ip, item) {
     ip = ENVIRON["REMOTE_ADDR"]
-
-    search = ARGV[1]
-    arguments = ARGV[2]
-    traversal = ARGV[5]
-    selector = ARGV[6]
-
-    if (geomyidae_version < 0.96) {
-        input = arguments
-
-        # geomyidae 0.69 doesn't populate selector, so do it manually
-        path = ENVIRON["PATH_TRANSLATED"]
-
-        # remove leading text up through beginning of cgipath
-        if (match(path, cgipath)) {
-            selector = substr(path, RSTART)
-        }
-
-        # remove trailing text from beginning of script name
-        if (match(selector, ENVIRON["SCRIPT_NAME"])) {
-            selector = substr(selector, 1, RSTART - 1)
-        }
-        selector = selector input
-    } else {
-        input = traversal
-    }
-
-    if (length(search) == 0 && match(input, /%09.*/)) {
-        # This is a hack to include a search in the URL.
-        # everything before %09 is considered arguments
-        # everything after %09 is considered the search
-        search = substr(input, RSTART + 3, RLENGTH - 3)
-        args = substr(input, 0, RSTART - 1)
-    } else {
-        args = input
-    }
-
-    # query is everything after ? in the gopher selector
-    # this is NOT the same as the gopher search string
-    query = args
-
-    # parse the path out of the selector
-    path = selector
+    selector = ENVIRON["SELECTOR"]
+
+    # If i run `nc -l -p 7070`, then:
+    # lynx -dump 'gopher://127.0.0.1:7070/1/foo?bar'
+    # The nc output is equivalent to: `printf "/foo?bar\r\n"`
+    #
+    # If i run `nc -l -p 7070`, then:
+    # lynx -dump 'gopher://127.0.0.1:7070/7/foo?bar'
+    # The nc output is equivalent to: `printf "/foo\tbar\r\n"`
+    #
+    # Gophernicus sets QUERY_STRING and SEARCHREQUEST to "bar"
+    # in both cases.  If the selector contains a ? at the same
+    # time that there is a search string, then the two will differ.
+    #
+    # See: <https://boston.conman.org/2020/01/06.1>
+
+    search = ENVIRON["QUERY_STRING"]
+    if (search != ENVIRON["SEARCHREQUEST"]) {
+        searchreq = ENVIRON["SEARCHREQUEST"]
+    }
+
+    # parse the path out of the request
+    path = ENVIRON["REQUEST"]
     if (substr(path, 1, length(cgipath)) == cgipath) {
         path = substr(path, length(cgipath) + 1)
     }
-    if (match(path, /%09/)) {
-        path = substr(path, 1, RSTART - 1)
-    }
 
     split(path, parts, "/")
     topdir = parts[2]
 
     if (pass_enabled) {
@@ -88,13 +66,13 @@
         close(pass_list)
         if (blocked) {
             extra = ""
             if (topdir == "details") {
                 extra = "\nIn the meanwhile, see:\n\n" \
-                    sprintf("[h|Web page|URL:%s/details/%s|%s|%s]\n",
+                    sprintf("hWeb page\tURL:%s/details/%s\t%s\t%s\n",
                     api_ssl_endpoint,
-                    uri_encode(parts[3]),
+                    uri_encode(search),
                     server,
                     port)
             }
             block_msg(ip, extra)
             exit(0)
@@ -102,16 +80,10 @@
     }
 
     return
 }
 
-function gph_encode(str,     retval) {
-    retval = str
-    gsub(/\|/, "\\|", retval)
-    return retval
-}
-
 function uri_encode_init(     i, c) {
     for (i = 0; i <= 255; i++) {
         c = sprintf("%c", i)
         uri_encode_ord[c] = i
         uri_encode_tab[i] = c

Index: src/config.awk
==================================================================
--- src/config.awk
+++ src/config.awk
@@ -4,10 +4,11 @@
     api_ssl_endpoint = "__API_SSL_ENDPOINT__"
     awk_ext = __AWK_EXT__
     cache_db = "__CACHE_DB__"
     cache_enabled = __CACHE_ENABLED__
     cgipath = "__CGIPATH__"
+    docpath = "__DOCPATH__"
     cmd_curl = "__CMD_CURL__"
     cmd_enc = "__CMD_ENV__"
     cmd_json2tsv = "__CMD_JSON2TSV__"
     cmd_mktemp = "__CMD_MKTEMP__"
     cmd_rm = "__CMD_RM__"
@@ -14,14 +15,13 @@
     cmd_sqlite = "__CMD_SQLITE__"
     cmd_strings = "__CMD_STRINGS__"
     cmd_webdump = "__CMD_WEBDUMP__"
     cmd_xargs = "__CMD_XARGS__"
     contact = "__CONTACT__"
-    geomyidae_version = __GEOMYIDAE_VERSION__
     max_bin_size = __MAX_BIN_SIZE__
     max_txt_size = __MAX_TXT_SIZE__
     pass_enabled = __PASS_ENABLED__
     pass_list = "__PASS_LIST__"
     server = "__SERVER__"
     port = "__PORT__"
     return
 }

ADDED   src/details.m4
Index: src/details.m4
==================================================================
--- /dev/null
+++ src/details.m4
@@ -0,0 +1,230 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/details
+#
+# Show details for an item
+
+include(src/config.awk)
+incl(src/api.awk)
+incl(src/cache.awk)
+incl(src/cgi.awk)
+incl(src/sqlite.awk)
+incl(src/util.awk)
+incl(src/web.awk)
+
+function main(     add_date, col, cols, cmd, creator, descr, dir, i,
+    iaout, id, item_id, item_server, item_size, label, language,
+    license, output, pub_date, scanner, signature, str, thumb,
+    title, topic, topics, type, uploader_account, uploader_email, url)
+{
+    item_id = search
+
+    signature = "details/" item_id
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
+
+    iaout = gettemp()
+    output = cache_begin()
+
+    url = api_endpoint "/metadata/" item_id
+    api_request(url, "GET", iaout)
+    
+    # format search results as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    cols = 0
+    delete col
+    add_date = ""
+    creators = 0
+    delete creator
+    descr = ""
+    dir = ""
+    id = ""
+    item_server = ""
+    item_size = 0
+    language = ""
+    license = ""
+    pub_date = ""
+    scanner = ""
+    thumb = ""
+    title = ""
+    topics = 0
+    delete topic
+    type = ""
+    uploader_account = ""
+    uploader_email = ""
+    while ((cmd | getline) > 0) {
+        if ($1 == ".dir" && $2 == "s") {
+            dir = $3
+        } else if ($1 == ".files[].name" && $2 == "s") {
+            if ($3 == "__ia_thumb.jpg") {
+                thumb = $3
+            }
+        } else if ($1 == ".item_size" && $2 == "n") {
+            item_size = $3
+        } else if ($1 == ".metadata.addeddate" && $2 == "s") {
+            added_date = $3
+        } else if ($1 == ".metadata.collection" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.collection[]" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.creator" && $2 == "s") {
+            creators++
+            creator[creators] = $3
+        } else if ($1 == ".metadata.creator[]" && $2 == "s") {
+            creators++
+            creator[creators] = $3
+        } else if ($1 == ".metadata.description" && $2 == "s") {
+            descr = $3
+        } else if ($1 == ".metadata.identifier" && $2 == "s") {
+            id = $3
+        } else if ($1 == ".metadata.language" && $2 == "s") {
+            language = $3
+        } else if ($1 == ".metadata.license" && $2 == "s") {
+            license = licenseurl[$3]
+        } else if ($1 == ".metadata.mediatype" && $2 == "s") {
+            type = $3
+        } else if ($1 == ".metadata.publicdate" && $2 == "s") {
+            pub_date = $3
+        } else if ($1 == ".metadata.scanner" && $2 == "s") {
+            scanner = $3
+        } else if ($1 == ".metadata.subject" && $2 == "s") {
+            topics++
+            topic[topics] = $3
+        } else if ($1 == ".metadata.subject[]" && $2 == "s") {
+            topics++
+            topic[topics] = $3
+        } else if ($1 == ".metadata.title" && $2 == "s") {
+            title = $3
+        } else if ($1 == ".metadata.uploader" && $2 == "s") {
+            uploader_email = $3
+        } else if ($1 == ".server" && $2 == "s") {
+            item_server = $3
+        }
+    }
+    close(cmd)
+
+    if (length(id) == 0) {
+        print_not_found(output, url)
+        cache_end()
+        unlink(iaout)
+        return
+    }
+
+    print shorten(title, 70) >>output
+    if (creators == 1) {
+        label = "by " shorten(creator[1], 70)
+        printf "1%s\t%ssearch?creator:(%s)\t%s\t%s\n", label,
+            cgipath, creator[1], server, port >>output
+    } else if (creators > 1) {
+        printf "\nby:\n" >>output
+        for (i = 1; i <= creators; i++) {
+            label = shorten(creator[i], 70)
+            printf "1%s\t%s/search?creator:(%s)\t%s\t%s\n", label,
+                cgipath, creator[i], server, port >>output
+        }
+        printf "\n" >>output
+    }
+    if (length(thumb) > 0) {
+        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
+        printf "IThumbnail\t%s/raw?%s\t%s\t%s\n", cgipath, url,
+            server, port >>output
+    }
+
+    printf "1Download\t%s/download?%s\t%s\t%s\n", cgipath, item_id,
+        server, port >>output
+    printf "hWeb page\tURL:%s/details/%s\t%s\t%s\n",
+        api_ssl_endpoint, uri_encode(id), server, port >>output
+    print "" >>output
+
+    print_html(output, descr)
+
+    print "" >>output
+    if (length(add_date) > 0) {
+        printf "%-20s %s\n", "Date Added:", add_date >>output
+    }
+    if (pub_date != add_date) {
+        printf "%-20s %s\n", "Date Published:", pub_date >>output
+    }
+    printf "%-20s %s\n", "Identifier:", id >>output
+    if (item_size > 0) {
+        printf "%-20s %d\n", "Item Size:", item_size >>output
+    }
+    if (length(language) > 0) {
+        printf "%-20s %s\n", "Language:", language >>output
+    }
+    if (length(license) > 0) {
+        printf "%-20s %s\n", "License:", license >>output
+    }
+    printf "%-20s %s\n", "Media Type:", type >>output
+    
+    if (topics > 0) {
+        print "" >>output
+        print "# Topics" >>output
+        for (i = 1; i <= topics; i++) {
+            label = shorten(topic[i], 40)
+            printf "1%s\t%s/search?subject:(%s)\t%s\t%s\n", label,
+                cgipath, topic[i], server, port >>output
+        }
+    }
+
+    # scrape uploader name from item web page HTML
+    url = api_ssl_endpoint "/details/" item_id
+    api_request(url, "GET", iaout)
+    while ((getline <iaout) > 0) {
+        if (/item-upload-info__uploader-name/ &&
+            match($0, /\/details\/[^"]*"/))
+        {
+            uploader_account = substr($0, RSTART + 9, RLENGTH - 10)
+        }
+    }
+    close(iaout)
+
+    if (cols > 0) {
+        print "" >>output
+        print "# Collections" >>output
+        for (i = 1; i <= cols; i++) {
+            label = shorten(col[i], 40)
+            printf "1%s\t%s/search?collection:(%s)\t%s\t%s\n",
+                label, cgipath, col[i], server, port >>output
+        }
+    }
+
+    print "" >>output
+    print "# Uploaded by" >>output
+    if (length(uploader_account) > 0) {
+        label = shorten(uploader_account, 70)
+        printf "1%s\t%s/account?%s/%s\t%s\t%s\n", label, cgipath,
+            uploader_account, uploader_email, server, port >>output
+    } else {
+        label = shorten(uploader_email, 70)
+        printf "1%s\t%s/search?uploader:%s\t%s\t%s\n", label,
+            cgipath, uploader_email, server, port >>output
+    }
+
+    print "" >>output
+    print "# Similar items" >>output
+    printf "1View similar items\t%s/search?similar:%s\t%s\t%s\n",
+        cgipath, item_id, server, port >>output
+
+    print "" >>output
+    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+
+    cache_end()
+    unlink(iaout)
+    return
+}
+
+BEGIN {
+    config_init()
+
+    uri_encode_init()
+    cgi_init()
+    main()
+}

DELETED src/details/index.dcgi.m4
Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ /dev/null
@@ -1,230 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# details/index.dcgi
-#
-# Show details for an item
-
-include(src/config.awk)
-incl(src/api.awk)
-incl(src/cache.awk)
-incl(src/cgi.awk)
-incl(src/sqlite.awk)
-incl(src/util.awk)
-incl(src/web.awk)
-
-function main(     add_date, col, cols, cmd, creator, descr, dir, i,
-    iaout, id, item_id, item_server, item_size, label, language,
-    license, output, pub_date, scanner, signature, str, thumb, title,
-    topic, topics, type, uploader_account, uploader_email, url)
-{
-    item_id = parts[3]
-
-    signature = "details/" item_id
-    str = cache_init(signature)
-    if (length(str) > 0) {
-        print str
-        return
-    }
-
-    iaout = gettemp()
-    output = cache_begin()
-
-    url = api_endpoint "/metadata/" item_id
-    api_request(url, "GET", iaout)
-    
-    # format search results as a gopher directory (menu)
-    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
-    FS = "\t"
-    cols = 0
-    delete col
-    add_date = ""
-    creators = 0
-    delete creator
-    descr = ""
-    dir = ""
-    id = ""
-    item_server = ""
-    item_size = 0
-    language = ""
-    license = ""
-    pub_date = ""
-    scanner = ""
-    thumb = ""
-    title = ""
-    topics = 0
-    delete topic
-    type = ""
-    uploader_account = ""
-    uploader_email = ""
-    while ((cmd | getline) > 0) {
-        if ($1 == ".dir" && $2 == "s") {
-            dir = $3
-        } else if ($1 == ".files[].name" && $2 == "s") {
-            if ($3 == "__ia_thumb.jpg") {
-                thumb = $3
-            }
-        } else if ($1 == ".item_size" && $2 == "n") {
-            item_size = $3
-        } else if ($1 == ".metadata.addeddate" && $2 == "s") {
-            added_date = $3
-        } else if ($1 == ".metadata.collection" && $2 == "s") {
-            cols++
-            col[cols] = $3
-        } else if ($1 == ".metadata.collection[]" && $2 == "s") {
-            cols++
-            col[cols] = $3
-        } else if ($1 == ".metadata.creator" && $2 == "s") {
-            creators++
-            creator[creators] = $3
-        } else if ($1 == ".metadata.creator[]" && $2 == "s") {
-            creators++
-            creator[creators] = $3
-        } else if ($1 == ".metadata.description" && $2 == "s") {
-            descr = $3
-        } else if ($1 == ".metadata.identifier" && $2 == "s") {
-            id = $3
-        } else if ($1 == ".metadata.language" && $2 == "s") {
-            language = $3
-        } else if ($1 == ".metadata.license" && $2 == "s") {
-            license = licenseurl[$3]
-        } else if ($1 == ".metadata.mediatype" && $2 == "s") {
-            type = $3
-        } else if ($1 == ".metadata.publicdate" && $2 == "s") {
-            pub_date = $3
-        } else if ($1 == ".metadata.scanner" && $2 == "s") {
-            scanner = $3
-        } else if ($1 == ".metadata.subject" && $2 == "s") {
-            topics++
-            topic[topics] = $3
-        } else if ($1 == ".metadata.subject[]" && $2 == "s") {
-            topics++
-            topic[topics] = $3
-        } else if ($1 == ".metadata.title" && $2 == "s") {
-            title = $3
-        } else if ($1 == ".metadata.uploader" && $2 == "s") {
-            uploader_email = $3
-        } else if ($1 == ".server" && $2 == "s") {
-            item_server = $3
-        }
-    }
-    close(cmd)
-
-    if (length(id) == 0) {
-        print_not_found(output, url)
-        cache_end()
-        unlink(iaout)
-        return
-    }
-
-    print shorten(title, 70) >>output
-    if (creators == 1) {
-        label = "by " shorten(creator[1], 70)
-        printf "[1|%s|%s/search/%%09creator:(%s)|%s|%s]\n", label,
-            cgipath, creator[1], server, port >>output
-    } else if (creators > 1) {
-        printf "\nby:\n" >>output
-        for (i = 1; i <= creators; i++) {
-            label = shorten(creator[i], 70)
-            printf "[1|%s|%s/search/%%09creator:(%s)|%s|%s]\n", label,
-                cgipath, creator[i], server, port >>output
-        }
-        printf "\n" >>output
-    }
-    if (length(thumb) > 0) {
-        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
-        printf "[I|Thumbnail|%s/raw/%%09%s|%s|%s]\n",
-            cgipath, url, server, port >>output
-    }
-
-    printf "[1|Download|%s/download/%s|%s|%s]\n", cgipath,
-        item_id, server, port >>output
-    printf "[h|Web page|URL:%s/details/%s|%s|%s]\n",
-        api_ssl_endpoint, uri_encode(id), server, port >>output
-    print "" >>output
-
-    print_html(output, descr)
-
-    print "" >>output
-    if (length(add_date) > 0) {
-        printf "%-20s %s\n", "Date Added:", add_date >>output
-    }
-    if (pub_date != add_date) {
-        printf "%-20s %s\n", "Date Published:", pub_date >>output
-    }
-    printf "%-20s %s\n", "Identifier:", id >>output
-    if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size >>output
-    }
-    if (length(language) > 0) {
-        printf "%-20s %s\n", "Language:", language >>output
-    }
-    if (length(license) > 0) {
-        printf "%-20s %s\n", "License:", license >>output
-    }
-    printf "%-20s %s\n", "Media Type:", type >>output
-    
-    if (topics > 0) {
-        print "" >>output
-        print "# Topics" >>output
-        for (i = 1; i <= topics; i++) {
-            label = shorten(topic[i], 40)
-            printf "[1|%s|%s/search/%%09subject:(%s)|%s|%s]\n", label,
-                cgipath, topic[i], server, port >>output
-        }
-    }
-
-    # scrape uploader name from item web page HTML
-    url = api_ssl_endpoint "/details/" item_id
-    api_request(url, "GET", iaout)
-    while ((getline <iaout) > 0) {
-        if (/item-upload-info__uploader-name/ &&
-            match($0, /\/details\/[^"]*"/))
-        {
-            uploader_account = substr($0, RSTART + 9, RLENGTH - 10)
-        }
-    }
-    close(iaout)
-
-    if (cols > 0) {
-        print "" >>output
-        print "# Collections" >>output
-        for (i = 1; i <= cols; i++) {
-            label = shorten(col[i], 40)
-            printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
-                label, cgipath, col[i], server, port >>output
-        }
-    }
-
-    print "" >>output
-    print "# Uploaded by" >>output
-    if (length(uploader_account) > 0) {
-        label = shorten(uploader_account, 70)
-        printf "[1|%s|%s/account/%s%%09%s|%s|%s]\n", label, cgipath,
-            uploader_account, uploader_email, server, port >>output
-    } else {
-        label = shorten(uploader_email, 70)
-        printf "[1|%s|%s/search/%%09uploader:%s|%s|%s]\n", label,
-            cgipath, uploader_email, server, port >>output
-    }
-
-    print "" >>output
-    print "# Similar items" >>output
-    printf "[1|View similar items|%s/search/%%09similar:%s|%s|%s]\n",
-        cgipath, item_id, server, port >>output
-
-    print "" >>output
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
-
-    cache_end()
-    unlink(iaout)
-    return
-}
-
-BEGIN {
-    config_init()
-
-    uri_encode_init()
-    cgi_init()
-    main()
-}

ADDED   src/download.m4
Index: src/download.m4
==================================================================
--- /dev/null
+++ src/download.m4
@@ -0,0 +1,103 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/download
+#
+# Show file downloads using either direct http or gopher proxy links
+
+include(src/config.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
+
+function main(     cmd, files, file_size, format, iaout, is_archive,
+      is_proxy, item_server, label, mtime, name, source, url)
+{
+    if (topdir == "download") {
+        is_proxy = 1
+    } else {
+        # topdir == "direct"
+        is_proxy = 0
+    }
+
+    iaout = gettemp()
+
+    url = api_endpoint "/metadata/" search
+    api_request(url, "GET", iaout)
+
+    # format search results as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    dir = ""
+    files = 0
+    delete format
+    item_server = ""
+    delete mtime
+    delete name
+    delete file_size
+    delete source
+
+    while ((cmd | getline) > 0) {
+        if ($1 == ".dir" && $2 == "s") {
+            dir = $3
+        } else if ($1 == ".files[]" && $2 == "o") {
+            files++
+        } else if ($1 == ".files[].format" && $2 == "s") {
+            format[files] = $3
+        } else if ($1 == ".files[].mtime" && $2 == "s") {
+            mtime[files] = $3
+        } else if ($1 == ".files[].name" && $2 == "s") {
+            name[files] = $3
+        } else if ($1 == ".files[].size" && $2 == "s") {
+            file_size[files] = $3
+        } else if ($1 == ".files[].source" && $2 == "s") {
+            source[files] = $3
+        } else if ($1 == ".server" && $2 == "s") {
+            item_server = $3
+        }
+    }
+    close(cmd)
+
+    for (i = 1; i <= files; i++) {
+        label = sprintf("%s %s %s",
+            shorten_left(name[i], 40),
+            strftime("%Y-%m-%d %H:%M", mtime[i]),
+            human_size(file_size[i]))
+        url = sprintf("http://%s%s/%s", item_server, dir, name[i])
+        if (is_proxy) {
+            if (max_bin_size > 0 && file_size[i] > max_bin_size * size_mb) {
+                printf "h%s\tURL:%s\t%s\t%s\n", label, uri_encode(url),
+                    server, port
+            } else {
+                printf "1%s\t%s/links?%s\t%s\t%s\n", label, cgipath,
+                    url, server, port
+            }
+        } else {
+            printf "h%s\tURL:%s\t%s\t%s\n", label, uri_encode(url),
+                server, port
+        }
+        is_archive = detect_archive(url)
+        if (is_archive) {
+            url = sprintf("http://%s/view_archive.php?archive=%s/%s",
+                item_server, dir, name[i])
+            printf "h%s (View Contents)\tURL:%s\t%s\t%s\n",
+                shorten_left(name[i], 40), uri_encode(url),
+                server, port
+        }
+    }
+
+    printf "1Downloads via http\t%s/direct?%s\t%s\t%s\n", cgipath,
+        search, server, port
+
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    util_init()
+    main()
+}

DELETED src/download/index.dcgi.m4
Index: src/download/index.dcgi.m4
==================================================================
--- src/download/index.dcgi.m4
+++ /dev/null
@@ -1,106 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# download/index.dcgi
-#
-# Show file downloads using either direct http or gopher proxy links
-
-include(src/config.awk)
-incl(src/api.awk)
-incl(src/cgi.awk)
-incl(src/util.awk)
-
-function main(cmd, dir, files, file_size, format, iaout, is_archive,
-    is_proxy, item_server, label, mtime, name, source, url)
-{
-    dir = parts[2]
-    item_id = parts[3]
-
-    if (dir == "download") {
-        is_proxy = 1
-    } else {
-        # dir == "direct"
-        is_proxy = 0
-    }
-
-    iaout = gettemp()
-
-    url = api_endpoint "/metadata/" item_id
-    api_request(url, "GET", iaout)
-
-    # format search results as a gopher directory (menu)
-    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
-    FS = "\t"
-    dir = ""
-    files = 0
-    delete format
-    item_server = ""
-    delete mtime
-    delete name
-    delete file_size
-    delete source
-
-    while ((cmd | getline) > 0) {
-        if ($1 == ".dir" && $2 == "s") {
-            dir = $3
-        } else if ($1 == ".files[]" && $2 == "o") {
-            files++
-        } else if ($1 == ".files[].format" && $2 == "s") {
-            format[files] = $3
-        } else if ($1 == ".files[].mtime" && $2 == "s") {
-            mtime[files] = $3
-        } else if ($1 == ".files[].name" && $2 == "s") {
-            name[files] = $3
-        } else if ($1 == ".files[].size" && $2 == "s") {
-            file_size[files] = $3
-        } else if ($1 == ".files[].source" && $2 == "s") {
-            source[files] = $3
-        } else if ($1 == ".server" && $2 == "s") {
-            item_server = $3
-        }
-    }
-    close(cmd)
-
-    for (i = 1; i <= files; i++) {
-        label = sprintf("%s %s %s",
-            shorten_left(name[i], 40),
-            strftime("%Y-%m-%d %H:%M", mtime[i]),
-            human_size(file_size[i]))
-        url = sprintf("http://%s%s/%s", item_server, dir, name[i])
-        if (is_proxy) {
-            if (max_bin_size > 0 && file_size[i] > max_bin_size * size_mb) {
-                printf "[h|%s|URL:%s|%s|%s]\n", label, uri_encode(url),
-                    server, port
-            } else {
-                printf "[1|%s|%s/links/%%09%s|%s|%s]\n", label, cgipath,
-                    url, server, port
-            }
-        } else {
-            printf "[h|%s|URL:%s|%s|%s]\n", label, uri_encode(url),
-                server, port
-        }
-        is_archive = detect_archive(url)
-        if (is_archive) {
-            url = sprintf("http://%s/view_archive.php?archive=%s/%s",
-                item_server, dir, name[i])
-            printf "[h|%s (View Contents)|URL:%s|%s|%s]\n",
-                shorten_left(name[i], 40), uri_encode(url),
-                server, port
-        }
-    }
-
-    printf "[1|Downloads via http|%s/direct/%s|%s|%s]\n", cgipath,
-        item_id, server, port
-
-    unlink(iaout)
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    uri_encode_init()
-    util_init()
-    main()
-}

ADDED   src/gophermap.m4
Index: src/gophermap.m4
==================================================================
--- /dev/null
+++ src/gophermap.m4
@@ -0,0 +1,26 @@
+include(config.m4)dnl
+i        .n.                     |			
+i       /___\          _.---.  \ _ /			
+i       [|||]         (_._ ) )--;_) =-			
+i       [___]           '---'.__,' \			
+i       }-=-{                    |			
+i       |-" |			
+i       |.-"|                p			
+i~^=~^~-|_.-|~^-~^~ ~^~ -^~^~|\ ~^-~^~-			
+i^   .=.| _.|__  ^       ~  /| \			
+i ~ /:. \" _|_/\    ~      /_|__\  ^			
+i.-/::.  |   |""|-._    ^   ~~~~			
+i  `===-'-----'""`  '-.              ~			
+i       PHAROS    __.-'      ^			
+i			
+iGopher proxy to Internet Archive.			
+i			
+7Search	__CGIPATH__/search	__SERVER__	__PORT__
+1Advanced Search	__CGIPATH__/wizard/step1	__SERVER__	__PORT__
+1Books	__DOCPATH__/books/	__SERVER__	__PORT__
+1Video	__DOCPATH__/video/	__SERVER__	__PORT__
+1Audio	__DOCPATH__/audio/	__SERVER__	__PORT__
+1Software	__DOCPATH__/software/	__SERVER__	__PORT__
+1Images	__DOCPATH__/images/	__SERVER__	__PORT__
+0About PHAROS	__DOCPATH__/about.txt	__SERVER__	__PORT__
+hSource Code	URL:https://chiselapp.com/user/bencollver/repository/pharos	__SERVER__	__PORT__

ADDED   src/images/gophermap.m4
Index: src/images/gophermap.m4
==================================================================
--- /dev/null
+++ src/images/gophermap.m4
@@ -0,0 +1,14 @@
+include(config.m4)dnl
+# Images
+
+[1|All Images|__CGIPATH__/search/sortweek desc/%09mediatype:image|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:image|__SERVER__|__PORT__]
+[1|Metropolitan Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:metropolitanmuseumofart-gallery|__SERVER__|__PORT__]
+[1|Cleveland Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:clevelandart|__SERVER__|__PORT__]
+[1|Flickr Commons|__CGIPATH__/search/sortweek desc/%09collection:flickrcommons|__SERVER__|__PORT__]
+[1|Occupy Wallstreet Flickr|__CGIPATH__/search/sortweek desc/%09collection:flickr-ows|__SERVER__|__PORT__]
+[1|Cover Art|__CGIPATH__/search/sortweek desc/%09collection:coverartarchive|__SERVER__|__PORT__]
+[1|USGS Maps|__CGIPATH__/search/sortweek desc/%09collection:maps_usgs|__SERVER__|__PORT__]
+[1|NASA Images|__CGIPATH__/search/sortweek desc/%09collection:nasa|__SERVER__|__PORT__]
+[1|Solar System Collection|__CGIPATH__/search/sortweek desc/%09collection:solarsystemcollection|__SERVER__|__PORT__]
+[1|Ames Research Center|__CGIPATH__/search/sortweek desc/%09collection:amesresearchcenterimagelibrary|__SERVER__|__PORT__]

DELETED src/images/index.gph.m4
Index: src/images/index.gph.m4
==================================================================
--- src/images/index.gph.m4
+++ /dev/null
@@ -1,14 +0,0 @@
-include(config.m4)dnl
-# Images
-
-[1|All Images|__CGIPATH__/search/sortweek desc/%09mediatype:image|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:image|__SERVER__|__PORT__]
-[1|Metropolitan Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:metropolitanmuseumofart-gallery|__SERVER__|__PORT__]
-[1|Cleveland Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:clevelandart|__SERVER__|__PORT__]
-[1|Flickr Commons|__CGIPATH__/search/sortweek desc/%09collection:flickrcommons|__SERVER__|__PORT__]
-[1|Occupy Wallstreet Flickr|__CGIPATH__/search/sortweek desc/%09collection:flickr-ows|__SERVER__|__PORT__]
-[1|Cover Art|__CGIPATH__/search/sortweek desc/%09collection:coverartarchive|__SERVER__|__PORT__]
-[1|USGS Maps|__CGIPATH__/search/sortweek desc/%09collection:maps_usgs|__SERVER__|__PORT__]
-[1|NASA Images|__CGIPATH__/search/sortweek desc/%09collection:nasa|__SERVER__|__PORT__]
-[1|Solar System Collection|__CGIPATH__/search/sortweek desc/%09collection:solarsystemcollection|__SERVER__|__PORT__]
-[1|Ames Research Center|__CGIPATH__/search/sortweek desc/%09collection:amesresearchcenterimagelibrary|__SERVER__|__PORT__]

DELETED src/index.gph.m4
Index: src/index.gph.m4
==================================================================
--- src/index.gph.m4
+++ /dev/null
@@ -1,26 +0,0 @@
-include(config.m4)dnl
-        .n.                     |
-       /___\          _.---.  \ _ /
-       [|||]         (_._ ) )--;_) =-
-       [___]           '---'.__,' \
-       }-=-{                    |
-       |-" |
-       |.-"|                p
-~^=~^~-|_.-|~^-~^~ ~^~ -^~^~|\ ~^-~^~-
-^   .=.| _.|__  ^       ~  /| \
- ~ /:. \" _|_/\    ~      /_|__\  ^
-.-/::.  |   |""|-._    ^   ~~~~
-  `===-'-----'""`  '-.              ~
-       PHAROS    __.-'      ^
-
-Gopher proxy to Internet Archive.
-
-[7|Search|__CGIPATH__/search/|__SERVER__|__PORT__]
-[1|Advanced Search|__CGIPATH__/wizard/step1|__SERVER__|__PORT__]
-[1|Books|__CGIPATH__/books/|__SERVER__|__PORT__]
-[1|Video|__CGIPATH__/video/|__SERVER__|__PORT__]
-[1|Audio|__CGIPATH__/audio/|__SERVER__|__PORT__]
-[1|Software|__CGIPATH__/software/|__SERVER__|__PORT__]
-[1|Images|__CGIPATH__/images/|__SERVER__|__PORT__]
-[0|About PHAROS|__CGIPATH__/about.txt|__SERVER__|__PORT__]
-[h|Source Code|URL:https://chiselapp.com/user/bencollver/repository/pharos|__SERVER__|__PORT__]

ADDED   src/list.m4
Index: src/list.m4
==================================================================
--- /dev/null
+++ src/list.m4
@@ -0,0 +1,229 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/list
+#
+# Show a specific list
+
+include(src/config.awk)
+incl(src/api.awk)
+incl(src/cache.awk)
+incl(src/cgi.awk)
+incl(src/sqlite.awk)
+incl(src/util.awk)
+
+function main(     acct, client_url, cmd, count, creator, iaout, id,
+    is_private, items, label, list_id, name, name_slug, numfound,
+    order, order_name, order_names, order_param, output, page, pages,
+    prefix, rows, sort_param, signature, str, title, type, url,
+    user_query)
+{
+    order_names["creator"] = "creatorSorter"
+    order_names["date"] = "date"
+    order_names["title"] = "titleSorter"
+    order_names["week"] = "week"
+
+    rows = 15
+    page = 1
+    order = ""
+    sort_param = ""
+
+    # parse out page number and sort order
+    numfound = 1
+    while (numfound == 1) {
+        if (match(search, /\/rows[0-9][0-9]*$/)) {
+            rows = substr(search, RSTART + 5)
+            search = substr(search, 1, RSTART - 1)
+        } else if (match(search, /\/page[0-9][0-9]*$/)) {
+            page = substr(search, RSTART + 5)
+            search = substr(search, 1, RSTART - 1)
+        } else if (match(search, /\/sort[^\/]*$/)) {
+            if (length(order) == 0) {
+                sort_param = substr(search, RSTART + 1)
+                order = substr(sort_param, 5)
+            }
+            search = substr(search, 1, RSTART - 1)
+        } else {
+            numfound = 0
+        }
+    }
+
+    split(search, parts, "/")
+    acct = parts[1]
+    list_id = parts[2]
+
+    signature = sprintf("list/%s/%s", input, search)
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
+
+    output = cache_begin()
+    iaout = gettemp()
+
+    url = api_ssl_endpoint "/services/users/" acct "/lists/" list_id
+    api_request(url, "GET", iaout)
+
+    # fetch identifiers of list members
+
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    id = ""
+    is_private = 0
+    name = ""
+    numfound = 0
+    user_query = ""
+
+    while ((cmd | getline) > 0) {
+        if ($1 == ".value.list_name" && $2 == "s") {
+            name = $3
+            is_private = 0
+        } else if ($1 == ".value.is_private" && $2 == "b") {
+            if ($3 == "true") {
+                is_private = 1
+            }
+        } else if ($1 == ".value.members[].identifier" && $2 == "s") {
+            if (!is_private) {
+                id = $3
+                numfound++
+                if (length(user_query) == 0) {
+                    user_query = id
+                } else {
+                    user_query = user_query "+OR+" id
+                }
+            }
+        }
+    }
+    close(cmd)
+    unlink(iaout)
+
+    # get metadata of list member items
+
+    name_slug = uri_encode(name)
+    gsub(/%20/, "-", name_slug)
+    client_url = api_ssl_endpoint "/details/" acct "/lists/" list_id \
+        "/" name_slug
+    order_param = ""
+    if (length(order) > 0) {
+        split(order, parts, " ")
+        order_name = order_names[parts[1]]
+        if (length(order_name) > 0) {
+            if (parts[2] == "desc") {
+                client_url = client_url "?-" parts[1]
+            } else {
+                client_url = client_url "?" parts[1]
+            }
+            order_param = "&sort=" uri_encode(order_name ":" parts[2])
+        }
+    }
+    url = api_ssl_endpoint "/services/search/beta/page_production/" \
+        "?user_query=identifier:(" user_query ")"                   \
+        "&hits_per_page=" rows                                      \
+        "&page=" page                                               \
+        order_param                                                 \
+        "&aggregations=false"                                       \
+        "&client_url=" client_url
+    api_request(url, "GET", iaout)
+
+    pages = int(numfound / rows)
+    if (numfound % rows != 0) {
+        pages++
+    }
+
+    # format as a gopher directory (menu)
+
+    print acct "'s Lists" >>output
+    print "" >>output
+    printf "# List: %s, page %d of %d\n", name, page, pages >>output
+    print "" >>output
+
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    count = 0
+    creator = ""
+    id = ""
+    title = ""
+    type = ""
+    while ((cmd | getline) > 0) {
+        if ($1 == ".response.body.hits.hits[].fields.creator[]" &&
+            $2 == "s" && length(creator) == 0)
+        {
+            creator = $3
+        } else if ($1 == ".response.body.hits.hits[].fields.identifier" &&
+            $2 == "s")
+        {
+            id = $3
+        } else if ($1 == ".response.body.hits.hits[].fields.mediatype" &&
+            $2 == "s")
+        {
+            type = $3
+        } else if ($1 == ".response.body.hits.hits[].fields.title" &&
+            $2 == "s")
+        {
+            title = $3
+        } else if ($1 == ".response.body.hits.hits[]._score") {
+            # the _score field happens to be toward the end of each item
+            if (length(title) > 0) {
+                if (length(creator) > 0) {
+                    label = sprintf("[%s] %s by %s", mediatype[type],
+                        shorten(title, 40), shorten(creator, 18))
+                } else {
+                    label = sprintf("[%s] %s", mediatype[type],
+                        shorten(title, 58))
+                }
+                printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath, id,
+                    server, port >>output
+                count++
+            }
+            creator = ""
+            descr = ""
+            id = ""
+            type = ""
+        }
+    }
+    close(cmd)
+
+    print "" >>output
+
+    # only show "page back" if the user is past page 1
+    if (page > 1) {
+        prefix = sprintf("/page%d/rows%d/%s", page - 1, rows, sort_param)
+        printf "1[<<] Page %d\t%s/list?%s/%d%s\t%s\t%s\n",
+            page - 1, cgipath, acct, list_id, prefix, server,
+            port >>output
+    }
+
+    # only show "next page" if the current page is completely full
+    if (count == rows) {
+        prefix = sprintf("/page%d/rows%d/%s", page + 1, rows, sort_param)
+        printf "1[>>] Page %d\t%s/list?%s/%d%s\t%s\t%s\n",
+            page + 1, cgipath, acct, list_id, prefix, server,
+            port >>output
+    }
+
+    # only show "sort" if there's more than one item to sort
+    if (numfound > 1) {
+        printf "1[^v] Sort\t%s/listsort?%s/%d\t%s\t%s\n", cgipath,
+            acct, list_id, server, port >>output
+    }
+
+    printf "1Account %s\t%s/account?%s\t%s\t%s\n", acct, cgipath,
+        acct, server, port >>output
+ 
+    print "" >>output
+    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+
+    cache_end()
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    util_init()
+    main()
+}

DELETED src/list/index.dcgi.m4
Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ /dev/null
@@ -1,220 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# list/index.dcgi
-#
-# Show a specific list
-
-include(src/config.awk)
-incl(src/api.awk)
-incl(src/cache.awk)
-incl(src/cgi.awk)
-incl(src/sqlite.awk)
-incl(src/util.awk)
-
-function main(     acct, client_url, cmd, count, creator, iaout, id,
-    is_private, items, label, list_id, name, name_slug, numfound,
-    order, order_name, order_names, order_param, output, page, pages,
-    rows, query, sort_param, signature, str, title, type, url)
-{
-    order_names["creator"] = "creatorSorter"
-    order_names["date"] = "date"
-    order_names["title"] = "titleSorter"
-    order_names["week"] = "week"
-
-    rows = 15
-    page = 1
-    order = ""
-    sort_param = ""
-
-    # parse out page number and sort order
-    for (i in parts) {
-        if (parts[i] ~ /^rows[0-9][0-9]*$/) {
-            rows = substr(parts[i], 5)
-        } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
-            page = substr(parts[i], 5)
-        } else if (parts[i] ~ /^sort/) {
-            if (length(order) == 0) {
-                sort_param = parts[i]
-                order = substr(parts[i], 5)
-            }
-        }
-    }
-
-    split(search, parts, "/")
-    acct = parts[1]
-    list_id = parts[2]
-
-    signature = sprintf("list/%s/%s", input, search)
-    str = cache_init(signature)
-    if (length(str) > 0) {
-        print str
-        return
-    }
-
-    output = cache_begin()
-    iaout = gettemp()
-
-    url = api_ssl_endpoint "/services/users/" acct "/lists/" list_id
-    api_request(url, "GET", iaout)
-
-    # fetch identifiers of list members
-
-    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
-    FS = "\t"
-    id = ""
-    is_private = 0
-    name = ""
-    numfound = 0
-    query = ""
-
-    while ((cmd | getline) > 0) {
-        if ($1 == ".value.list_name" && $2 == "s") {
-            name = $3
-            is_private = 0
-        } else if ($1 == ".value.is_private" && $2 == "b") {
-            if ($3 == "true") {
-                is_private = 1
-            }
-        } else if ($1 == ".value.members[].identifier" && $2 == "s") {
-            if (!is_private) {
-                id = $3
-                numfound++
-                if (length(query) == 0) {
-                    query = id
-                } else {
-                    query = query "+OR+" id
-                }
-            }
-        }
-    }
-    close(cmd)
-    unlink(iaout)
-
-    # get metadata of list member items
-
-    name_slug = uri_encode(name)
-    gsub(/%20/, "-", name_slug)
-    client_url = api_ssl_endpoint "/details/" acct "/lists/" list_id \
-        "/" name_slug
-    order_param = ""
-    if (length(order) > 0) {
-        split(order, parts, " ")
-        order_name = order_names[parts[1]]
-        if (length(order_name) > 0) {
-            if (parts[2] == "desc") {
-                client_url = client_url "?-" parts[1]
-            } else {
-                client_url = client_url "?" parts[1]
-            }
-            order_param = "&sort=" uri_encode(order_name ":" parts[2])
-        }
-    }
-    url = api_ssl_endpoint "/services/search/beta/page_production/" \
-        "?user_query=identifier:(" query ")"                        \
-        "&hits_per_page=" rows                                      \
-        "&page=" page                                               \
-        order_param                                                 \
-        "&aggregations=false"                                       \
-        "&client_url=" client_url
-    api_request(url, "GET", iaout)
-
-    pages = int(numfound / rows)
-    if (numfound % rows != 0) {
-        pages++
-    }
-
-    # format as a gopher directory (menu)
-
-    print acct "'s Lists" >>output
-    print "" >>output
-    printf "# List: %s, page %d of %d\n", name, page, pages >>output
-    print "" >>output
-
-    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
-    FS = "\t"
-    count = 0
-    creator = ""
-    id = ""
-    title = ""
-    type = ""
-    while ((cmd | getline) > 0) {
-        if ($1 == ".response.body.hits.hits[].fields.creator[]" &&
-            $2 == "s" && length(creator) == 0)
-        {
-            creator = $3
-        } else if ($1 == ".response.body.hits.hits[].fields.identifier" &&
-            $2 == "s")
-        {
-            id = $3
-        } else if ($1 == ".response.body.hits.hits[].fields.mediatype" &&
-            $2 == "s")
-        {
-            type = $3
-        } else if ($1 == ".response.body.hits.hits[].fields.title" &&
-            $2 == "s")
-        {
-            title = $3
-        } else if ($1 == ".response.body.hits.hits[]._score") {
-            # the _score field happens to be toward the end of each item
-            if (length(title) > 0) {
-                if (length(creator) > 0) {
-                    label = sprintf("[%s] %s by %s", mediatype[type],
-                        gph_encode(shorten(title, 40)), shorten(creator, 18))
-                } else {
-                    label = sprintf("[%s] %s", mediatype[type],
-                        gph_encode(shorten(title, 58)))
-                }
-                printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath, id,
-                    server, port >>output
-                count++
-            }
-            creator = ""
-            descr = ""
-            id = ""
-            type = ""
-        }
-    }
-    close(cmd)
-
-    print "" >>output
-
-    # only show "page back" if the user is past page 1
-    if (page > 1) {
-        printf "[1|[<<] Page %d|%s/list/page%d/rows%d/%s%%09%s/%d|%s|%s]\n",
-            page - 1, cgipath, page - 1, rows, sort_param,
-            acct, list_id, server, port >>output
-    }
-
-    # only show "next page" if the current page is completely full
-    if (count == rows) {
-        printf "[1|[>>] Page %d|%s/list/page%d/rows%d/%s%%09%s/%d|%s|%s]\n",
-            page + 1, cgipath, page + 1, rows, sort_param,
-            acct, list_id, server, port >>output
-    }
-
-    # only show "sort" if there's more than one item to sort
-    if (numfound > 1) {
-        printf "[1|[^v] Sort|%s/listsort/%%09%s/%d|%s|%s]\n", cgipath,
-            acct, list_id, server, port >>output
-    }
-
-    printf "[1|Account %s|%s/account/%s|%s|%s]\n", acct, cgipath,
-        acct, server, port >>output
- 
-    print "" >>output
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
-
-    cache_end()
-    unlink(iaout)
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    uri_encode_init()
-    util_init()
-    main()
-}

ADDED   src/lists.m4
Index: src/lists.m4
==================================================================
--- /dev/null
+++ src/lists.m4
@@ -0,0 +1,113 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/lists
+#
+# Show a list of a user's lists
+
+include(src/config.awk)
+incl(src/api.awk)
+incl(src/cache.awk)
+incl(src/cgi.awk)
+incl(src/sqlite.awk)
+incl(src/util.awk)
+
+function main(     cmd, count, fields, iaout, i, id, is_private, item,
+    item_count, item_id, label, name, output, record, records,
+    signature, str, url)
+{
+    signature = sprintf("%s/lists", search)
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
+
+    output = cache_begin()
+    iaout = gettemp()
+
+    url = api_ssl_endpoint "/services/users/" search "/lists"
+    api_request(url, "GET", iaout)
+
+    # format list as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    count = 0
+    delete fields[0]
+    id = 0
+    is_private = 0
+    item = ""
+    item_count = 0
+    item_id = ""
+    name = ""
+    record = ""
+    delete records[0]
+
+    while ((cmd | getline) > 0) {
+        if ($1 == ".value[]" && $2 == "o") {
+            # add information for previous list
+            if (!is_private && length(name) > 0 && item_count > 0) {
+                label = shorten_left(name, 50)
+                item = sprintf("1%4d Items: %-50s\t%s/list?%s/%d\t%s\t%s", 
+                    item_count, label, cgipath, search, id, server, port)
+                record = label "\v" id "\v" item
+                count++
+                records[count] = record
+            }
+        } else if ($1 == ".value[].list_name" && $2 == "s") {
+            name = $3
+            id = 0
+            is_private = 0
+            item_count = 0
+        } else if ($1 == ".value[].is_private" && $2 == "b") {
+            if ($3 == "true") {
+                is_private = 1
+            }
+        } else if ($1 == ".value[].id" && $2 == "n") {
+            id = $3
+        } else if ($1 == ".value[].members[].identifier" && $2 == "s") {
+            item_count++
+        }
+    }
+    close(cmd)
+
+    # add information for previous list
+    if (!is_private && length(name) > 0 && item_count > 0) {
+        label = shorten_left(name, 50)
+        item = sprintf("1%4d Items: %-50s\t%s/list?%s/%d\t%s\t%s",
+            item_count, label, cgipath, search, id, server, port)
+        record = label "\v" id "\v" item
+        count++
+        records[count] = record
+    }
+
+    # sort lists by label and id
+    if (count > 0) {
+        hsort(records, count)
+    }
+
+    print search "'s Lists" >>output
+    print "" >>output
+
+    for (i = 1; i <= count; i++) {
+        record = records[i]
+        split(record, fields, /\v/)
+        item = fields[3]
+        print item >>output
+    }
+
+    print "" >>output
+    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
+
+    cache_end()
+    unlink(iaout)
+    return
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    main()
+}

DELETED src/lists/index.dcgi.m4
Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ /dev/null
@@ -1,111 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# lists/index.dcgi
-#
-# Show a list of a user's lists
-
-include(src/config.awk)
-incl(src/api.awk)
-incl(src/cache.awk)
-incl(src/cgi.awk)
-incl(src/sqlite.awk)
-incl(src/util.awk)
-
-function main(     cmd, count, fields, iaout, i, id, is_private, item,
-    item_count, item_id, label, name, output, record, records,
-    signature, str, url)
-{
-    signature = sprintf("%s/lists", search)
-    str = cache_init(signature)
-    if (length(str) > 0) {
-        print str
-        return
-    }
-
-    output = cache_begin()
-    iaout = gettemp()
-
-    url = api_ssl_endpoint "/services/users/" search "/lists"
-    api_request(url, "GET", iaout)
-
-    # format list as a gopher directory (menu)
-    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
-    FS = "\t"
-    count = 0
-    delete fields[0]
-    id = 0
-    is_private = 0
-    item = ""
-    item_count = 0
-    item_id = ""
-    name = ""
-    record = ""
-    delete records[0]
-
-    while ((cmd | getline) > 0) {
-        if ($1 == ".value[]" && $2 == "o") {
-            # add information for previous list
-            if (!is_private && length(name) > 0 && item_count > 0) {
-                label = shorten_left(name, 50)
-                item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", 
-                    item_count, label, cgipath, search, id, server, port)
-                record = label "\t" id "\t" item
-                count++
-                records[count] = record
-            }
-        } else if ($1 == ".value[].list_name" && $2 == "s") {
-            name = $3
-            id = 0
-            is_private = 0
-            item_count = 0
-        } else if ($1 == ".value[].is_private" && $2 == "b") {
-            if ($3 == "true") {
-                is_private = 1
-            }
-        } else if ($1 == ".value[].id" && $2 == "n") {
-            id = $3
-        } else if ($1 == ".value[].members[].identifier" && $2 == "s") {
-            item_count++
-        }
-    }
-    close(cmd)
-
-    # add information for previous list
-    if (!is_private && length(name) > 0 && item_count > 0) {
-        label = shorten_left(name, 50)
-        item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]",
-            item_count, label, cgipath, search, id, server, port)
-        record = label "\t" id "\t" item
-        count++
-        records[count] = record
-    }
-
-    # sort lists by label and id
-    hsort(records, count)
-
-    print search "'s Lists" >>output
-    print "" >>output
-
-    for (i = 1; i <= count; i++) {
-        record = records[i]
-        split(record, fields, /\t/)
-        item = fields[3]
-        print item >>output
-    }
-
-    print "" >>output
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
-
-    cache_end()
-    unlink(iaout)
-    return
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    uri_encode_init()
-    main()
-}

ADDED   src/listsort.m4
Index: src/listsort.m4
==================================================================
--- /dev/null
+++ src/listsort.m4
@@ -0,0 +1,54 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/listsort
+#
+# Change list sort order
+
+include(src/config.awk)
+incl(src/cgi.awk)
+
+function main(     acct, i, lbl, list_id, opt) {
+    lbl[1] = "Relevance"
+    opt[1] = ""
+    lbl[2] = "Weekly views [^]"
+    opt[2] = "week asc"
+    lbl[3] = "Weekly views [v]"
+    opt[3] = "week desc"
+    lbl[4] = "Title [^]"
+    opt[4] = "title asc"
+    lbl[5] = "Title [v]"
+    opt[5] = "title desc"
+    lbl[6] = "Date published [^]"
+    opt[6] = "date asc"
+    lbl[7] = "Date published [v]"
+    opt[7] = "date desc"
+    lbl[8] = "Creator [^]"
+    opt[8] = "creator asc"
+    lbl[9] = "Creator [v]"
+    opt[9] = "creator desc"
+
+    split(search, parts, "/")
+    acct = parts[1]
+    list_id = parts[2]
+
+    print "# Sort by"
+    print ""
+    for (i = 1; i < 10; i++) {
+        if (length(opt[i]) == 0) {
+            printf "1%s\t%s/list?%s/%d\t%s\t%s\n", lbl[i], cgipath,
+                acct, list_id, server, port
+        } else {
+            printf "1%s\t%s/list?%s/%d/sort%s\t%s\t%s\n", lbl[i],
+                cgipath, acct, list_id, opt[i], server, port
+        }
+    }
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

DELETED src/listsort/index.dcgi.m4
Index: src/listsort/index.dcgi.m4
==================================================================
--- src/listsort/index.dcgi.m4
+++ /dev/null
@@ -1,54 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# listsort/index.dcgi
-#
-# Change list sort order
-
-include(src/config.awk)
-incl(src/cgi.awk)
-
-function main(     acct, i, lbl, list_id, opt) {
-    lbl[1] = "Relevance"
-    opt[1] = ""
-    lbl[2] = "Weekly views [^]"
-    opt[2] = "week asc"
-    lbl[3] = "Weekly views [v]"
-    opt[3] = "week desc"
-    lbl[4] = "Title [^]"
-    opt[4] = "title asc"
-    lbl[5] = "Title [v]"
-    opt[5] = "title desc"
-    lbl[6] = "Date published [^]"
-    opt[6] = "date asc"
-    lbl[7] = "Date published [v]"
-    opt[7] = "date desc"
-    lbl[8] = "Creator [^]"
-    opt[8] = "creator asc"
-    lbl[9] = "Creator [v]"
-    opt[9] = "creator desc"
-
-    split(search, parts, "/")
-    acct = parts[1]
-    list_id = parts[2]
-
-    print "# Sort by"
-    print ""
-    for (i = 1; i < 10; i++) {
-        if (length(opt[i]) == 0) {
-            printf "[1|%s|%s/list/%%09%s/%d|%s|%s]\n",
-                lbl[i], cgipath, acct, list_id, server, port
-        } else {
-            printf "[1|%s|%s/list/sort%s%%09%s/%d|%s|%s]\n",
-                lbl[i], cgipath, opt[i], acct, list_id, server, port
-        }
-    }
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    main()
-}

ADDED   src/raw.m4
Index: src/raw.m4
==================================================================
--- /dev/null
+++ src/raw.m4
@@ -0,0 +1,46 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/debug
+#
+# Show HTTP headers from curl fetching a URL
+#
+# __CGIPATH__/links
+#
+# Show download links, plus links scraped from HTML document
+#
+# __CGIPATH__/raw
+#
+# Show raw bytes from binary document
+#
+# __CGIPATH__/text
+#
+# Show text content scraped from HTML document
+
+include(src/config.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
+incl(src/web.awk)
+
+function main() {
+    if (path == "/debug") {
+        dump(search, TYPE_HEADERS)
+    } else if (path == "/raw") {
+        dump(search, TYPE_RAW)
+    } else if (path == "/text") {
+        dump(search, TYPE_TEXT)
+    } else if (path == "/links") {
+        dump(search, TYPE_LINKS)
+    } 
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    web_init()
+    main()
+}

DELETED src/raw/index.cgi.m4
Index: src/raw/index.cgi.m4
==================================================================
--- src/raw/index.cgi.m4
+++ /dev/null
@@ -1,46 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# debug/index.cgi
-#
-# Show HTTP headers from curl fetching a URL
-#
-# links/index.dcgi
-#
-# Show download links, plus links scraped from HTML document
-#
-# raw/index.cgi
-#
-# Show raw bytes from binary document
-#
-# text/index.cgi
-#
-# Show text content scraped from HTML document
-
-include(src/config.awk)
-incl(src/api.awk)
-incl(src/cgi.awk)
-incl(src/util.awk)
-incl(src/web.awk)
-
-function main() {
-    if (path == "/debug/") {
-        dump(search, TYPE_HEADERS)
-    } else if (path == "/raw/") {
-        dump(search, TYPE_RAW)
-    } else if (path == "/text/") {
-        dump(search, TYPE_TEXT)
-    } else if (path == "/links/") {
-        dump(search, TYPE_LINKS)
-    } 
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    uri_encode_init()
-    web_init()
-    main()
-}

ADDED   src/search.m4
Index: src/search.m4
==================================================================
--- /dev/null
+++ src/search.m4
@@ -0,0 +1,261 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/search
+#
+# Show search results
+
+include(src/config.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
+
+function main(search,     cmd, count, creator, descr, field, fields, i,
+    iaout, id, item, items, jsout, label, numfound, order,
+    order_names, page, prefix, rows, searchstr, sort_param, str,
+    title, type, url)
+{
+    order_names["addeddate"] = "addeddate"
+    order_names["collection_size"] = "collection_size"
+    order_names["createddate"] = "createddate"
+    order_names["creator"] = "creatorSorter"
+    order_names["date"] = "date"
+    order_names["downloads"] = "downloads"
+    order_names["nav_order"] = "nav_order"
+    order_names["random"] = "random"
+    order_names["title"] = "titleSorter"
+    order_names["week"] = "week"
+
+    rows = 15
+    page = 1
+    order = ""
+
+    # parse out page number and sort orders
+    numfound = 1
+    while (numfound == 1) {
+        if (match(search, /\/rows[0-9][0-9]*$/)) {
+            rows = substr(search, RSTART + 5)
+            search = substr(search, 1, RSTART - 1)
+        } else if (match(search, /\/page[0-9][0-9]*$/)) {
+            page = substr(search, RSTART + 5)
+            search = substr(search, 1, RSTART - 1)
+        } else if (match(search, /\/sort[^\/]*$/)) {
+            if (length(order) == 0) {
+                sort_param = substr(search, RSTART + 1)
+                order = substr(sort_param, 5)
+            }
+            search = substr(search, 1, RSTART - 1)
+        } else {
+            numfound = 0
+        }
+    }
+
+    iaout = gettemp()
+    jsout = gettemp()
+
+    # special case for when the search term is an archive.org details URL
+    if (match(search, /^https:\/\/(www\.)?archive\.org\/details\//)) {
+        id = substr(search, RLENGTH + 1)
+        search = "identifier:" id
+    }
+
+    # special case to list similar items
+    if (match(search, /^similar:/)) {
+        id = substr(search, RLENGTH + 1)
+
+        url = api_endpoint "/details/" id "?RelatedItemsGet=1"
+        api_request(url, "GET", iaout)
+    
+        # scrape similar items
+        FS = "\t"
+        items = 0
+        delete item
+        str = ""
+        while ((getline <iaout) > 0) {
+            if (match($0, /<a href="\/details\/[^"]*"/)) {
+                str = substr($0, RSTART + 18, RLENGTH - 19)
+                items++
+                item[items] = str
+            }
+        }
+        close(iaout)
+
+        # truncate temporary file for re-use
+        printf "" >iaout
+        close(iaout)
+
+        for (i = 1; i <= items; i++) {
+            if (i == 1) {
+                search = "identifier:" item[i]
+            } else {
+                search = search "%20OR%20identifier:" item[i]
+            }
+        }
+    }
+
+    # remove quotes from search string, since it gets quoted later
+    gsub(/"/, "", search)
+
+    # default sort orders if none were specified
+    if (length(order) == 0) {
+        if (search == "mediatype:collection" ||
+            search == "mediatype:(collection)")
+        {
+            order = "collection_size desc"
+            sort_param = "sort" order
+        } else {
+            order = "nav_order desc"
+            sort_param = "sort" order
+        }
+    }
+
+    # get search results
+
+    field[1] = "creator"
+    field[2] = "description"
+    field[3] = "identifier"
+    field[4] = "mediatype"
+    field[5] = "title"
+    fields = 5
+
+    # remove anyfield, a hobgoblin of consistency
+    searchstr = search
+    gsub(/anyfield:/, "", searchstr)
+
+    url = sprintf("%s/advancedsearch.php?q=%s&output=json&rows=%d&page=%d",
+        api_endpoint, searchstr, rows, page)
+    if (length(order) > 0) {
+        split(order, parts, " ")
+        url = url sprintf("&sort%%5B0%%5D=%s %s", order_names[parts[1]],
+            parts[2])
+    }
+    for (i = 1; i <= fields; i++) {
+        url = url sprintf("&fl%%5B%d%%5D=%s", i - 1, field[i])
+    }
+    api_request(url, "GET", iaout)
+
+    cmd = sprintf("%s <%s >%s 2>&1", cmd_json2tsv, iaout, jsout)
+    system(cmd)
+
+    numfound = 0
+    FS = "\t"
+    while ((getline <jsout) > 0) {
+        if ($1 == ".response.numFound" && $2 == "n") {
+            numfound = $3
+        }
+    }
+    close(jsout)
+
+    if (search ~ /^@/) {
+        numfound++
+    }
+    if (numfound == 0) {
+        print "Your search did not match any items in the Archive."
+        print "Try different keywords or a more general search."
+        print ""
+        printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port
+        unlink(jsout)
+        unlink(iaout)
+        return
+    } else {
+        pages = int(numfound / rows)
+        if (numfound % rows != 0) {
+            pages++
+        }
+        printf "# %s search results, page %d of %d\n", numfound,
+            page, pages
+        print ""
+    }
+
+    # format search results as a gopher directory (menu)
+    FS = "\t"
+    creator = ""
+    descr = ""
+    id = ""
+    title = ""
+    type = ""
+    count = 0
+
+    if (search ~ /^@/) {
+        printf "1Account %s\t%s/account?%s\t%s\t%s\n", search, cgipath,
+           search, server, port
+    }
+
+    while ((getline <jsout) > 0) {
+        if ($1 == ".response.docs[].creator" && $2 == "s") {
+            creator = $3
+        } else if ($1 == ".response.docs[].description" && $2 == "s") {
+            descr = $3
+        } else if ($1 == ".response.docs[].identifier" && $2 == "s") {
+            id = $3
+        } else if ($1 == ".response.docs[].mediatype" && $2 == "s") {
+            type = $3
+        } else if ($1 == ".response.docs[].title" && $2 == "s") {
+            # the title field happens to be toward the end of each item
+            title = $3
+            count++
+            if (length(creator) > 0) {
+                label = sprintf("[%s] %s by %s", mediatype[type],
+                    shorten(title, 40), shorten(creator, 18))
+            } else {
+                label = sprintf("[%s] %s", mediatype[type],
+                    shorten(title, 58))
+            }
+            if (type == "collection") {
+                printf "1%s\t%s/search?collection:(%s)\t%s\t%s\n",
+                    label, cgipath, id, server, port
+            } else {
+                printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath,
+                   id, server, port
+            }
+            creator = ""
+            descr = ""
+            id = ""
+            type = ""
+        }
+    }
+    close(jsout)
+
+    print ""
+
+    # only show "page back" if the user is past page 1
+    if (page > 1) {
+        prefix = sprintf("/page%d/rows%d/%s", page - 1, rows, sort_param)
+        printf "1[<<] Page %d\t%s/search?%s%s\t%s\t%s\n",
+            page - 1, cgipath, search, prefix, server, port
+    }
+
+    # only show "next page" if the current page is completely full
+    if (count == rows) {
+        prefix = sprintf("/page%d/rows%d/%s", page + 1, rows, sort_param)
+        printf "1[>>] Page %d\t%s/search?%s%s\t%s\t%s\n",
+            page + 1, cgipath, search, prefix, server, port
+    }
+
+    # only show "sort" if there's more than one item to sort
+    if (count > 1) {
+        printf "1[^v] Sort\t%s/sort?%s\t%s\t%s\n", cgipath,
+            search, server, port
+    }
+
+    # only show "search within list" if there's multiple pages of results
+    if (numfound > rows) {
+        printf "1[\\/] Filter results\t%s/wizard/step1?%s\t%s\t%s\n",
+            cgipath, search, server, port
+    }
+
+    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port
+
+    unlink(jsout)
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    util_init()
+    main(search)
+}

DELETED src/search/index.dcgi.m4
Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ /dev/null
@@ -1,255 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# search/index.dcgi
-#
-# Show search results
-
-include(src/config.awk)
-incl(src/api.awk)
-incl(src/cgi.awk)
-incl(src/util.awk)
-
-function main(search,     cmd, count, creator, descr, field, fields, i,
-    iaout, id, item, items, jsout, label, numfound, order,
-    order_names, page, rows, searchstr, sort_param, str, title, type,
-    url)
-{
-    order_names["addeddate"] = "addeddate"
-    order_names["collection_size"] = "collection_size"
-    order_names["createddate"] = "createddate"
-    order_names["creator"] = "creatorSorter"
-    order_names["date"] = "date"
-    order_names["downloads"] = "downloads"
-    order_names["nav_order"] = "nav_order"
-    order_names["random"] = "random"
-    order_names["title"] = "titleSorter"
-    order_names["week"] = "week"
-
-    rows = 15
-    page = 1
-    order = ""
-
-    # parse out page number and sort orders
-    for (i in parts) {
-        if (parts[i] ~ /^rows[0-9][0-9]*$/) {
-            rows = substr(parts[i], 5)
-        } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
-            page = substr(parts[i], 5)
-        } else if (parts[i] ~ /^sort/) {
-            if (length(order) == 0) {
-                sort_param = parts[i]
-                order = substr(parts[i], 5)
-            }
-        }
-    }
-
-    iaout = gettemp()
-    jsout = gettemp()
-
-    # special case for when the search term is an archive.org details URL
-    if (match(search, /^https:\/\/(www\.)?archive\.org\/details\//)) {
-        id = substr(search, RLENGTH + 1)
-        search = "identifier:" id
-    }
-
-    # special case to list similar items
-    if (match(search, /^similar:/)) {
-        id = substr(search, RLENGTH + 1)
-
-        url = api_endpoint "/details/" id "?RelatedItemsGet=1"
-        api_request(url, "GET", iaout)
-    
-        # scrape similar items
-        FS = "\t"
-        items = 0
-        delete item
-        str = ""
-        while ((getline <iaout) > 0) {
-            if (match($0, /<a href="\/details\/[^"]*"/)) {
-                str = substr($0, RSTART + 18, RLENGTH - 19)
-                items++
-                item[items] = str
-            }
-        }
-        close(iaout)
-
-        # truncate temporary file for re-use
-        printf "" >iaout
-        close(iaout)
-
-        for (i = 1; i <= items; i++) {
-            if (i == 1) {
-                search = "identifier:" item[i]
-            } else {
-                search = search "%20OR%20identifier:" item[i]
-            }
-        }
-    }
-
-    # remove quotes from search string, since it gets quoted later
-    gsub(/"/, "", search)
-
-    # default sort orders if none were specified
-    if (length(order) == 0) {
-        if (search == "mediatype:collection" ||
-            search == "mediatype:(collection)")
-        {
-            order = "collection_size desc"
-            sort_param = "sort" order
-        } else {
-            order = "nav_order desc"
-            sort_param = "sort" order
-        }
-    }
-
-    # get search results
-
-    field[1] = "creator"
-    field[2] = "description"
-    field[3] = "identifier"
-    field[4] = "mediatype"
-    field[5] = "title"
-    fields = 5
-
-    # remove anyfield, a hobgoblin of consistency
-    searchstr = search
-    gsub(/anyfield:/, "", searchstr)
-
-    url = sprintf("%s/advancedsearch.php?q=%s&output=json&rows=%d&page=%d",
-        api_endpoint, searchstr, rows, page)
-    if (length(order) > 0) {
-        split(order, parts, " ")
-        url = url sprintf("&sort%%5B0%%5D=%s %s", order_names[parts[1]],
-            parts[2])
-    }
-    for (i = 1; i <= fields; i++) {
-        url = url sprintf("&fl%%5B%d%%5D=%s", i - 1, field[i])
-    }
-    api_request(url, "GET", iaout)
-
-    cmd = sprintf("%s <%s >%s 2>&1", cmd_json2tsv, iaout, jsout)
-    system(cmd)
-
-    numfound = 0
-    FS = "\t"
-    while ((getline <jsout) > 0) {
-        if ($1 == ".response.numFound" && $2 == "n") {
-            numfound = $3
-        }
-    }
-    close(jsout)
-
-    if (search ~ /^@/) {
-        numfound++
-    }
-    if (numfound == 0) {
-        print "Your search did not match any items in the Archive."
-        print "Try different keywords or a more general search."
-        print ""
-        printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
-        unlink(jsout)
-        unlink(iaout)
-        return
-    } else {
-        pages = int(numfound / rows)
-        if (numfound % rows != 0) {
-            pages++
-        }
-        printf "# %s search results, page %d of %d\n", numfound,
-            page, pages
-        print ""
-    }
-
-    # format search results as a gopher directory (menu)
-    FS = "\t"
-    creator = ""
-    descr = ""
-    id = ""
-    title = ""
-    type = ""
-    count = 0
-
-    if (search ~ /^@/) {
-        printf "[1|Account %s|%s/account/%s|%s|%s]\n", search, cgipath,
-           search, server, port
-    }
-
-    while ((getline <jsout) > 0) {
-        if ($1 == ".response.docs[].creator" && $2 == "s") {
-            creator = $3
-        } else if ($1 == ".response.docs[].description" && $2 == "s") {
-            descr = $3
-        } else if ($1 == ".response.docs[].identifier" && $2 == "s") {
-            id = $3
-        } else if ($1 == ".response.docs[].mediatype" && $2 == "s") {
-            type = $3
-        } else if ($1 == ".response.docs[].title" && $2 == "s") {
-            # the title field happens to be toward the end of each item
-            title = $3
-            count++
-            if (length(creator) > 0) {
-                label = sprintf("[%s] %s by %s", mediatype[type],
-                    gph_encode(shorten(title, 40)), shorten(creator, 18))
-            } else {
-                label = sprintf("[%s] %s", mediatype[type],
-                    gph_encode(shorten(title, 58)))
-            }
-            if (type == "collection") {
-                printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
-                    label, cgipath, id, server, port
-            } else {
-                printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
-                   id, server, port
-            }
-            creator = ""
-            descr = ""
-            id = ""
-            type = ""
-        }
-    }
-    close(jsout)
-
-    print ""
-
-    # only show "page back" if the user is past page 1
-    if (page > 1) {
-        printf "[1|[<<] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
-            page - 1, cgipath, page - 1, rows, sort_param, search,
-            server, port
-    }
-
-    # only show "next page" if the current page is completely full
-    if (count == rows) {
-        printf "[1|[>>] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
-            page + 1, cgipath, page + 1, rows, sort_param, search,
-            server, port
-    }
-
-    # only show "sort" if there's more than one item to sort
-    if (count > 1) {
-        printf "[1|[^v] Sort|%s/sort/%%09%s|%s|%s]\n", cgipath, search,
-            server, port
-    }
-
-    # only show "search within list" if there's multiple pages of results
-    if (numfound > rows) {
-        printf "[1|[\\/] Filter results|%s/wizard/step1/%s|%s|%s]\n",
-            cgipath, search, server, port
-    }
-
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
-
-    unlink(jsout)
-    unlink(iaout)
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    uri_encode_init()
-    util_init()
-    main(search)
-}

ADDED   src/software/gophermap.m4
Index: src/software/gophermap.m4
==================================================================
--- /dev/null
+++ src/software/gophermap.m4
@@ -0,0 +1,28 @@
+include(config.m4)dnl
+
+[1|All Software|__CGIPATH__/search/sortweek desc/%09mediatype:software|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:software|__SERVER__|__PORT__]
+[1|Internet Arcade|__CGIPATH__/search/sortweek desc/%09collection:internetarcade|__SERVER__|__PORT__]
+[1|Console Living Room|__CGIPATH__/search/sortweek desc/%09collection:consolelivingroom|__SERVER__|__PORT__]
+[1|Old School Emulation|__CGIPATH__/search/sortweek desc/%09collection:tosec|__SERVER__|__PORT__]
+[1|MS-DOS Games|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_games|__SERVER__|__PORT__]
+[1|Historical Software|__CGIPATH__/search/sortweek desc/%09collection:historicalsoftware|__SERVER__|__PORT__]
+[1|Classic PC Games|__CGIPATH__/search/sortweek desc/%09collection:classicpcgames|__SERVER__|__PORT__]
+[1|Software Library|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary AND mediatype:collection|__SERVER__|__PORT__]
+[1|Kodi Archive & Support Files|__CGIPATH__/search/sortweek desc/%09collection:kodi_archive|__SERVER__|__PORT__]
+[1|Vintage Software|__CGIPATH__/search/sortweek desc/%09collection:vintagesoftware|__SERVER__|__PORT__]
+[1|APK|__CGIPATH__/search/sortweek desc/%09collection:apkarchive|__SERVER__|__PORT__]
+[1|MS-DOS|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos|__SERVER__|__PORT__]
+[1|CD-ROM Software|__CGIPATH__/search/sortweek desc/%09collection:cd-roms|__SERVER__|__PORT__]
+[1|CD-ROM Software Library|__CGIPATH__/search/sortweek desc/%09collection:cdromsoftware|__SERVER__|__PORT__]
+[1|Software Sites|__CGIPATH__/search/sortweek desc/%09collection:softwaresites|__SERVER__|__PORT__]
+[1|Tucows Software Library|__CGIPATH__/search/sortweek desc/%09collection:tucows|__SERVER__|__PORT__]
+[1|Shareware CD-ROMs|__CGIPATH__/search/sortweek desc/%09collection:cdbbsarchive|__SERVER__|__PORT__]
+[1|Software Capsules Compilation|__CGIPATH__/search/sortweek desc/%09collection:softwarecapsules|__SERVER__|__PORT__]
+[1|CD-ROM Images|__CGIPATH__/search/sortweek desc/%09collection:cdromimages|__SERVER__|__PORT__]
+[1|Underground CD-ROM CompilationsLibrary|__CGIPATH__/search/sortweek desc/%09collection:undergroundcds|__SERVER__|__PORT__]
+[1|ZX Spectrum|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_zx_spectrum|__SERVER__|__PORT__]
+[1|DOOM Level CD|__CGIPATH__/search/sortweek desc/%09collection:doom-cds|__SERVER__|__PORT__]
+[1|Floppy Disks of Software|__CGIPATH__/search/sortweek desc/%09collection:floppysoftware|__SERVER__|__PORT__]
+[1|The Good Old Days IBM PC Floppy Collection|__CGIPATH__/search/sortweek desc/%09collection:TGODFloppyCollection|__SERVER__|__PORT__]
+[1|MS-DOS: The Frostbyte Shareware Collection|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_frostbyte|__SERVER__|__PORT__]

DELETED src/software/index.gph.m4
Index: src/software/index.gph.m4
==================================================================
--- src/software/index.gph.m4
+++ /dev/null
@@ -1,28 +0,0 @@
-include(config.m4)dnl
-
-[1|All Software|__CGIPATH__/search/sortweek desc/%09mediatype:software|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:software|__SERVER__|__PORT__]
-[1|Internet Arcade|__CGIPATH__/search/sortweek desc/%09collection:internetarcade|__SERVER__|__PORT__]
-[1|Console Living Room|__CGIPATH__/search/sortweek desc/%09collection:consolelivingroom|__SERVER__|__PORT__]
-[1|Old School Emulation|__CGIPATH__/search/sortweek desc/%09collection:tosec|__SERVER__|__PORT__]
-[1|MS-DOS Games|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_games|__SERVER__|__PORT__]
-[1|Historical Software|__CGIPATH__/search/sortweek desc/%09collection:historicalsoftware|__SERVER__|__PORT__]
-[1|Classic PC Games|__CGIPATH__/search/sortweek desc/%09collection:classicpcgames|__SERVER__|__PORT__]
-[1|Software Library|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary AND mediatype:collection|__SERVER__|__PORT__]
-[1|Kodi Archive & Support Files|__CGIPATH__/search/sortweek desc/%09collection:kodi_archive|__SERVER__|__PORT__]
-[1|Vintage Software|__CGIPATH__/search/sortweek desc/%09collection:vintagesoftware|__SERVER__|__PORT__]
-[1|APK|__CGIPATH__/search/sortweek desc/%09collection:apkarchive|__SERVER__|__PORT__]
-[1|MS-DOS|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos|__SERVER__|__PORT__]
-[1|CD-ROM Software|__CGIPATH__/search/sortweek desc/%09collection:cd-roms|__SERVER__|__PORT__]
-[1|CD-ROM Software Library|__CGIPATH__/search/sortweek desc/%09collection:cdromsoftware|__SERVER__|__PORT__]
-[1|Software Sites|__CGIPATH__/search/sortweek desc/%09collection:softwaresites|__SERVER__|__PORT__]
-[1|Tucows Software Library|__CGIPATH__/search/sortweek desc/%09collection:tucows|__SERVER__|__PORT__]
-[1|Shareware CD-ROMs|__CGIPATH__/search/sortweek desc/%09collection:cdbbsarchive|__SERVER__|__PORT__]
-[1|Software Capsules Compilation|__CGIPATH__/search/sortweek desc/%09collection:softwarecapsules|__SERVER__|__PORT__]
-[1|CD-ROM Images|__CGIPATH__/search/sortweek desc/%09collection:cdromimages|__SERVER__|__PORT__]
-[1|Underground CD-ROM CompilationsLibrary|__CGIPATH__/search/sortweek desc/%09collection:undergroundcds|__SERVER__|__PORT__]
-[1|ZX Spectrum|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_zx_spectrum|__SERVER__|__PORT__]
-[1|DOOM Level CD|__CGIPATH__/search/sortweek desc/%09collection:doom-cds|__SERVER__|__PORT__]
-[1|Floppy Disks of Software|__CGIPATH__/search/sortweek desc/%09collection:floppysoftware|__SERVER__|__PORT__]
-[1|The Good Old Days IBM PC Floppy Collection|__CGIPATH__/search/sortweek desc/%09collection:TGODFloppyCollection|__SERVER__|__PORT__]
-[1|MS-DOS: The Frostbyte Shareware Collection|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_frostbyte|__SERVER__|__PORT__]

ADDED   src/sort.m4
Index: src/sort.m4
==================================================================
--- /dev/null
+++ src/sort.m4
@@ -0,0 +1,61 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/sort
+#
+# Change search sort order
+
+include(src/config.awk)
+incl(src/cgi.awk)
+
+function main(     i, lbl, opt) {
+    lbl[1]  = "Default [^]"
+    opt[1]  = "nav_order asc"
+    lbl[2]  = "Default [v]"
+    opt[2]  = "nav_order desc"
+    lbl[3]  = "Weekly views [^]"
+    opt[3]  = "week asc"
+    lbl[4]  = "Weekly views [v]"
+    opt[4]  = "week desc"
+    lbl[5]  = "All-time views [^]"
+    opt[5]  = "downloads asc"
+    lbl[6]  = "All-time views [v]"
+    opt[6]  = "downloads desc"
+    lbl[7]  = "Title [^]"
+    opt[7]  = "title asc"
+    lbl[8]  = "Title [v]"
+    opt[8]  = "title desc"
+    lbl[9]  = "Date added [^]"
+    opt[9]  = "addeddate asc"
+    lbl[10] = "Date added [v]"
+    opt[10] = "addeddate desc"
+    lbl[11] = "Date created [^]"
+    opt[11] = "createddate asc"
+    lbl[12] = "Date created [v]"
+    opt[12] = "createddate desc"
+    lbl[13] = "Date published [^]"
+    opt[13] = "date asc"
+    lbl[14] = "Date published [v]"
+    opt[14] = "date desc"
+    lbl[15] = "Creator [^]"
+    opt[15] = "creator asc"
+    lbl[16] = "Creator [v]"
+    opt[16] = "creator desc"
+    lbl[17] = "Random"
+    opt[17] = "random asc"
+
+    print "# Sort by"
+    print ""
+    for (i = 1; i < 18; i++) {
+        printf "1%s\t%s/search?%s/sort%s\t%s\t%s\n",
+            lbl[i], cgipath, search, opt[i], server, port
+    }
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

DELETED src/sort/index.dcgi.m4
Index: src/sort/index.dcgi.m4
==================================================================
--- src/sort/index.dcgi.m4
+++ /dev/null
@@ -1,61 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# sort/index.dcgi
-#
-# Change search sort order
-
-include(src/config.awk)
-incl(src/cgi.awk)
-
-function main(     i, lbl, opt) {
-    lbl[1]  = "Default [^]"
-    opt[1]  = "nav_order asc"
-    lbl[2]  = "Default [v]"
-    opt[2]  = "nav_order desc"
-    lbl[3]  = "Weekly views [^]"
-    opt[3]  = "week asc"
-    lbl[4]  = "Weekly views [v]"
-    opt[4]  = "week desc"
-    lbl[5]  = "All-time views [^]"
-    opt[5]  = "downloads asc"
-    lbl[6]  = "All-time views [v]"
-    opt[6]  = "downloads desc"
-    lbl[7]  = "Title [^]"
-    opt[7]  = "title asc"
-    lbl[8]  = "Title [v]"
-    opt[8]  = "title desc"
-    lbl[9]  = "Date added [^]"
-    opt[9]  = "addeddate asc"
-    lbl[10] = "Date added [v]"
-    opt[10] = "addeddate desc"
-    lbl[11] = "Date created [^]"
-    opt[11] = "createddate asc"
-    lbl[12] = "Date created [v]"
-    opt[12] = "createddate desc"
-    lbl[13] = "Date published [^]"
-    opt[13] = "date asc"
-    lbl[14] = "Date published [v]"
-    opt[14] = "date desc"
-    lbl[15] = "Creator [^]"
-    opt[15] = "creator asc"
-    lbl[16] = "Creator [v]"
-    opt[16] = "creator desc"
-    lbl[17] = "Random"
-    opt[17] = "random asc"
-
-    print "# Sort by"
-    print ""
-    for (i = 1; i < 18; i++) {
-        printf "[1|%s|%s/search/sort%s%%09%s|%s|%s]\n",
-            lbl[i], cgipath, opt[i], search, server, port
-    }
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    main()
-}

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -117,17 +117,17 @@
     print "" >>output
     print "Items may be taken down for various reasons," >>output
     print "including by decision of the uploader or" >>output
     print "due to a violation of the Terms of Use." >>output
     print "" >>output
-    printf "[h|Metadata|URL:%s|%s|%s]\n", url, server, port >>output
+    printf "hMetadata\tURL:%s\t%s\t%s\n", url, server, port >>output
     print "" >>output
     url = api_ssl_endpoint "/about/terms.php"
-    printf "[0|Terms of Use|%s/text/%%09%s|%s|%s]\n", cgipath,
+    printf "0Terms of Use\t%s/text?%s\t%s\t%s\n", cgipath,
         url, server, port >>output
     print "" >>output
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
+    printf "1PHAROS\t%s\t%s\t%s\n", docpath, server, port >>output
     return
 }
 
 function read_file(name,    retval) {
     while ((getline <name) > 0) {

ADDED   src/video/gophermap.m4
Index: src/video/gophermap.m4
==================================================================
--- /dev/null
+++ src/video/gophermap.m4
@@ -0,0 +1,23 @@
+include(config.m4)dnl
+# Video
+
+[1|All Video|__CGIPATH__/search/sortweek desc/%09mediatype:movies|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:movies|__SERVER__|__PORT__]
+[1|Prelinger Archives|__CGIPATH__/search/sortweek desc/%09collection:prelinger|__SERVER__|__PORT__]
+[1|Democracy Now!|__CGIPATH__/search/sortweek desc/%09collection:democracy_now_vid|__SERVER__|__PORT__]
+[1|Occupy Wall Street|__CGIPATH__/search/sortweek desc/%09collection:occupywallstreet|__SERVER__|__PORT__]
+[1|TV NSA Clip Library|__CGIPATH__/search/sortweek desc/%09collection:nsa|__SERVER__|__PORT__]
+[1|Animation And Cartoons|__CGIPATH__/search/sortweek desc/%09collection:animationandcartoons|__SERVER__|__PORT__]
+[1|Arts & Music|__CGIPATH__/search/sortweek desc/%09collection:artsandmusicvideos|__SERVER__|__PORT__]
+[1|Computers & Technology|__CGIPATH__/search/sortweek desc/%09collection:computersandtechvideos|__SERVER__|__PORT__]
+[1|Cultural & Academic Films|__CGIPATH__/search/sortweek desc/%09collection:culturalandacademicfilms|__SERVER__|__PORT__]
+[1|Ephemeral Films|__CGIPATH__/search/sortweek desc/%09collection:ephemera|__SERVER__|__PORT__]
+[1|Movies|__CGIPATH__/search/sortweek desc/%09collection:moviesandfilms|__SERVER__|__PORT__]
+[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:newsandpublicaffairs|__SERVER__|__PORT__]
+[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:spiritualityandreligion|__SERVER__|__PORT__]
+[1|Sports|__CGIPATH__/search/sortweek desc/%09collection:sports|__SERVER__|__PORT__]
+[1|Television|__CGIPATH__/search/sortweek desc/%09collection:television AND mediatype:collection|__SERVER__|__PORT__]
+[1|VHS Vault|__CGIPATH__/search/sortweek desc/%09collection:vhsvault|__SERVER__|__PORT__]
+[1|Video Games|__CGIPATH__/search/sortweek desc/%09collection:gamevideos AND mediatype:collection|__SERVER__|__PORT__]
+[1|Vlogs|__CGIPATH__/search/sortweek desc/%09collection:vlogs|__SERVER__|__PORT__]
+[1|Youth Media|__CGIPATH__/search/sortweek desc/%09collection:youth_media|__SERVER__|__PORT__]

DELETED src/video/index.gph.m4
Index: src/video/index.gph.m4
==================================================================
--- src/video/index.gph.m4
+++ /dev/null
@@ -1,23 +0,0 @@
-include(config.m4)dnl
-# Video
-
-[1|All Video|__CGIPATH__/search/sortweek desc/%09mediatype:movies|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:movies|__SERVER__|__PORT__]
-[1|Prelinger Archives|__CGIPATH__/search/sortweek desc/%09collection:prelinger|__SERVER__|__PORT__]
-[1|Democracy Now!|__CGIPATH__/search/sortweek desc/%09collection:democracy_now_vid|__SERVER__|__PORT__]
-[1|Occupy Wall Street|__CGIPATH__/search/sortweek desc/%09collection:occupywallstreet|__SERVER__|__PORT__]
-[1|TV NSA Clip Library|__CGIPATH__/search/sortweek desc/%09collection:nsa|__SERVER__|__PORT__]
-[1|Animation And Cartoons|__CGIPATH__/search/sortweek desc/%09collection:animationandcartoons|__SERVER__|__PORT__]
-[1|Arts & Music|__CGIPATH__/search/sortweek desc/%09collection:artsandmusicvideos|__SERVER__|__PORT__]
-[1|Computers & Technology|__CGIPATH__/search/sortweek desc/%09collection:computersandtechvideos|__SERVER__|__PORT__]
-[1|Cultural & Academic Films|__CGIPATH__/search/sortweek desc/%09collection:culturalandacademicfilms|__SERVER__|__PORT__]
-[1|Ephemeral Films|__CGIPATH__/search/sortweek desc/%09collection:ephemera|__SERVER__|__PORT__]
-[1|Movies|__CGIPATH__/search/sortweek desc/%09collection:moviesandfilms|__SERVER__|__PORT__]
-[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:newsandpublicaffairs|__SERVER__|__PORT__]
-[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:spiritualityandreligion|__SERVER__|__PORT__]
-[1|Sports|__CGIPATH__/search/sortweek desc/%09collection:sports|__SERVER__|__PORT__]
-[1|Television|__CGIPATH__/search/sortweek desc/%09collection:television AND mediatype:collection|__SERVER__|__PORT__]
-[1|VHS Vault|__CGIPATH__/search/sortweek desc/%09collection:vhsvault|__SERVER__|__PORT__]
-[1|Video Games|__CGIPATH__/search/sortweek desc/%09collection:gamevideos AND mediatype:collection|__SERVER__|__PORT__]
-[1|Vlogs|__CGIPATH__/search/sortweek desc/%09collection:vlogs|__SERVER__|__PORT__]
-[1|Youth Media|__CGIPATH__/search/sortweek desc/%09collection:youth_media|__SERVER__|__PORT__]

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -1,8 +1,7 @@
 function dump(search, type,     base, cmd, curlcfg, is_html, is_image,
-    label, limit, link, marker, parts, prefix, proto, relative, root,
-    url)
+    label, limit, link, marker, parts, proto, relative, root, url)
 {
     url = search
     gsub(/%3F/, "?", url)
 
     if (url !~ /^(http|https):\/\/[[:alnum:].-]+(:[0-9]+)*(\/[[:alnum:].,?@~=%%:\/+&_() -]*)*$/) {
@@ -75,32 +74,32 @@
     marker = 999999
 
     if (type == TYPE_LINKS) {
         is_html = detect_html(url)
         is_image = detect_image(url)
-        printf "[9|Binary download|%s/raw/%%09%s|%s|%s]\n", cgipath,
+        printf "9Binary download\t%s/raw?%s\t%s\t%s\n", cgipath,
             search, server, port
         if (is_image) {
-            printf "[I|Image view|%s/raw/%%09%s|%s|%s]\n", cgipath,
+            printf "IImage view\t%s/raw?%s\t%s\t%s\n", cgipath,
                 search, server, port
         }
         if (is_html) {
             label = "Source"
         } else {
             label = "Text view"
         }
-        printf "[0|%s|%s/raw/%%09%s|%s|%s]\n", label, cgipath, search,
+        printf "0%s\t%s/raw?%s\t%s\t%s\n", label, cgipath, search,
             server, port
         if (is_html) {
             label = "HTML view"
         } else {
             label = "Strings"
         }
-        printf "[0|%s|%s/text/%%09%s|%s|%s]\n", label, cgipath, search,
-            server, port
-        printf "[0|Headers|%s/debug/%%09%s|%s|%s]\n", cgipath, search,
-            server, port
+        printf "0%s\t%s/text?%s\t%s\t%s\n", label, cgipath,
+            search, server, port
+        printf "0Headers\t%s/debug?%s\t%s\t%s\n", cgipath,
+            search, server, port
         print ""
     }
 
     while ((cmd | getline) > 0) {
         if (NR < marker) {
@@ -191,11 +190,11 @@
                 id = substr(id, 1, RSTART - 1)
             }
         }
         if (length(id) > 0) {
             label = prefix id
-            printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
+            printf "1%s\t%s/details?%s\t%s\t%s\n", label, cgipath,
                 id, server, port >>output
         } else {
              print str >>output
         }
     } else {

ADDED   src/wizard/gophermap
Index: src/wizard/gophermap
==================================================================
--- /dev/null
+++ src/wizard/gophermap
@@ -0,0 +1,10 @@
+i             ,    _			
+i            /|   | |			
+i          _/_\_  >_<			
+i         .-\-/.   |			
+i        /  | | \_ |			
+i        \ \| |\__(/			
+i        /(`---')  |			
+i       / /     \  |			
+i    _.'  \'-'  /  |			
+i    `----'`=-='   '    hjw			

ADDED   src/wizard/step1.m4
Index: src/wizard/step1.m4
==================================================================
--- /dev/null
+++ src/wizard/step1.m4
@@ -0,0 +1,65 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/wizard/step1
+#
+# Select field to filter/search by
+
+include(src/config.awk)
+incl(src/cgi.awk)
+
+function main(     i, lbl, opt) {
+    lbl[1]  = "Any field contains"
+    opt[1]  = "anyfield"
+    lbl[2]  = "Any field does not contain"
+    opt[2]  = "-anyfield"
+    lbl[3]  = "Title contains"
+    opt[3]  = "title"
+    lbl[4]  = "Title does not contain"
+    opt[4]  = "-title"
+    lbl[5]  = "Creator contains"
+    opt[5]  = "creator"
+    lbl[6]  = "Creator does not contain"
+    opt[6]  = "-creator"
+    lbl[7]  = "Description contains"
+    opt[7]  = "description"
+    lbl[8]  = "Description does not contain"
+    opt[8]  = "-description"
+    lbl[9]  = "Collection is"
+    opt[9]  = "collection"
+    lbl[10] = "Collection does not contain"
+    opt[10] = "-collection"
+    lbl[11] = "Mediatype is"
+    opt[11] = "mediatype"
+    lbl[12] = "Mediatype does not contain"
+    opt[12] = "-mediatype"
+    lbl[13] = "Date or date range is"
+    opt[13] = "date"
+    lbl[14] = "Language contains"
+    opt[14] = "language"
+    lbl[15] = "Always available"
+    opt[15] = "-access-restricted-item"
+
+    print "# Search wizard: Select field"
+    print ""
+    for (i = 1; i < 16; i++) {
+        if (opt[i] ~ /mediatype$/) {
+            printf "1%s\t%s/wizard/step2?%s/%s\t%s\t%]\n",
+                lbl[i], cgipath, search, opt[i], server, port
+        } else if (lbl[i] == "Always available") {
+            printf "1%s\t%s/wizard/step3?%s/%s/true\t%s\t%s\n",
+                lbl[i], cgipath, search, opt[i], server, port
+        } else {
+            printf "7%s\t%s/wizard/step3?%s/%s\t%s\t%s\n",
+                lbl[i], cgipath, search, opt[i], server, port
+        }
+    }
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

DELETED src/wizard/step1/index.dcgi.m4
Index: src/wizard/step1/index.dcgi.m4
==================================================================
--- src/wizard/step1/index.dcgi.m4
+++ /dev/null
@@ -1,67 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# wizard/step1/index.dcgi
-#
-# Select field to filter/search by
-
-include(src/config.awk)
-incl(src/cgi.awk)
-
-function main(     i, lbl, opt, searchstr) {
-    searchstr = parts[4]
-
-    lbl[1]  = "Any field contains"
-    opt[1]  = "anyfield"
-    lbl[2]  = "Any field does not contain"
-    opt[2]  = "-anyfield"
-    lbl[3]  = "Title contains"
-    opt[3]  = "title"
-    lbl[4]  = "Title does not contain"
-    opt[4]  = "-title"
-    lbl[5]  = "Creator contains"
-    opt[5]  = "creator"
-    lbl[6]  = "Creator does not contain"
-    opt[6]  = "-creator"
-    lbl[7]  = "Description contains"
-    opt[7]  = "description"
-    lbl[8]  = "Description does not contain"
-    opt[8]  = "-description"
-    lbl[9]  = "Collection is"
-    opt[9]  = "collection"
-    lbl[10] = "Collection does not contain"
-    opt[10] = "-collection"
-    lbl[11] = "Mediatype is"
-    opt[11] = "mediatype"
-    lbl[12] = "Mediatype does not contain"
-    opt[12] = "-mediatype"
-    lbl[13] = "Date or date range is"
-    opt[13] = "date"
-    lbl[14] = "Language contains"
-    opt[14] = "language"
-    lbl[15] = "Always available"
-    opt[15] = "-access-restricted-item"
-
-    print "# Search wizard: Select field"
-    print ""
-    for (i = 1; i < 16; i++) {
-        if (opt[i] ~ /mediatype$/) {
-            printf "[1|%s|%s/wizard/step2/%s/%s|%s|%s]\n", lbl[i],
-                cgipath, opt[i], searchstr, server, port
-        } else if (lbl[i] == "Always available") {
-            printf "[1|%s|%s/wizard/step3/%s/%s%%09true|%s|%s]\n",
-                lbl[i], cgipath, opt[i], searchstr, server, port
-        } else {
-            printf "[7|%s|%s/wizard/step3/%s/%s|%s|%s]\n", lbl[i],
-                cgipath, opt[i], searchstr, server, port
-        }
-    }
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    main()
-}

ADDED   src/wizard/step2.m4
Index: src/wizard/step2.m4
==================================================================
--- /dev/null
+++ src/wizard/step2.m4
@@ -0,0 +1,51 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/wizard/step2
+#
+# Select mediatype to filter/search by
+
+include(src/config.awk)
+incl(src/cgi.awk)
+
+function main(     field, newsearch, searchstr) {
+    field = parts[4]
+    searchstr = parts[5]
+
+    if (field == "mediatype") {
+        print "# Mediatype is:"
+    } else {
+        print "# Mediatype does not contain:"
+    }
+    print ""
+
+    lbl[1] = "audio"
+    lbl[2] = "collection"
+    lbl[3] = "data"
+    lbl[4] = "etree"
+    lbl[5] = "image"
+    lbl[6] = "movies"
+    lbl[7] = "software"
+    lbl[8] = "texts"
+    lbl[9] = "web"
+    for (i = 1; i < 10; i++) {
+        if (length(searchstr) == 0) {
+            newsearch = sprintf("%s:(%s)", field, lbl[i])
+        } else {
+            newsearch = sprintf("%s AND %s:(%s)", searchstr, field, lbl[i])
+        }
+        printf "1%s\t%s/search?%s\t%s\t%s\n", lbl[i], cgipath,
+            newsearch, server, port
+    }
+    print "# Progress:"
+    print ""
+    print "* Field: Mediatype"
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

DELETED src/wizard/step2/index.dcgi.m4
Index: src/wizard/step2/index.dcgi.m4
==================================================================
--- src/wizard/step2/index.dcgi.m4
+++ /dev/null
@@ -1,51 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# wizard/step2/index.dcgi
-#
-# Select mediatype to filter/search by
-
-include(src/config.awk)
-incl(src/cgi.awk)
-
-function main(     field, newsearch, searchstr) {
-    field = parts[4]
-    searchstr = parts[5]
-
-    if (field == "mediatype") {
-        print "# Mediatype is:"
-    } else {
-        print "# Mediatype does not contain:"
-    }
-    print ""
-
-    lbl[1] = "audio"
-    lbl[2] = "collection"
-    lbl[3] = "data"
-    lbl[4] = "etree"
-    lbl[5] = "image"
-    lbl[6] = "movies"
-    lbl[7] = "software"
-    lbl[8] = "texts"
-    lbl[9] = "web"
-    for (i = 1; i < 10; i++) {
-        if (length(searchstr) == 0) {
-            newsearch = sprintf("%s:(%s)", field, lbl[i])
-        } else {
-            newsearch = sprintf("%s AND %s:(%s)", searchstr, field, lbl[i])
-        }
-        printf "[1|%s|%s/search/%%09%s|%s|%s]\n", lbl[i], cgipath,
-            newsearch, server, port
-    }
-    print "# Progress:"
-    print ""
-    print "* Field: Mediatype"
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    main()
-}

ADDED   src/wizard/step3.m4
Index: src/wizard/step3.m4
==================================================================
--- /dev/null
+++ src/wizard/step3.m4
@@ -0,0 +1,45 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# __CGIPATH__/wizard/step3
+#
+# Apply new search terms
+
+include(src/config.awk)
+incl(src/cgi.awk)
+
+function main(     field, label, newsearch, op, searchstr, value) {
+    split(search, parts, "/")
+    searchstr = parts[1]
+    field = parts[2]
+    value = searchreq
+
+    if (field ~ /^-/) {
+        label = toupper(substr(field, 2, 1)) substr(field, 3)
+        op = "does not contain"
+    } else {
+        label = toupper(substr(field, 1, 1)) substr(field, 2)
+        op = "contains"
+    }
+    if (length(searchstr) == 0) {
+        newsearch = sprintf("%s:(%s)", field, value)
+    } else {
+        newsearch = sprintf("%s AND %s:(%s)", searchstr, field, value)
+    }
+    print ""
+    printf "1Apply search criteria\t%s/search?%s\t%s\t%s\n",
+        cgipath, newsearch, server, port
+    print ""
+    print "# Progress:"
+    print ""
+    printf "* Field %s %s %s\n", label, op, value
+    printf "* New search: %s\n", newsearch
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

DELETED src/wizard/step3/index.dcgi.m4
Index: src/wizard/step3/index.dcgi.m4
==================================================================
--- src/wizard/step3/index.dcgi.m4
+++ /dev/null
@@ -1,44 +0,0 @@
-include(config.m4)dnl
-#!__CMD_AWK__ -f
-
-# wizard/step3/index.dcgi
-#
-# Apply new search terms
-
-include(src/config.awk)
-incl(src/cgi.awk)
-
-function main(     field, label, newsearch, op, searchstr, value) {
-    field = parts[4]
-    searchstr = parts[5]
-    value = search
-
-    if (field ~ /^-/) {
-        label = toupper(substr(field, 2, 1)) substr(field, 3)
-        op = "does not contain"
-    } else {
-        label = toupper(substr(field, 1, 1)) substr(field, 2)
-        op = "contains"
-    }
-    if (length(searchstr) == 0) {
-        newsearch = sprintf("%s:(%s)", field, value)
-    } else {
-        newsearch = sprintf("%s AND %s:(%s)", searchstr, field, value)
-    }
-    print ""
-    printf "[1|Apply search criteria|%s/search/%%09%s|%s|%s]\n",
-        cgipath, newsearch, server, port
-    print ""
-    print "# Progress:"
-    print ""
-    printf "* Field %s %s %s\n", label, op, value
-    printf "* New search: %s\n", newsearch
-    exit 0
-}
-
-BEGIN {
-    config_init()
-
-    cgi_init()
-    main()
-}

]]></content>
<updated>2025-11-09 01:26:03</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/3fd6e38a37/</id>
<title>Add configuration for PHAROS on bitre...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-06 14:36:35

Add configuration for PHAROS on bitreich.org

  INSERTED    DELETED
        12         12 config.m4
        12         12 TOTAL over 1 changed file

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -6,25 +6,25 @@
 define(__API_ENDPOINT__, http://archive.org)dnl
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
 define(__AWK_EXT__, 0)dnl
 define(__CACHE_DB__, /home/user/pharos/db/cache.dat)dnl
 define(__CACHE_ENABLED__, 0)dnl
-define(__CGIPATH__, /~user/pharos)dnl
-define(__CMD_AWK__, /usr/bin/awk)dnl
-define(__CMD_CURL__, /usr/bin/curl)dnl
+define(__CGIPATH__, /ia)dnl
+define(__CMD_AWK__, /usr/bin/env awk)dnl
+define(__CMD_CURL__, curl)dnl
 define(__CMD_ENV__, /usr/bin/env)dnl
-define(__CMD_JSON2TSV__, /usr/local/bin/json2tsv)dnl
-define(__CMD_MKTEMP__, /bin/mktemp)dnl
+define(__CMD_JSON2TSV__, json2tsv)dnl
+define(__CMD_MKTEMP__, mktemp)dnl
 define(__CMD_RM__, rm)dnl
 define(__CMD_SQLITE__, /usr/bin/sqlite3)dnl
-define(__CMD_STRINGS__, /bin/busybox strings)dnl
-define(__CMD_WEBDUMP__, /usr/local/bin/webdump)dnl
-define(__CMD_XARGS__, /usr/bin/xargs)dnl
-define(__CONTACT__, admin@example.com)dnl
-define(__GEOMYIDAE_VERSION__, 0.96)dnl
-define(__MAX_BIN_SIZE__, 10)dnl
-define(__MAX_TXT_SIZE__, 1)dnl
+define(__CMD_STRINGS__, strings)dnl
+define(__CMD_WEBDUMP__, webdump)dnl
+define(__CMD_XARGS__, xargs)dnl
+define(__CONTACT__, 20h@r-36.net)dnl
+define(__GEOMYIDAE_VERSION__, 0.99)dnl
+define(__MAX_BIN_SIZE__, 0)dnl
+define(__MAX_TXT_SIZE__, 0)dnl
 define(__PASS_ENABLED__, 0)dnl
 define(__PASS_LIST__, /home/user/pharos/pass.tsv)dnl
 define(__SERVER__, server)dnl
 define(__PORT__, port)dnl
 dnl

]]></content>
<updated>2025-11-06 14:36:35</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/68d99a56a7/</id>
<title>Disable limits when max_bin_size or m...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-06 14:33:16

Disable limits when max_bin_size or max_txt_size are zero.

  INSERTED    DELETED
         1          1 src/download/index.dcgi.m4
         3          1 src/web.awk
         4          2 TOTAL over 2 changed files

Index: src/download/index.dcgi.m4
==================================================================
--- src/download/index.dcgi.m4
+++ src/download/index.dcgi.m4
@@ -66,11 +66,11 @@
             shorten_left(name[i], 40),
             strftime("%Y-%m-%d %H:%M", mtime[i]),
             human_size(file_size[i]))
         url = sprintf("http://%s%s/%s", item_server, dir, name[i])
         if (is_proxy) {
-            if (file_size[i] > max_bin_size * size_mb) {
+            if (max_bin_size > 0 && file_size[i] > max_bin_size * size_mb) {
                 printf "[h|%s|URL:%s|%s|%s]\n", label, uri_encode(url),
                     server, port
             } else {
                 printf "[1|%s|%s/links/%%09%s|%s|%s]\n", label, cgipath,
                     url, server, port

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -19,11 +19,13 @@
     # Use temporary file for curl configuration.
     # This keeps user input separate from shell execution.
 
     curlcfg = gettemp()
     printf "--connect-timeout 10\n" > curlcfg
-    printf "--max-filesize %dM\n", limit >> curlcfg
+    if (limit > 0) {
+        printf "--max-filesize %dM\n", limit >> curlcfg
+    }
     printf "--max-redirs 0\n" >> curlcfg
     printf "--proto =http,https\n" >> curlcfg
     printf "--show-error\n" >> curlcfg
     printf "--silent\n" >> curlcfg
     printf "--url %s\n", uri_encode(url) >> curlcfg

]]></content>
<updated>2025-11-06 14:33:16</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/4208c74e8f/</id>
<title>Add 4d2.org pubnix IP addresses to pa...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-02 16:42:19

Add 4d2.org pubnix IP addresses to pass.tsv

  INSERTED    DELETED
         6          0 pass.tsv
         6          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -2,10 +2,14 @@
 #2a01:4f8:191:63af::3	de3-apache2.meulie.net	www proxy crawled a lot https://gopherproxy.meulie.net/statistics/
 5.161.108.85	vern.cc	https://tilde.wiki/Known_tildes
 23.133.40.12	tilde.town	tildeverse.org
 24.33.195.204	syn-024-033-195-204.res.spectrum.com	friendly
 45.33.66.185	rawtext.club	https://tilde.wiki/Known_tildes
+45.43.14.195	bbs.4d2.org	pubnix
+45.43.14.223	matrix.4d2.org	pubnix
+45.43.14.224	depot.4d2.org	pubnix
+45.43.14.225	lemmy.4d2.org	pubnix
 45.58.42.71	rw.rs	tildeverse.org
 46.110.208.211	metronetinc.com	friendly
 46.225.38.51	vps-d19ace40.vps.ovh.net	friendly
 54.38.176.219	tilde.green	tildeverse.org
 54.68.197.142	nyx.nyx.net	pubnix
@@ -27,14 +31,16 @@
 129.146.136.128	tylde.karx.xyz	https://tilde.wiki/Known_tildes
 138.197.211.90	skylab.org	https://tilde.wiki/Known_tildes
 142.44.150.184	tilde.club	tildeverse.org
 142.44.150.185	thunix.net	tildeverse.org
 149.210.185.89	codemadness.org	www proxy
+155.254.16.17	4d2.org 	pubnix
 157.90.196.56	fr.tild3.org	https://tilde.wiki/Known_tildes
 159.69.146.152	tilde.institute	tildeverse.org
 159.196.9.199	gopher.mills.io	www proxy
 159.203.148.233	squiggle.city	https://tilde.wiki/Known_tildes
+161.129.68.144	4d2.su		pubnix
 165.227.127.54	ctrl-c.club	tildeverse.org
 166.84.1.8	shell.panix.com	pubnix
 166.84.1.9	shell.panix.com	pubnix
 166.84.1.10	shell.panix.com	pubnix
 172.66.40.245	crime.team	https://tilde.wiki/Known_tildes

]]></content>
<updated>2025-11-02 16:42:19</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/9ce146e3ef/</id>
<title>Add friendly IP address from rima</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-11-02 16:33:28

Add friendly IP address from rima

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -38,10 +38,11 @@
 166.84.1.9	shell.panix.com	pubnix
 166.84.1.10	shell.panix.com	pubnix
 172.66.40.245	crime.team	https://tilde.wiki/Known_tildes
 172.66.43.11	crime.team	https://tilde.wiki/Known_tildes
 173.32.203.226	piepi.art	https://tilde.wiki/Known_tildes
+176.84.207.21	21.red-176-84-207.dynamicip.rima-tde.net	friendly
 185.179.143.92	xinu.me	https://tilde.wiki/Known_tildes
 192.9.177.29	aussies.space	tildeverse.org
 198.50.210.248	tilde.team	tildeverse.org
 205.166.94.16	sdf.org	pubnix
 205.166.94.4	tty.sdf.org	pubnix

]]></content>
<updated>2025-11-02 16:33:28</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/40f3523863/</id>
<title>Add friendly IP address to pass.tsv</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-10-31 13:17:19

Add friendly IP address to pass.tsv

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -19,10 +19,11 @@
 85.214.104.232	tilde.fun	https://tilde.wiki/Known_tildes
 88.198.101.33	r-36.net	friendly
 89.163.145.170	envs.net	https://tilde.wiki/Known_tildes
 95.179.178.246	tilde.guru	tildeverse.org
 95.216.6.12	tilde.pink	tildeverse.org
+103.171.163.189	Jakarta IP	friendly
 104.238.186.103	cosmic.voyage	tildeverse.org
 116.202.128.144	freeshell.de	pubnix
 129.146.136.128	tylde.karx.xyz	https://tilde.wiki/Known_tildes
 138.197.211.90	skylab.org	https://tilde.wiki/Known_tildes
 142.44.150.184	tilde.club	tildeverse.org

]]></content>
<updated>2025-10-31 13:17:19</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/b6aa850d49/</id>
<title>Remove duplicate items introduced in ...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-10-18 19:19:14

Remove duplicate items introduced in last commit

  INSERTED    DELETED
         0          2 src/software/index.gph.m4
         0          2 TOTAL over 1 changed file

Index: src/software/index.gph.m4
==================================================================
--- src/software/index.gph.m4
+++ src/software/index.gph.m4
@@ -18,12 +18,10 @@
 [1|Software Sites|__CGIPATH__/search/sortweek desc/%09collection:softwaresites|__SERVER__|__PORT__]
 [1|Tucows Software Library|__CGIPATH__/search/sortweek desc/%09collection:tucows|__SERVER__|__PORT__]
 [1|Shareware CD-ROMs|__CGIPATH__/search/sortweek desc/%09collection:cdbbsarchive|__SERVER__|__PORT__]
 [1|Software Capsules Compilation|__CGIPATH__/search/sortweek desc/%09collection:softwarecapsules|__SERVER__|__PORT__]
 [1|CD-ROM Images|__CGIPATH__/search/sortweek desc/%09collection:cdromimages|__SERVER__|__PORT__]
-[1|CD-ROM Software Collection|__CGIPATH__/search/sortweek desc/%09collection:cd-roms|__SERVER__|__PORT__]
-[1|CD-ROM Software Library|__CGIPATH__/search/sortweek desc/%09collection:cdromsoftware|__SERVER__|__PORT__]
 [1|Underground CD-ROM CompilationsLibrary|__CGIPATH__/search/sortweek desc/%09collection:undergroundcds|__SERVER__|__PORT__]
 [1|ZX Spectrum|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_zx_spectrum|__SERVER__|__PORT__]
 [1|DOOM Level CD|__CGIPATH__/search/sortweek desc/%09collection:doom-cds|__SERVER__|__PORT__]
 [1|Floppy Disks of Software|__CGIPATH__/search/sortweek desc/%09collection:floppysoftware|__SERVER__|__PORT__]
 [1|The Good Old Days IBM PC Floppy Collection|__CGIPATH__/search/sortweek desc/%09collection:TGODFloppyCollection|__SERVER__|__PORT__]

]]></content>
<updated>2025-10-18 19:19:14</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/a76eb8fc2f/</id>
<title>Add more CD-ROM software collections</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-10-18 19:15:54

Add more CD-ROM software collections

  INSERTED    DELETED
         3          0 src/software/index.gph.m4
         3          0 TOTAL over 1 changed file

Index: src/software/index.gph.m4
==================================================================
--- src/software/index.gph.m4
+++ src/software/index.gph.m4
@@ -18,10 +18,13 @@
 [1|Software Sites|__CGIPATH__/search/sortweek desc/%09collection:softwaresites|__SERVER__|__PORT__]
 [1|Tucows Software Library|__CGIPATH__/search/sortweek desc/%09collection:tucows|__SERVER__|__PORT__]
 [1|Shareware CD-ROMs|__CGIPATH__/search/sortweek desc/%09collection:cdbbsarchive|__SERVER__|__PORT__]
 [1|Software Capsules Compilation|__CGIPATH__/search/sortweek desc/%09collection:softwarecapsules|__SERVER__|__PORT__]
 [1|CD-ROM Images|__CGIPATH__/search/sortweek desc/%09collection:cdromimages|__SERVER__|__PORT__]
+[1|CD-ROM Software Collection|__CGIPATH__/search/sortweek desc/%09collection:cd-roms|__SERVER__|__PORT__]
+[1|CD-ROM Software Library|__CGIPATH__/search/sortweek desc/%09collection:cdromsoftware|__SERVER__|__PORT__]
+[1|Underground CD-ROM CompilationsLibrary|__CGIPATH__/search/sortweek desc/%09collection:undergroundcds|__SERVER__|__PORT__]
 [1|ZX Spectrum|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_zx_spectrum|__SERVER__|__PORT__]
 [1|DOOM Level CD|__CGIPATH__/search/sortweek desc/%09collection:doom-cds|__SERVER__|__PORT__]
 [1|Floppy Disks of Software|__CGIPATH__/search/sortweek desc/%09collection:floppysoftware|__SERVER__|__PORT__]
 [1|The Good Old Days IBM PC Floppy Collection|__CGIPATH__/search/sortweek desc/%09collection:TGODFloppyCollection|__SERVER__|__PORT__]
 [1|MS-DOS: The Frostbyte Shareware Collection|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_frostbyte|__SERVER__|__PORT__]

]]></content>
<updated>2025-10-18 19:15:54</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/fff249e112/</id>
<title>Add harzilein.eu.org to pass.tsv</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-10-12 01:42:41

Add harzilein.eu.org to pass.tsv

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -10,10 +10,11 @@
 54.38.176.219	tilde.green	tildeverse.org
 54.68.197.142	nyx.nyx.net	pubnix
 54.149.138.40	hackers.cool	https://tilde.wiki/Known_tildes
 64.190.63.222	pebble.ink	https://tilde.wiki/Known_tildes
 63.155.6.201	63-155-6-201.eugn.qwest.net	friendly
+78.47.64.246	harzilein.eu.org	friendly
 79.116.139.115	79-116-139-115.digimobil.es	friendly
 79.116.139.107	79-116-139-107.digimobil.es	friendly
 82.64.43.33	atl.huld.re	friendly
 85.214.104.232	tilde.fun	https://tilde.wiki/Known_tildes
 88.198.101.33	r-36.net	friendly

]]></content>
<updated>2025-10-12 01:42:41</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/8dee409f02/</id>
<title>The crawlers are back.  Remove floodg...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-09-05 15:10:45

The crawlers are back.  Remove floodgap from pass.tsv

  INSERTED    DELETED
         0          1 pass.tsv
         0          1 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -38,11 +38,10 @@
 172.66.40.245	crime.team	https://tilde.wiki/Known_tildes
 172.66.43.11	crime.team	https://tilde.wiki/Known_tildes
 173.32.203.226	piepi.art	https://tilde.wiki/Known_tildes
 185.179.143.92	xinu.me	https://tilde.wiki/Known_tildes
 192.9.177.29	aussies.space	tildeverse.org
-192.80.49.36	ampm.floodgap.com	www proxy
 198.50.210.248	tilde.team	tildeverse.org
 205.166.94.16	sdf.org	pubnix
 205.166.94.4	tty.sdf.org	pubnix
 205.166.94.9	tty.sdf.org	pubnix
 207.246.69.54	texto-plano.xyz	tildeverse.org

]]></content>
<updated>2025-09-05 15:10:45</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/46a8783ec6/</id>
<title>Add friendly IP 46.110.208.211 to pas...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-08-28 19:06:49

Add friendly IP 46.110.208.211 to pass.tsv

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -3,10 +3,11 @@
 5.161.108.85	vern.cc	https://tilde.wiki/Known_tildes
 23.133.40.12	tilde.town	tildeverse.org
 24.33.195.204	syn-024-033-195-204.res.spectrum.com	friendly
 45.33.66.185	rawtext.club	https://tilde.wiki/Known_tildes
 45.58.42.71	rw.rs	tildeverse.org
+46.110.208.211	metronetinc.com	friendly
 46.225.38.51	vps-d19ace40.vps.ovh.net	friendly
 54.38.176.219	tilde.green	tildeverse.org
 54.68.197.142	nyx.nyx.net	pubnix
 54.149.138.40	hackers.cool	https://tilde.wiki/Known_tildes
 64.190.63.222	pebble.ink	https://tilde.wiki/Known_tildes

]]></content>
<updated>2025-08-28 19:06:49</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/9f7a83ab8d/</id>
<title>Add new friendly IP address to pass list</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-08-27 16:22:47

Add new friendly IP address to pass list

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -1,9 +1,10 @@
 #IP	name	notes
 #2a01:4f8:191:63af::3	de3-apache2.meulie.net	www proxy crawled a lot https://gopherproxy.meulie.net/statistics/
 5.161.108.85	vern.cc	https://tilde.wiki/Known_tildes
 23.133.40.12	tilde.town	tildeverse.org
+24.33.195.204	syn-024-033-195-204.res.spectrum.com	friendly
 45.33.66.185	rawtext.club	https://tilde.wiki/Known_tildes
 45.58.42.71	rw.rs	tildeverse.org
 46.225.38.51	vps-d19ace40.vps.ovh.net	friendly
 54.38.176.219	tilde.green	tildeverse.org
 54.68.197.142	nyx.nyx.net	pubnix

]]></content>
<updated>2025-08-27 16:22:47</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/bea905f030/</id>
<title>Add new friendly IP address to pass.tsv</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-08-26 19:56:21

Add new friendly IP address to pass.tsv

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -8,10 +8,11 @@
 54.38.176.219	tilde.green	tildeverse.org
 54.68.197.142	nyx.nyx.net	pubnix
 54.149.138.40	hackers.cool	https://tilde.wiki/Known_tildes
 64.190.63.222	pebble.ink	https://tilde.wiki/Known_tildes
 63.155.6.201	63-155-6-201.eugn.qwest.net	friendly
+79.116.139.115	79-116-139-115.digimobil.es	friendly
 79.116.139.107	79-116-139-107.digimobil.es	friendly
 82.64.43.33	atl.huld.re	friendly
 85.214.104.232	tilde.fun	https://tilde.wiki/Known_tildes
 88.198.101.33	r-36.net	friendly
 89.163.145.170	envs.net	https://tilde.wiki/Known_tildes

]]></content>
<updated>2025-08-26 19:56:21</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/d18a2a633c/</id>
<title>When blocking a client from an item d...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-08-01 07:01:15

  When blocking a client from an item detail, link to the original
  web page.

  INSERTED    DELETED
        13          3 src/cgi.awk
         1          1 src/details/index.dcgi.m4
        14          4 TOTAL over 2 changed files

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -1,6 +1,6 @@
-function block_msg(ip) {
+function block_msg(ip, extra) {
     print "Access Denied"
     print "============="
     print ""
     print "This service only allows access from vetted client IP addresses."
     print "The intent is to thwart abusive bots and crawlers."
@@ -11,14 +11,15 @@
     print "    " contact
     print ""
     print "* Include the word \"friendly\" in your message."
     print ""
     print "Service admin will reply when granted."
+    print extra
     return
 }
 
-function cgi_init(    ip) {
+function cgi_init(     extra, ip) {
     ip = ENVIRON["REMOTE_ADDR"]
 
     search = ARGV[1]
     arguments = ARGV[2]
     traversal = ARGV[5]
@@ -84,11 +85,20 @@
                 break
             }
         }
         close(pass_list)
         if (blocked) {
-            block_msg(ip)
+            extra = ""
+            if (topdir == "details") {
+                extra = "\nIn the meanwhile, see:\n\n" \
+                    sprintf("[h|Web page|URL:%s/details/%s|%s|%s]\n",
+                    api_ssl_endpoint,
+                    uri_encode(parts[3]),
+                    server,
+                    port)
+            }
+            block_msg(ip, extra)
             exit(0)
         }
     }
 
     return

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -222,9 +222,9 @@
 }
 
 BEGIN {
     config_init()
 
-    cgi_init()
     uri_encode_init()
+    cgi_init()
     main()
 }

]]></content>
<updated>2025-08-01 07:01:15</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/303250f6bc/</id>
<title>Wait to block clients until CGI input...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-08-01 07:00:15

Wait to block clients until CGI input has been parsed.

  INSERTED    DELETED
        21         20 src/cgi.awk
        21         20 TOTAL over 1 changed file

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -16,30 +16,10 @@
     return
 }
 
 function cgi_init(    ip) {
     ip = ENVIRON["REMOTE_ADDR"]
-    if (pass_enabled) {
-        # default deny access except client IP addresses in pass list
-        blocked = 1
-        FS = "\t"
-        while ((getline < pass_list) > 0) {
-            if (match(/^#/, $0)) {
-                # ignore comments
-                continue
-            }
-            if (ip == $1) {
-                blocked = 0
-                break
-            }
-        }
-        close(pass_list)
-        if (blocked) {
-            block_msg(ip)
-            exit(0)
-        }
-    }
 
     search = ARGV[1]
     arguments = ARGV[2]
     traversal = ARGV[5]
     selector = ARGV[6]
@@ -87,10 +67,31 @@
         path = substr(path, 1, RSTART - 1)
     }
 
     split(path, parts, "/")
     topdir = parts[2]
+
+    if (pass_enabled) {
+        # default deny access except client IP addresses in pass list
+        blocked = 1
+        FS = "\t"
+        while ((getline < pass_list) > 0) {
+            if (match(/^#/, $0)) {
+                # ignore comments
+                continue
+            }
+            if (ip == $1) {
+                blocked = 0
+                break
+            }
+        }
+        close(pass_list)
+        if (blocked) {
+            block_msg(ip)
+            exit(0)
+        }
+    }
 
     return
 }
 
 function gph_encode(str,     retval) {

]]></content>
<updated>2025-08-01 07:00:15</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/c5f1d3c11e/</id>
<title>Add atl.huld.re to pass.tsv</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-08-01 06:00:14

Add atl.huld.re to pass.tsv

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -9,10 +9,11 @@
 54.68.197.142	nyx.nyx.net	pubnix
 54.149.138.40	hackers.cool	https://tilde.wiki/Known_tildes
 64.190.63.222	pebble.ink	https://tilde.wiki/Known_tildes
 63.155.6.201	63-155-6-201.eugn.qwest.net	friendly
 79.116.139.107	79-116-139-107.digimobil.es	friendly
+82.64.43.33	atl.huld.re	friendly
 85.214.104.232	tilde.fun	https://tilde.wiki/Known_tildes
 88.198.101.33	r-36.net	friendly
 89.163.145.170	envs.net	https://tilde.wiki/Known_tildes
 95.179.178.246	tilde.guru	tildeverse.org
 95.216.6.12	tilde.pink	tildeverse.org

]]></content>
<updated>2025-08-01 06:00:14</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/8830b2f656/</id>
<title>Remove "All Books" link.  The books c...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-07-23 17:01:23

  Remove "All Books" link.  The books collection is almost
  entirely comprised of access-restricted-item's.  It only
  contains 13 books that can be freely accessed. 
  <https://archive.org/search?query=collection%3A(books) AND
  -access-restricted-item%3A(true)>

  INSERTED    DELETED
         0          1 src/books/index.gph.m4
         0          1 TOTAL over 1 changed file

Index: src/books/index.gph.m4
==================================================================
--- src/books/index.gph.m4
+++ src/books/index.gph.m4
@@ -1,9 +1,8 @@
 include(config.m4)dnl
 # Books
 
-[1|All Books|__CGIPATH__/search/sortweek desc/%09collection:books AND -access-restricted-item:true|__SERVER__|__PORT__]
 [1|All Texts|__CGIPATH__/search/sortweek desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
 [1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
 [1|Smithsonian Libraries|__CGIPATH__/search/sortweek desc/%09collection:smithsonian|__SERVER__|__PORT__]
 [1|FEDLINK|__CGIPATH__/search/sortweek desc/%09collection:fedlink AND mediatype:collection|__SERVER__|__PORT__]
 [1|Genealogy|__CGIPATH__/search/sortweek desc/%09collection:genealogy AND mediatype:collection|__SERVER__|__PORT__]

]]></content>
<updated>2025-07-23 17:01:23</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/a85cea2208/</id>
<title>Add new friendly client IP address to...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-07-21 20:55:40

Add new friendly client IP address to pass.tsv

  INSERTED    DELETED
         1          0 pass.tsv
         1          0 TOTAL over 1 changed file

Index: pass.tsv
==================================================================
--- pass.tsv
+++ pass.tsv
@@ -8,10 +8,11 @@
 54.38.176.219	tilde.green	tildeverse.org
 54.68.197.142	nyx.nyx.net	pubnix
 54.149.138.40	hackers.cool	https://tilde.wiki/Known_tildes
 64.190.63.222	pebble.ink	https://tilde.wiki/Known_tildes
 63.155.6.201	63-155-6-201.eugn.qwest.net	friendly
+79.116.139.107	79-116-139-107.digimobil.es	friendly
 85.214.104.232	tilde.fun	https://tilde.wiki/Known_tildes
 88.198.101.33	r-36.net	friendly
 89.163.145.170	envs.net	https://tilde.wiki/Known_tildes
 95.179.178.246	tilde.guru	tildeverse.org
 95.216.6.12	tilde.pink	tildeverse.org

]]></content>
<updated>2025-07-21 20:55:40</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/aa7c71cf55/</id>
<title>Explicitly use tab to split fields fr...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-07-06 23:18:38

Explicitly use tab to split fields from pass.tsv

  INSERTED    DELETED
         1          0 src/cgi.awk
         1          0 TOTAL over 1 changed file

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -19,10 +19,11 @@
 function cgi_init(    ip) {
     ip = ENVIRON["REMOTE_ADDR"]
     if (pass_enabled) {
         # default deny access except client IP addresses in pass list
         blocked = 1
+        FS = "\t"
         while ((getline < pass_list) > 0) {
             if (match(/^#/, $0)) {
                 # ignore comments
                 continue
             }

]]></content>
<updated>2025-07-06 23:18:38</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/9058a38d80/</id>
<title>Add feature to default deny, granting...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-07-06 02:08:13

  Add feature to default deny, granting access only to client IP
  addresses in pass list.  Pre-populate pass.tsv with known
  tildeverse plus a few known pubnix and friendly IP addresses.

  INSERTED    DELETED
         4          1 config.m4
         1          0 make.sh
        68          0 pass.tsv
        39          0 src/cgi.awk
         3          0 src/config.awk
       115          1 TOTAL over 5 changed files

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -1,8 +1,8 @@
 dnl Set configuration variables
 dnl
-define(__PHAROS_VERSION__, 12)dnl
+define(__PHAROS_VERSION__, 13)dnl
 dnl
 define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
 define(__API_ENDPOINT__, http://archive.org)dnl
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
 define(__AWK_EXT__, 0)dnl
@@ -17,17 +17,20 @@
 define(__CMD_RM__, rm)dnl
 define(__CMD_SQLITE__, /usr/bin/sqlite3)dnl
 define(__CMD_STRINGS__, /bin/busybox strings)dnl
 define(__CMD_WEBDUMP__, /usr/local/bin/webdump)dnl
 define(__CMD_XARGS__, /usr/bin/xargs)dnl
+define(__CONTACT__, admin@example.com)dnl
 define(__GEOMYIDAE_VERSION__, 0.96)dnl
 define(__MAX_BIN_SIZE__, 10)dnl
 define(__MAX_TXT_SIZE__, 1)dnl
+define(__PASS_ENABLED__, 0)dnl
+define(__PASS_LIST__, /home/user/pharos/pass.tsv)dnl
 define(__SERVER__, server)dnl
 define(__PORT__, port)dnl
 dnl
 dnl Set up M4 to work with AWK code
 dnl
 define(m4_substr, defn(substr))dnl
 undefine(substr)dnl
 changecom(`/*', `*/')dnl
 define(incl,`ifdef(paste,paste($1),undivert($1))')dnl

Index: make.sh
==================================================================
--- make.sh
+++ make.sh
@@ -36,6 +36,7 @@
 # WEIRD
 build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/debug/index.cgi
 build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/text/index.cgi
 build $SRC ${SRC}/download/index.dcgi.m4 ${DESTDIR}/direct/index.dcgi
 build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/links/index.dcgi
+cp pass.tsv ${DESTDIR}/pass.tsv
 exit 0

ADDED   pass.tsv
Index: pass.tsv
==================================================================
--- /dev/null
+++ pass.tsv
@@ -0,0 +1,68 @@
+#IP	name	notes
+#2a01:4f8:191:63af::3	de3-apache2.meulie.net	www proxy crawled a lot https://gopherproxy.meulie.net/statistics/
+5.161.108.85	vern.cc	https://tilde.wiki/Known_tildes
+23.133.40.12	tilde.town	tildeverse.org
+45.33.66.185	rawtext.club	https://tilde.wiki/Known_tildes
+45.58.42.71	rw.rs	tildeverse.org
+46.225.38.51	vps-d19ace40.vps.ovh.net	friendly
+54.38.176.219	tilde.green	tildeverse.org
+54.68.197.142	nyx.nyx.net	pubnix
+54.149.138.40	hackers.cool	https://tilde.wiki/Known_tildes
+64.190.63.222	pebble.ink	https://tilde.wiki/Known_tildes
+63.155.6.201	63-155-6-201.eugn.qwest.net	friendly
+85.214.104.232	tilde.fun	https://tilde.wiki/Known_tildes
+88.198.101.33	r-36.net	friendly
+89.163.145.170	envs.net	https://tilde.wiki/Known_tildes
+95.179.178.246	tilde.guru	tildeverse.org
+95.216.6.12	tilde.pink	tildeverse.org
+104.238.186.103	cosmic.voyage	tildeverse.org
+116.202.128.144	freeshell.de	pubnix
+129.146.136.128	tylde.karx.xyz	https://tilde.wiki/Known_tildes
+138.197.211.90	skylab.org	https://tilde.wiki/Known_tildes
+142.44.150.184	tilde.club	tildeverse.org
+142.44.150.185	thunix.net	tildeverse.org
+149.210.185.89	codemadness.org	www proxy
+157.90.196.56	fr.tild3.org	https://tilde.wiki/Known_tildes
+159.69.146.152	tilde.institute	tildeverse.org
+159.196.9.199	gopher.mills.io	www proxy
+159.203.148.233	squiggle.city	https://tilde.wiki/Known_tildes
+165.227.127.54	ctrl-c.club	tildeverse.org
+166.84.1.8	shell.panix.com	pubnix
+166.84.1.9	shell.panix.com	pubnix
+166.84.1.10	shell.panix.com	pubnix
+172.66.40.245	crime.team	https://tilde.wiki/Known_tildes
+172.66.43.11	crime.team	https://tilde.wiki/Known_tildes
+173.32.203.226	piepi.art	https://tilde.wiki/Known_tildes
+185.179.143.92	xinu.me	https://tilde.wiki/Known_tildes
+192.9.177.29	aussies.space	tildeverse.org
+192.80.49.36	ampm.floodgap.com	www proxy
+198.50.210.248	tilde.team	tildeverse.org
+205.166.94.16	sdf.org	pubnix
+205.166.94.4	tty.sdf.org	pubnix
+205.166.94.9	tty.sdf.org	pubnix
+207.246.69.54	texto-plano.xyz	tildeverse.org
+209.50.56.120	remotes.club	https://tilde.wiki/Known_tildes
+209.97.187.90	dimension.sh	https://tilde.wiki/Known_tildes
+213.108.110.40	tilde.pt	https://tilde.wiki/Known_tildes
+2001:41d0:303:5ddb::	tilde.green	tildeverse.org
+2001:41d0:304:200::83e5	vps-d19ace40.vps.ovh.net	friendly
+2001:4ba0:ffa4:180::2	envs.net	https://tilde.wiki/Known_tildes
+2600:3c00::f03c:92ff:fe97:26fd	gopher.zcrayfish.soy	www proxy
+2602:f72c:0:400::be50:77ed	tilde.town	tildeverse.org
+2604:a880:2:d0::493a:b001	skylab.org	https://tilde.wiki/Known_tildes
+2604:a880:800:a1::14ff:9001	squiccle.city	https://tilde.wiki/Known_tildes
+2605:7380:1000:1310:202e:aff:fe0a:3ccb	remotes.club	https://tilde.wiki/Known_tildes
+2606:4700:3108::ac42:28f5	crime.team	https://tilde.wiki/Known_tildes
+2606:4700:3108::ac42:2b0b	crime.team	https://tilde.wiki/Known_tildes
+2607:5300:60:4f58::248	tilde.team	tildeverse.org & www proxy
+2607:5300:203:31b6::115	tilde.club	tildeverse.org
+2607:5300:203:31b6::185	thunix.net	tildeverse.org
+2a01:238:43f9:7700:2bc8:48bf:b264:3fc1	tilde.fun	https://tilde.wiki/Known_tildes
+2a01:4f8:222:19a::3	r-36.net	friendly
+2a01:4f8:231:482b::2	freeshell.de	pubnix
+2a01:4f8:252:3e22::56	fr.tild3.org	https://tilde.wiki/Known_tildes
+2a01:4f9:2a:65b::4	tilde.pink	tildeverse.org
+2a01:4ff:f0:4fb3::1	vern.cc	https://tilde.wiki/Known_tildes
+2a03:6000:6e65:627::37	fugu.contractcoder.biz	friendly
+2a03:b0c0:1:e0::38a:c001	dimension.sh	https://tilde.wiki/Known_tildes
+2a05:f480:1400:a93:5400:3ff:fe7b:d45d	tilde.guru	tildeverse.org

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -1,6 +1,45 @@
+function block_msg(ip) {
+    print "Access Denied"
+    print "============="
+    print ""
+    print "This service only allows access from vetted client IP addresses."
+    print "The intent is to thwart abusive bots and crawlers."
+    print ""
+    print "To gain access:"
+    print ""
+    print "* Email your client IP address (" ip ") to:"
+    print "    " contact
+    print ""
+    print "* Include the word \"friendly\" in your message."
+    print ""
+    print "Service admin will reply when granted."
+    return
+}
+
 function cgi_init(    ip) {
+    ip = ENVIRON["REMOTE_ADDR"]
+    if (pass_enabled) {
+        # default deny access except client IP addresses in pass list
+        blocked = 1
+        while ((getline < pass_list) > 0) {
+            if (match(/^#/, $0)) {
+                # ignore comments
+                continue
+            }
+            if (ip == $1) {
+                blocked = 0
+                break
+            }
+        }
+        close(pass_list)
+        if (blocked) {
+            block_msg(ip)
+            exit(0)
+        }
+    }
+
     search = ARGV[1]
     arguments = ARGV[2]
     traversal = ARGV[5]
     selector = ARGV[6]
 

Index: src/config.awk
==================================================================
--- src/config.awk
+++ src/config.awk
@@ -13,12 +13,15 @@
     cmd_rm = "__CMD_RM__"
     cmd_sqlite = "__CMD_SQLITE__"
     cmd_strings = "__CMD_STRINGS__"
     cmd_webdump = "__CMD_WEBDUMP__"
     cmd_xargs = "__CMD_XARGS__"
+    contact = "__CONTACT__"
     geomyidae_version = __GEOMYIDAE_VERSION__
     max_bin_size = __MAX_BIN_SIZE__
     max_txt_size = __MAX_TXT_SIZE__
+    pass_enabled = __PASS_ENABLED__
+    pass_list = "__PASS_LIST__"
     server = "__SERVER__"
     port = "__PORT__"
     return
 }

]]></content>
<updated>2025-07-06 02:08:13</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/3f32b09be6/</id>
<title>Remove feature to block by IP address.</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-07-06 01:31:02

Remove feature to block by IP address.

  INSERTED    DELETED
         0          1 config.m4
         0          1 readme.txt
         0          6 src/account/index.dcgi.m4
         0         11 src/cgi.awk
         0          1 src/config.awk
         0          6 src/details/index.dcgi.m4
         0          6 src/download/index.dcgi.m4
         0          6 src/list/index.dcgi.m4
         0          6 src/lists/index.dcgi.m4
         0          6 src/listsort/index.dcgi.m4
         0          6 src/raw/index.cgi.m4
         0          6 src/search/index.dcgi.m4
         0          6 src/sort/index.dcgi.m4
         0          6 src/wizard/step1/index.dcgi.m4
         0          6 src/wizard/step2/index.dcgi.m4
         0          6 src/wizard/step3/index.dcgi.m4
         0         86 TOTAL over 16 changed files

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -4,11 +4,10 @@
 dnl
 define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
 define(__API_ENDPOINT__, http://archive.org)dnl
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
 define(__AWK_EXT__, 0)dnl
-define(__BLOCKLIST__, /home/user/pharos/block.txt)dnl
 define(__CACHE_DB__, /home/user/pharos/db/cache.dat)dnl
 define(__CACHE_ENABLED__, 0)dnl
 define(__CGIPATH__, /~user/pharos)dnl
 define(__CMD_AWK__, /usr/bin/awk)dnl
 define(__CMD_CURL__, /usr/bin/curl)dnl

Index: readme.txt
==================================================================
--- readme.txt
+++ readme.txt
@@ -33,11 +33,10 @@
 Configuration
 =============
 To set configuration variables, edit config.m4
 
 AWK_EXT allows use of non-standard features in gawk and mawk
-BLOCKLIST is a file to block by IP address, one regex per line
 CACHE_ENABLED caches content in sqlite to reduce API calls
 
 Installation
 ============
 Installation depends on m4.

Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ src/account/index.dcgi.m4
@@ -18,16 +18,10 @@
     type, url)
 {
     acct = parts[3]
     email = search
 
-    if (blocked) {
-        printf "[h|Account|URL:%s/details/%s|%s|%s]\n",
-            api_ssl_endpoint, acct, server, port
-        exit 0
-    }
-
     signature = sprintf("account/%s/%s", acct, email)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -1,17 +1,6 @@
 function cgi_init(    ip) {
-    blocked = 0
-    ip = ENVIRON["REMOTE_ADDR"]
-    while ((getline < blocklist) > 0) {
-        # bad bot ignoring robots.txt, block by IP address
-        if (match(ip, $0)) {
-            blocked = 1
-            break
-        }
-    }
-    close(blocklist)
-
     search = ARGV[1]
     arguments = ARGV[2]
     traversal = ARGV[5]
     selector = ARGV[6]
 

Index: src/config.awk
==================================================================
--- src/config.awk
+++ src/config.awk
@@ -1,11 +1,10 @@
 function config_init() {
     agent = "__AGENT__"
     api_endpoint = "__API_ENDPOINT__"
     api_ssl_endpoint = "__API_SSL_ENDPOINT__"
     awk_ext = __AWK_EXT__
-    blocklist = "__BLOCKLIST__"
     cache_db = "__CACHE_DB__"
     cache_enabled = __CACHE_ENABLED__
     cgipath = "__CGIPATH__"
     cmd_curl = "__CMD_CURL__"
     cmd_enc = "__CMD_ENV__"

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -18,16 +18,10 @@
     license, output, pub_date, scanner, signature, str, thumb, title,
     topic, topics, type, uploader_account, uploader_email, url)
 {
     item_id = parts[3]
 
-    if (blocked) {
-        printf "[h|Details|URL:%s/details/%s|%s|%s]\n",
-            api_ssl_endpoint, item_id, server, port
-        exit 0
-    }
-
     signature = "details/" item_id
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return

Index: src/download/index.dcgi.m4
==================================================================
--- src/download/index.dcgi.m4
+++ src/download/index.dcgi.m4
@@ -14,16 +14,10 @@
     is_proxy, item_server, label, mtime, name, source, url)
 {
     dir = parts[2]
     item_id = parts[3]
 
-    if (blocked) {
-        printf "[h|Download|URL:%s/download/%s|%s|%s]\n",
-            api_ssl_endpoint, item_id, server, port
-        exit 0
-    }
-
     if (dir == "download") {
         is_proxy = 1
     } else {
         # dir == "direct"
         is_proxy = 0

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -43,16 +43,10 @@
 
     split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
 
-    if (blocked) {
-        printf "[h|List|URL:%s/details/%s/lists/%d|%s|%s]\n",
-            api_ssl_endpoint, acct, list_id, server, port
-        exit 0
-    }
-
     signature = sprintf("list/%s/%s", input, search)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -14,16 +14,10 @@
 
 function main(     cmd, count, fields, iaout, i, id, is_private, item,
     item_count, item_id, label, name, output, record, records,
     signature, str, url)
 {
-    if (blocked) {
-        printf "[h|Lists|URL:%s/details/%s/lists|%s|%s]\n",
-            api_ssl_endpoint, search, server, port
-        exit 0
-    }
-
     signature = sprintf("%s/lists", search)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return

Index: src/listsort/index.dcgi.m4
==================================================================
--- src/listsort/index.dcgi.m4
+++ src/listsort/index.dcgi.m4
@@ -7,16 +7,10 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     acct, i, lbl, list_id, opt) {
-    if (blocked) {
-        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
-            api_ssl_endpoint, server, port
-        exit 0
-    }
-
     lbl[1] = "Relevance"
     opt[1] = ""
     lbl[2] = "Weekly views [^]"
     opt[2] = "week asc"
     lbl[3] = "Weekly views [v]"

Index: src/raw/index.cgi.m4
==================================================================
--- src/raw/index.cgi.m4
+++ src/raw/index.cgi.m4
@@ -22,16 +22,10 @@
 incl(src/cgi.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 
 function main() {
-    if (blocked) {
-        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
-            api_ssl_endpoint, server, port
-        exit 0
-    }
-
     if (path == "/debug/") {
         dump(search, TYPE_HEADERS)
     } else if (path == "/raw/") {
         dump(search, TYPE_RAW)
     } else if (path == "/text/") {

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -13,16 +13,10 @@
 function main(search,     cmd, count, creator, descr, field, fields, i,
     iaout, id, item, items, jsout, label, numfound, order,
     order_names, page, rows, searchstr, sort_param, str, title, type,
     url)
 {
-    if (blocked) {
-        printf "[h|Search|URL:%s/|%s|%s]\n", api_ssl_endpoint, 
-            server, port
-        exit 0
-    }
-
     order_names["addeddate"] = "addeddate"
     order_names["collection_size"] = "collection_size"
     order_names["createddate"] = "createddate"
     order_names["creator"] = "creatorSorter"
     order_names["date"] = "date"

Index: src/sort/index.dcgi.m4
==================================================================
--- src/sort/index.dcgi.m4
+++ src/sort/index.dcgi.m4
@@ -7,16 +7,10 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     i, lbl, opt) {
-    if (blocked) {
-        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
-            api_ssl_endpoint, server, port
-        exit 0
-    }
-
     lbl[1]  = "Default [^]"
     opt[1]  = "nav_order asc"
     lbl[2]  = "Default [v]"
     opt[2]  = "nav_order desc"
     lbl[3]  = "Weekly views [^]"

Index: src/wizard/step1/index.dcgi.m4
==================================================================
--- src/wizard/step1/index.dcgi.m4
+++ src/wizard/step1/index.dcgi.m4
@@ -7,16 +7,10 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     i, lbl, opt, searchstr) {
-    if (blocked) {
-        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
-            api_ssl_endpoint, server, port
-        exit 0
-    }
-
     searchstr = parts[4]
 
     lbl[1]  = "Any field contains"
     opt[1]  = "anyfield"
     lbl[2]  = "Any field does not contain"

Index: src/wizard/step2/index.dcgi.m4
==================================================================
--- src/wizard/step2/index.dcgi.m4
+++ src/wizard/step2/index.dcgi.m4
@@ -7,16 +7,10 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     field, newsearch, searchstr) {
-    if (blocked) {
-        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
-            api_ssl_endpoint, server, port
-        exit 0
-    }
-
     field = parts[4]
     searchstr = parts[5]
 
     if (field == "mediatype") {
         print "# Mediatype is:"

Index: src/wizard/step3/index.dcgi.m4
==================================================================
--- src/wizard/step3/index.dcgi.m4
+++ src/wizard/step3/index.dcgi.m4
@@ -7,16 +7,10 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     field, label, newsearch, op, searchstr, value) {
-    if (blocked) {
-        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
-            api_ssl_endpoint, server, port
-        exit 0
-    }
-
     field = parts[4]
     searchstr = parts[5]
     value = search
 
     if (field ~ /^-/) {

]]></content>
<updated>2025-07-06 01:31:02</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/7253bacd27/</id>
<title>Include search string in list signatu...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-06-19 19:52:57

  Include search string in list signature to avoid cache
  collisions.

  INSERTED    DELETED
         1          1 src/list/index.dcgi.m4
         1          1 TOTAL over 1 changed file

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -49,11 +49,11 @@
         printf "[h|List|URL:%s/details/%s/lists/%d|%s|%s]\n",
             api_ssl_endpoint, acct, list_id, server, port
         exit 0
     }
 
-    signature = sprintf("list/%s", input)
+    signature = sprintf("list/%s/%s", input, search)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return
     }

]]></content>
<updated>2025-06-19 19:52:57</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/9a9884612f/</id>
<title>Set global variabled blocked=1 in cgi...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-06-19 03:33:26

  Set global variabled blocked=1 in cgi.awk function cgi_init()
  when client IP address is in block.txt.  Change how blocked
  clients are treated.  OLD: Send empty document NEW: Send item
  URL to redirect back to archive.org  My intention is to block
  gopher to web proxies without leaving end users stuck on an
  empty document.  Instead, bots and users on blocked IP addresses
  will be redirected to item web pages.

  INSERTED    DELETED
         6          0 src/account/index.dcgi.m4
         3          1 src/cgi.awk
         6          0 src/details/index.dcgi.m4
         7          0 src/download/index.dcgi.m4
         6          0 src/list/index.dcgi.m4
         6          0 src/lists/index.dcgi.m4
         6          0 src/listsort/index.dcgi.m4
         6          0 src/raw/index.cgi.m4
         6          0 src/search/index.dcgi.m4
         6          0 src/sort/index.dcgi.m4
         6          0 src/wizard/step1/index.dcgi.m4
         6          0 src/wizard/step2/index.dcgi.m4
         6          0 src/wizard/step3/index.dcgi.m4
        76          1 TOTAL over 13 changed files

Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ src/account/index.dcgi.m4
@@ -17,10 +17,16 @@
     item_server, item_size, output, signature, str, thumb, title,
     type, url)
 {
     acct = parts[3]
     email = search
+
+    if (blocked) {
+        printf "[h|Account|URL:%s/details/%s|%s|%s]\n",
+            api_ssl_endpoint, acct, server, port
+        exit 0
+    }
 
     signature = sprintf("account/%s/%s", acct, email)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -1,11 +1,13 @@
 function cgi_init(    ip) {
+    blocked = 0
     ip = ENVIRON["REMOTE_ADDR"]
     while ((getline < blocklist) > 0) {
         # bad bot ignoring robots.txt, block by IP address
         if (match(ip, $0)) {
-            exit 0
+            blocked = 1
+            break
         }
     }
     close(blocklist)
 
     search = ARGV[1]

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -17,10 +17,16 @@
     iaout, id, item_id, item_server, item_size, label, language,
     license, output, pub_date, scanner, signature, str, thumb, title,
     topic, topics, type, uploader_account, uploader_email, url)
 {
     item_id = parts[3]
+
+    if (blocked) {
+        printf "[h|Details|URL:%s/details/%s|%s|%s]\n",
+            api_ssl_endpoint, item_id, server, port
+        exit 0
+    }
 
     signature = "details/" item_id
     str = cache_init(signature)
     if (length(str) > 0) {
         print str

Index: src/download/index.dcgi.m4
==================================================================
--- src/download/index.dcgi.m4
+++ src/download/index.dcgi.m4
@@ -13,10 +13,17 @@
 function main(cmd, dir, files, file_size, format, iaout, is_archive,
     is_proxy, item_server, label, mtime, name, source, url)
 {
     dir = parts[2]
     item_id = parts[3]
+
+    if (blocked) {
+        printf "[h|Download|URL:%s/download/%s|%s|%s]\n",
+            api_ssl_endpoint, item_id, server, port
+        exit 0
+    }
+
     if (dir == "download") {
         is_proxy = 1
     } else {
         # dir == "direct"
         is_proxy = 0

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -42,10 +42,16 @@
     }
 
     split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
+
+    if (blocked) {
+        printf "[h|List|URL:%s/details/%s/lists/%d|%s|%s]\n",
+            api_ssl_endpoint, acct, list_id, server, port
+        exit 0
+    }
 
     signature = sprintf("list/%s", input)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -14,10 +14,16 @@
 
 function main(     cmd, count, fields, iaout, i, id, is_private, item,
     item_count, item_id, label, name, output, record, records,
     signature, str, url)
 {
+    if (blocked) {
+        printf "[h|Lists|URL:%s/details/%s/lists|%s|%s]\n",
+            api_ssl_endpoint, search, server, port
+        exit 0
+    }
+
     signature = sprintf("%s/lists", search)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return

Index: src/listsort/index.dcgi.m4
==================================================================
--- src/listsort/index.dcgi.m4
+++ src/listsort/index.dcgi.m4
@@ -7,10 +7,16 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     acct, i, lbl, list_id, opt) {
+    if (blocked) {
+        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
+            api_ssl_endpoint, server, port
+        exit 0
+    }
+
     lbl[1] = "Relevance"
     opt[1] = ""
     lbl[2] = "Weekly views [^]"
     opt[2] = "week asc"
     lbl[3] = "Weekly views [v]"

Index: src/raw/index.cgi.m4
==================================================================
--- src/raw/index.cgi.m4
+++ src/raw/index.cgi.m4
@@ -22,10 +22,16 @@
 incl(src/cgi.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 
 function main() {
+    if (blocked) {
+        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
+            api_ssl_endpoint, server, port
+        exit 0
+    }
+
     if (path == "/debug/") {
         dump(search, TYPE_HEADERS)
     } else if (path == "/raw/") {
         dump(search, TYPE_RAW)
     } else if (path == "/text/") {

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -13,10 +13,16 @@
 function main(search,     cmd, count, creator, descr, field, fields, i,
     iaout, id, item, items, jsout, label, numfound, order,
     order_names, page, rows, searchstr, sort_param, str, title, type,
     url)
 {
+    if (blocked) {
+        printf "[h|Search|URL:%s/|%s|%s]\n", api_ssl_endpoint, 
+            server, port
+        exit 0
+    }
+
     order_names["addeddate"] = "addeddate"
     order_names["collection_size"] = "collection_size"
     order_names["createddate"] = "createddate"
     order_names["creator"] = "creatorSorter"
     order_names["date"] = "date"

Index: src/sort/index.dcgi.m4
==================================================================
--- src/sort/index.dcgi.m4
+++ src/sort/index.dcgi.m4
@@ -7,10 +7,16 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     i, lbl, opt) {
+    if (blocked) {
+        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
+            api_ssl_endpoint, server, port
+        exit 0
+    }
+
     lbl[1]  = "Default [^]"
     opt[1]  = "nav_order asc"
     lbl[2]  = "Default [v]"
     opt[2]  = "nav_order desc"
     lbl[3]  = "Weekly views [^]"

Index: src/wizard/step1/index.dcgi.m4
==================================================================
--- src/wizard/step1/index.dcgi.m4
+++ src/wizard/step1/index.dcgi.m4
@@ -7,10 +7,16 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     i, lbl, opt, searchstr) {
+    if (blocked) {
+        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
+            api_ssl_endpoint, server, port
+        exit 0
+    }
+
     searchstr = parts[4]
 
     lbl[1]  = "Any field contains"
     opt[1]  = "anyfield"
     lbl[2]  = "Any field does not contain"

Index: src/wizard/step2/index.dcgi.m4
==================================================================
--- src/wizard/step2/index.dcgi.m4
+++ src/wizard/step2/index.dcgi.m4
@@ -7,10 +7,16 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     field, newsearch, searchstr) {
+    if (blocked) {
+        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
+            api_ssl_endpoint, server, port
+        exit 0
+    }
+
     field = parts[4]
     searchstr = parts[5]
 
     if (field == "mediatype") {
         print "# Mediatype is:"

Index: src/wizard/step3/index.dcgi.m4
==================================================================
--- src/wizard/step3/index.dcgi.m4
+++ src/wizard/step3/index.dcgi.m4
@@ -7,10 +7,16 @@
 
 include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     field, label, newsearch, op, searchstr, value) {
+    if (blocked) {
+        printf "[h|Internet Archive|URL:%s/|%s|%s]\n",
+            api_ssl_endpoint, server, port
+        exit 0
+    }
+
     field = parts[4]
     searchstr = parts[5]
     value = search
 
     if (field ~ /^-/) {

]]></content>
<updated>2025-06-19 03:33:26</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/9e43349b2e/</id>
<title>Don't cache "not found", may be outag...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-06-06 16:03:24

Don't cache "not found", may be outage or scheduled maintenance

  INSERTED    DELETED
         6          0 src/cache.awk
         6          0 TOTAL over 1 changed file

Index: src/cache.awk
==================================================================
--- src/cache.awk
+++ src/cache.awk
@@ -49,10 +49,16 @@
 
 function cache_set_value(value,    sql) {
     if (!cache_enabled) {
         return
     }
+
+    # don't cache "not found", may be outage or scheduled maintenance
+    if (value ~ /^Item cannot be found/) {
+        return
+    }
+
     sql = "REPLACE INTO cache(id, ip, epoch, result) VALUES('" \
         sqlite_escape(cache_signature) "', '" \
         ENVIRON["REMOTE_ADDR"] "', unixepoch(), '" \
         sqlite_escape(value) "')"
     sqlite_exec(cache_db, sql)

]]></content>
<updated>2025-06-06 16:03:24</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/b87f964a15/</id>
<title>Use string concatenation to avoid maw...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-05-24 17:26:38

  Use string concatenation to avoid mawk sprintf() limits in
  function cache_set()

  INSERTED    DELETED
         4          5 src/cache.awk
         4          5 TOTAL over 1 changed file

Index: src/cache.awk
==================================================================
--- src/cache.awk
+++ src/cache.awk
@@ -49,13 +49,12 @@
 
 function cache_set_value(value,    sql) {
     if (!cache_enabled) {
         return
     }
-    sql = sprintf("REPLACE INTO cache(id, ip, epoch, result) \
-        VALUES('%s', '%s', unixepoch(), '%s')",
-        sqlite_escape(cache_signature),
-        ENVIRON["REMOTE_ADDR"],
-        sqlite_escape(value))
+    sql = "REPLACE INTO cache(id, ip, epoch, result) VALUES('" \
+        sqlite_escape(cache_signature) "', '" \
+        ENVIRON["REMOTE_ADDR"] "', unixepoch(), '" \
+        sqlite_escape(value) "')"
     sqlite_exec(cache_db, sql)
     return
 }

]]></content>
<updated>2025-05-24 17:26:38</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/ba8329da42/</id>
<title>Default to sorting a user's uploads b...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-05-08 15:23:54

  Default to sorting a user's uploads by date added in descending
  order.

  INSERTED    DELETED
         4          3 src/account/index.dcgi.m4
         4          3 TOTAL over 1 changed file

Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ src/account/index.dcgi.m4
@@ -89,15 +89,16 @@
             server, port >>output
     }
     print_html(output, descr)
 
     if (length(email) > 0) {
-        printf "[1|Uploads|%s/search/%%09uploader:%s|%s|%s]\n",
+        printf "[1|Uploads|%s/search/sortaddeddate desc%%09uploader:%s|" \
+            "%s|%s]\n",
             cgipath, email, server, port >>output
     }
-    printf "[1|Items|%s/search/%%09anyfield:%s|%s|%s]\n", cgipath,
-        acct, server, port >>output
+    printf "[1|Items|%s/search/sortaddeddate desc%%09anyfield:%s|%s|%s]\n",
+        cgipath, acct, server, port >>output
     printf "[1|Lists|%s/lists/%%09%s|%s|%s]\n", cgipath, acct,
         server, port >>output
 
     print "" >>output
     printf "%-20s %s\n", "Identifier:", id >>output

]]></content>
<updated>2025-05-08 15:23:54</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/86a47da961/</id>
<title>When formatting HTML make sure to pri...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-05-01 02:19:44

  When formatting HTML make sure to print URL referrences in the
  correct place so that they get cached and displayed in the
  correct order.

  INSERTED    DELETED
         1          1 src/web.awk
         1          1 TOTAL over 1 changed file

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -190,11 +190,11 @@
             }
         }
         if (length(id) > 0) {
             label = prefix id
             printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
-                id, server, port
+                id, server, port >>output
         } else {
              print str >>output
         }
     } else {
         print str >>output

]]></content>
<updated>2025-05-01 02:19:44</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/43949a6909/</id>
<title>Change section heading from Similar I...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-30 01:29:18

Change section heading from Similar Items to Similar items

  INSERTED    DELETED
         1          1 src/details/index.dcgi.m4
         1          1 TOTAL over 1 changed file

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -207,11 +207,11 @@
         printf "[1|%s|%s/search/%%09uploader:%s|%s|%s]\n", label,
             cgipath, uploader_email, server, port >>output
     }
 
     print "" >>output
-    print "# Similar Items" >>output
+    print "# Similar items" >>output
     printf "[1|View similar items|%s/search/%%09similar:%s|%s|%s]\n",
         cgipath, item_id, server, port >>output
 
     print "" >>output
     printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output

]]></content>
<updated>2025-04-30 01:29:18</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/5b6df39427/</id>
<title>Move "Web page" below Download in det...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-30 01:22:43

  Move "Web page" below Download in details. Add Similar Items
  section to details. Add similar: special case to search.

  INSERTED    DELETED
         7          2 src/details/index.dcgi.m4
        40          5 src/search/index.dcgi.m4
        47          7 TOTAL over 2 changed files

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -137,10 +137,12 @@
             cgipath, url, server, port >>output
     }
 
     printf "[1|Download|%s/download/%s|%s|%s]\n", cgipath,
         item_id, server, port >>output
+    printf "[h|Web page|URL:%s/details/%s|%s|%s]\n",
+        api_ssl_endpoint, uri_encode(id), server, port >>output
     print "" >>output
 
     print_html(output, descr)
 
     print "" >>output
@@ -205,12 +207,15 @@
         printf "[1|%s|%s/search/%%09uploader:%s|%s|%s]\n", label,
             cgipath, uploader_email, server, port >>output
     }
 
     print "" >>output
-    printf "[h|Item web page|URL:%s/details/%s|%s|%s]\n",
-        api_ssl_endpoint, uri_encode(id), server, port >>output
+    print "# Similar Items" >>output
+    printf "[1|View similar items|%s/search/%%09similar:%s|%s|%s]\n",
+        cgipath, item_id, server, port >>output
+
+    print "" >>output
     printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
 
     cache_end()
     unlink(iaout)
     return

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -9,12 +9,13 @@
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(search,     cmd, count, creator, descr, field, fields, i,
-    iaout, id, jsout, label, numfound, order, order_names, page, rows,
-    searchstr, sort_param, title, type, url)
+    iaout, id, item, items, jsout, label, numfound, order,
+    order_names, page, rows, searchstr, sort_param, str, title, type,
+    url)
 {
     order_names["addeddate"] = "addeddate"
     order_names["collection_size"] = "collection_size"
     order_names["createddate"] = "createddate"
     order_names["creator"] = "creatorSorter"
@@ -40,16 +41,53 @@
                 sort_param = parts[i]
                 order = substr(parts[i], 5)
             }
         }
     }
+
+    iaout = gettemp()
+    jsout = gettemp()
 
     # special case for when the search term is an archive.org details URL
     if (match(search, /^https:\/\/(www\.)?archive\.org\/details\//)) {
         id = substr(search, RLENGTH + 1)
         search = "identifier:" id
     }
+
+    # special case to list similar items
+    if (match(search, /^similar:/)) {
+        id = substr(search, RLENGTH + 1)
+
+        url = api_endpoint "/details/" id "?RelatedItemsGet=1"
+        api_request(url, "GET", iaout)
+    
+        # scrape similar items
+        FS = "\t"
+        items = 0
+        delete item
+        str = ""
+        while ((getline <iaout) > 0) {
+            if (match($0, /<a href="\/details\/[^"]*"/)) {
+                str = substr($0, RSTART + 18, RLENGTH - 19)
+                items++
+                item[items] = str
+            }
+        }
+        close(iaout)
+
+        # truncate temporary file for re-use
+        printf "" >iaout
+        close(iaout)
+
+        for (i = 1; i <= items; i++) {
+            if (i == 1) {
+                search = "identifier:" item[i]
+            } else {
+                search = search "%20OR%20identifier:" item[i]
+            }
+        }
+    }
 
     # remove quotes from search string, since it gets quoted later
     gsub(/"/, "", search)
 
     # default sort orders if none were specified
@@ -63,13 +101,10 @@
             order = "nav_order desc"
             sort_param = "sort" order
         }
     }
 
-    iaout = gettemp()
-    jsout = gettemp()
-
     # get search results
 
     field[1] = "creator"
     field[2] = "description"
     field[3] = "identifier"

]]></content>
<updated>2025-04-30 01:22:43</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/c37a9eb847/</id>
<title>Use consistent spacing in numerical e...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-30 00:57:35

Use consistent spacing in numerical expressions

  INSERTED    DELETED
         4          4 src/cgi.awk
         1          1 src/details/index.dcgi.m4
         2          2 src/search/index.dcgi.m4
         1          1 src/util.awk
         3          3 src/web.awk
        11         11 TOTAL over 5 changed files

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -24,11 +24,11 @@
             selector = substr(path, RSTART)
         }
 
         # remove trailing text from beginning of script name
         if (match(selector, ENVIRON["SCRIPT_NAME"])) {
-            selector = substr(selector, 1, RSTART-1)
+            selector = substr(selector, 1, RSTART - 1)
         }
         selector = selector input
     } else {
         input = traversal
     }
@@ -35,12 +35,12 @@
 
     if (length(search) == 0 && match(input, /%09.*/)) {
         # This is a hack to include a search in the URL.
         # everything before %09 is considered arguments
         # everything after %09 is considered the search
-        search = substr(input, RSTART+3, RLENGTH-3)
-        args = substr(input, 0, RSTART-1)
+        search = substr(input, RSTART + 3, RLENGTH - 3)
+        args = substr(input, 0, RSTART - 1)
     } else {
         args = input
     }
 
     # query is everything after ? in the gopher selector
@@ -51,11 +51,11 @@
     path = selector
     if (substr(path, 1, length(cgipath)) == cgipath) {
         path = substr(path, length(cgipath) + 1)
     }
     if (match(path, /%09/)) {
-        path = substr(path, 1, RSTART-1)
+        path = substr(path, 1, RSTART - 1)
     }
 
     split(path, parts, "/")
     topdir = parts[2]
 

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -177,11 +177,11 @@
     api_request(url, "GET", iaout)
     while ((getline <iaout) > 0) {
         if (/item-upload-info__uploader-name/ &&
             match($0, /\/details\/[^"]*"/))
         {
-            uploader_account = substr($0, RSTART+9, RLENGTH-10)
+            uploader_account = substr($0, RSTART + 9, RLENGTH - 10)
         }
     }
     close(iaout)
 
     if (cols > 0) {

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -43,11 +43,11 @@
         }
     }
 
     # special case for when the search term is an archive.org details URL
     if (match(search, /^https:\/\/(www\.)?archive\.org\/details\//)) {
-        id = substr(search, RLENGTH+1)
+        id = substr(search, RLENGTH + 1)
         search = "identifier:" id
     }
 
     # remove quotes from search string, since it gets quoted later
     gsub(/"/, "", search)
@@ -87,11 +87,11 @@
         split(order, parts, " ")
         url = url sprintf("&sort%%5B0%%5D=%s %s", order_names[parts[1]],
             parts[2])
     }
     for (i = 1; i <= fields; i++) {
-        url = url sprintf("&fl%%5B%d%%5D=%s", i-1, field[i])
+        url = url sprintf("&fl%%5B%d%%5D=%s", i - 1, field[i])
     }
     api_request(url, "GET", iaout)
 
     cmd = sprintf("%s <%s >%s 2>&1", cmd_json2tsv, iaout, jsout)
     system(cmd)

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -87,11 +87,11 @@
                 A[1] = t
                 return
             }
         }
         for (p = i; (c = 2*p) <= n; p = c) {
-            if ((c < n) && (A[c] < A[c+1]))
+            if ((c < n) && (A[c] < A[c + 1]))
                 c++
             if (t < A[c])
                 A[p] = A[c]
             else break
         }

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -148,11 +148,11 @@
 # to full URLs
 
 function print_ref_full(str, base, proto, root,     link, prefix, relative) {
     if (match(str, /^ [0-9]+\. /)) {
         prefix = substr(str, 0, RLENGTH)
-        link = substr(str, RLENGTH+1)
+        link = substr(str, RLENGTH + 1)
         # convert relative links to full URLs
         if (link !~ /^[a-z]+:/) {
             # convert relative link to full URL
             relative = link
             if (relative ~ /^\/\//) {
@@ -177,18 +177,18 @@
 function print_ref_pharos(output, str,     id, label, link, prefix,
     relative, token)
 {
     if (match(str, /^ [0-9]+\. /)) {
         prefix = substr(str, 0, RLENGTH)
-        link = substr(str, RLENGTH+1)
+        link = substr(str, RLENGTH + 1)
 
         id = ""
         if (match(link, /https?:\/\/(www\.)?archive\.org\/details\//)) {
             token = substr(link, RSTART+RLENGTH)
             id = substr(token, 1, length(token) - 7)
             if (match(id, /[?\/ ]/)) {
-                id = substr(id, 1, RSTART-1)
+                id = substr(id, 1, RSTART - 1)
             }
         }
         if (length(id) > 0) {
             label = prefix id
             printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,

]]></content>
<updated>2025-04-30 00:57:35</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/6da1905276/</id>
<title>Be careful to return the cache output...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-30 00:33:10

Be careful to return the cache output from function cache_begin()

  INSERTED    DELETED
         1          1 src/cache.awk
         1          1 TOTAL over 1 changed file

Index: src/cache.awk
==================================================================
--- src/cache.awk
+++ src/cache.awk
@@ -6,11 +6,11 @@
     } else {
         # awk_ext micro-optimization for gawk and mawk:
         # when cache is disabled, print directly to stdout
         cache_output = "/dev/stdout"
     }
-    return
+    return cache_output
 }
 
 function cache_end(    value) {
     if (cache_enabled || !awk_ext) {
         close(cache_output)

]]></content>
<updated>2025-04-30 00:33:10</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/86679b4841/</id>
<title>Clean up "return value" code</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-27 21:03:08

Clean up "return value" code

  INSERTED    DELETED
         1          1 src/cache.awk
         2          2 src/cgi.awk
         7          7 src/util.awk
        10         10 TOTAL over 3 changed files

Index: src/cache.awk
==================================================================
--- src/cache.awk
+++ src/cache.awk
@@ -6,11 +6,11 @@
     } else {
         # awk_ext micro-optimization for gawk and mawk:
         # when cache is disabled, print directly to stdout
         cache_output = "/dev/stdout"
     }
-    return retval
+    return
 }
 
 function cache_end(    value) {
     if (cache_enabled || !awk_ext) {
         close(cache_output)

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -60,11 +60,11 @@
     topdir = parts[2]
 
     return
 }
 
-function gph_encode(str) {
+function gph_encode(str,     retval) {
     retval = str
     gsub(/\|/, "\\|", retval)
     return retval
 }
 
@@ -90,14 +90,14 @@
     uri_encode_tab[127] = sprintf("%%%02X", 127)
 
     return
 }
 
-function uri_encode(str,     i, c, len) {
+function uri_encode(str,     i, c, len, retval) {
     len = length(str)
     retval = ""
     for (i = 1; i <= len; i++) {
         c = substr(str, i, 1)
         retval = retval uri_encode_tab[uri_encode_ord[c]]
     }
     return retval
 }

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -1,6 +1,6 @@
-function detect_archive(url,     str) {
+function detect_archive(url,     retval, str) {
     str = tolower(url)
     if (str ~ /\.iso$/ ||
         str ~ /\.zip$/)
     {
         retval = 1
@@ -8,11 +8,11 @@
         retval = 0
     }
     return retval
 }
 
-function detect_html(url,     str) {
+function detect_html(url,     retval, str) {
     str = tolower(url)
     if (str ~ /\.html?$/ ||
         str ~ /\.php$/)
     {
         retval = 1
@@ -20,11 +20,11 @@
         retval = 0
     }
     return retval
 }
 
-function detect_image(url,     str) {
+function detect_image(url,     retval, str) {
     str = tolower(url)
     if (str ~ /\.bmp$/  ||
         str ~ /\.gif$/  ||
         str ~ /\.jpg$/  ||
         str ~ /\.jpeg$/ ||
@@ -36,11 +36,11 @@
         retval = 0
     }
     return retval
 }
 
-function gettemp(     result) {
+function gettemp(     result, retval) {
     while ((cmd_mktemp | getline) > 0) {
         retval = $0
     }
     result = close(cmd_mktemp)
     if (result != 0) {
@@ -97,11 +97,11 @@
         }
         A[p] = t
     }
 }
 
-function human_size(bytes) {
+function human_size(bytes,     retval) {
     if (bytes > size_gb) {
         retval = sprintf("%.1fG", bytes / size_gb)
     } else if (bytes > size_mb) { 
         retval = sprintf("%.1fM", bytes / size_mb)
     } else if (bytes > size_kb) { 
@@ -135,20 +135,20 @@
     }
     close(name)
     return retval
 }
 
-function shorten_left(str, len) {
+function shorten_left(str, len,     retval) {
     if (length(str) > len) {
         retval = "..." substr(str, 3 + length(str) - len)
     } else {
         retval = str
     }
     return retval
 }
 
-function shorten(str, len) {
+function shorten(str, len,     retval) {
     if (length(str) > len) {
         retval = substr(str, 0, len - 3) "..."
     } else {
         retval = str
     }

]]></content>
<updated>2025-04-27 21:03:08</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/275c838adb/</id>
<title>Update link to curl web page.</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-27 21:00:34

Update link to curl web page.

  INSERTED    DELETED
         1          1 readme.txt
         1          1 TOTAL over 1 changed file

Index: readme.txt
==================================================================
--- readme.txt
+++ readme.txt
@@ -24,11 +24,11 @@
 * Runs under the Geomyidae gopher server.
   <gopher://bitreich.org/1/scm/geomyidae/log.gph>
 * Written in AWK.  Tested using busybox awk.
 * Commands used:
   * env, mktemp, rm, strings, xargs
-  * curl <https://curl.haxx.se/>
+  * curl <https://curl.se/>
   * json2tsv <gopher://codemadness.org/1/phlog/json2tsv/>
   * webdump <gopher://codemadness.org/1/phlog/webdump/>
 
 Configuration
 =============

]]></content>
<updated>2025-04-27 21:00:34</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/9f67b17ddc/</id>
<title>Stop caching search results in order ...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-17 14:31:41

  Stop caching search results in order to always show up-to-date
  results.

  INSERTED    DELETED
        17         29 src/search/index.dcgi.m4
        17         29 TOTAL over 1 changed file

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -5,18 +5,16 @@
 #
 # Show search results
 
 include(src/config.awk)
 incl(src/api.awk)
-incl(src/cache.awk)
 incl(src/cgi.awk)
-incl(src/sqlite.awk)
 incl(src/util.awk)
 
 function main(search,     cmd, count, creator, descr, field, fields, i,
-    iaout, id, jsout, label, numfound, order, order_names, output,
-    page, rows, searchstr, signature, sort_param, str, title, type, url)
+    iaout, id, jsout, label, numfound, order, order_names, page, rows,
+    searchstr, sort_param, title, type, url)
 {
     order_names["addeddate"] = "addeddate"
     order_names["collection_size"] = "collection_size"
     order_names["createddate"] = "createddate"
     order_names["creator"] = "creatorSorter"
@@ -65,18 +63,10 @@
             order = "nav_order desc"
             sort_param = "sort" order
         }
     }
 
-    signature = sprintf("search/%s/%s", input, searchstr)
-    str = cache_init(signature)
-    if (length(str) > 0) {
-        print str
-        return
-    }
-
-    output = cache_begin()
     iaout = gettemp()
     jsout = gettemp()
 
     # get search results
 
@@ -117,26 +107,25 @@
 
     if (search ~ /^@/) {
         numfound++
     }
     if (numfound == 0) {
-        print "Your search did not match any items in the Archive." >>output
-        print "Try different keywords or a more general search." >>output
-        print "" >>output
-        printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
-        cache_end()
+        print "Your search did not match any items in the Archive."
+        print "Try different keywords or a more general search."
+        print ""
+        printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
         unlink(jsout)
         unlink(iaout)
         return
     } else {
         pages = int(numfound / rows)
         if (numfound % rows != 0) {
             pages++
         }
         printf "# %s search results, page %d of %d\n", numfound,
-            page, pages >>output
-        print "" >>output
+            page, pages
+        print ""
     }
 
     # format search results as a gopher directory (menu)
     FS = "\t"
     creator = ""
@@ -146,11 +135,11 @@
     type = ""
     count = 0
 
     if (search ~ /^@/) {
         printf "[1|Account %s|%s/account/%s|%s|%s]\n", search, cgipath,
-           search, server, port >>output
+           search, server, port
     }
 
     while ((getline <jsout) > 0) {
         if ($1 == ".response.docs[].creator" && $2 == "s") {
             creator = $3
@@ -171,54 +160,53 @@
                 label = sprintf("[%s] %s", mediatype[type],
                     gph_encode(shorten(title, 58)))
             }
             if (type == "collection") {
                 printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
-                    label, cgipath, id, server, port >>output
+                    label, cgipath, id, server, port
             } else {
                 printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
-                   id, server, port >>output
+                   id, server, port
             }
             creator = ""
             descr = ""
             id = ""
             type = ""
         }
     }
     close(jsout)
 
-    print "" >>output
+    print ""
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
         printf "[1|[<<] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
             page - 1, cgipath, page - 1, rows, sort_param, search,
-            server, port >>output
+            server, port
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
         printf "[1|[>>] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
             page + 1, cgipath, page + 1, rows, sort_param, search,
-            server, port >>output
+            server, port
     }
 
     # only show "sort" if there's more than one item to sort
     if (count > 1) {
         printf "[1|[^v] Sort|%s/sort/%%09%s|%s|%s]\n", cgipath, search,
-            server, port >>output
+            server, port
     }
 
     # only show "search within list" if there's multiple pages of results
     if (numfound > rows) {
         printf "[1|[\\/] Filter results|%s/wizard/step1/%s|%s|%s]\n",
-            cgipath, search, server, port >>output
+            cgipath, search, server, port
     }
 
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
 
-    cache_end()
     unlink(jsout)
     unlink(iaout)
     exit 0
 }
 

]]></content>
<updated>2025-04-17 14:31:41</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/947e8d74dd/</id>
<title>Add search string to cache signature.</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-14 20:44:04

Add search string to cache signature.

  INSERTED    DELETED
         1          1 src/search/index.dcgi.m4
         1          1 TOTAL over 1 changed file

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -65,11 +65,11 @@
             order = "nav_order desc"
             sort_param = "sort" order
         }
     }
 
-    signature = sprintf("search/%s", input)
+    signature = sprintf("search/%s/%s", input, searchstr)
     str = cache_init(signature)
     if (length(str) > 0) {
         print str
         return
     }

]]></content>
<updated>2025-04-14 20:44:04</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/4d6f4c92b4/</id>
<title>Add feature to block by IP address. B...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-13 00:05:13

  Add feature to block by IP address. Block bad bots that are
  ignoring gopher://tilde.pink/0/robots.txt

  INSERTED    DELETED
         1          0 config.m4
         1          0 readme.txt
         4          2 src/cache.awk
        10          1 src/cgi.awk
         1          0 src/config.awk
        17          3 TOTAL over 5 changed files

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -4,10 +4,11 @@
 dnl
 define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
 define(__API_ENDPOINT__, http://archive.org)dnl
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
 define(__AWK_EXT__, 0)dnl
+define(__BLOCKLIST__, /home/user/pharos/block.txt)dnl
 define(__CACHE_DB__, /home/user/pharos/db/cache.dat)dnl
 define(__CACHE_ENABLED__, 0)dnl
 define(__CGIPATH__, /~user/pharos)dnl
 define(__CMD_AWK__, /usr/bin/awk)dnl
 define(__CMD_CURL__, /usr/bin/curl)dnl

Index: readme.txt
==================================================================
--- readme.txt
+++ readme.txt
@@ -33,10 +33,11 @@
 Configuration
 =============
 To set configuration variables, edit config.m4
 
 AWK_EXT allows use of non-standard features in gawk and mawk
+BLOCKLIST is a file to block by IP address, one regex per line
 CACHE_ENABLED caches content in sqlite to reduce API calls
 
 Installation
 ============
 Installation depends on m4.

Index: src/cache.awk
==================================================================
--- src/cache.awk
+++ src/cache.awk
@@ -27,10 +27,11 @@
         return
     }
     cache_signature = signature
     sql = "CREATE TABLE IF NOT EXISTS cache ( \
         id TEXT PRIMARY KEY,                  \
+        ip TEXT,                              \
         epoch INTEGER,                        \
         result TEXT)"
     sqlite_exec(cache_db, sql)
     retval = cache_get_value()
     return retval
@@ -48,12 +49,13 @@
 
 function cache_set_value(value,    sql) {
     if (!cache_enabled) {
         return
     }
-    sql = sprintf("REPLACE INTO cache(id, epoch, result) \
-        VALUES('%s', unixepoch(), '%s')",
+    sql = sprintf("REPLACE INTO cache(id, ip, epoch, result) \
+        VALUES('%s', '%s', unixepoch(), '%s')",
         sqlite_escape(cache_signature),
+        ENVIRON["REMOTE_ADDR"],
         sqlite_escape(value))
     sqlite_exec(cache_db, sql)
     return
 }

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -1,6 +1,15 @@
-function cgi_init() {
+function cgi_init(    ip) {
+    ip = ENVIRON["REMOTE_ADDR"]
+    while ((getline < blocklist) > 0) {
+        # bad bot ignoring robots.txt, block by IP address
+        if (match(ip, $0)) {
+            exit 0
+        }
+    }
+    close(blocklist)
+
     search = ARGV[1]
     arguments = ARGV[2]
     traversal = ARGV[5]
     selector = ARGV[6]
 

Index: src/config.awk
==================================================================
--- src/config.awk
+++ src/config.awk
@@ -1,10 +1,11 @@
 function config_init() {
     agent = "__AGENT__"
     api_endpoint = "__API_ENDPOINT__"
     api_ssl_endpoint = "__API_SSL_ENDPOINT__"
     awk_ext = __AWK_EXT__
+    blocklist = "__BLOCKLIST__"
     cache_db = "__CACHE_DB__"
     cache_enabled = __CACHE_ENABLED__
     cgipath = "__CGIPATH__"
     cmd_curl = "__CMD_CURL__"
     cmd_enc = "__CMD_ENV__"

]]></content>
<updated>2025-04-13 00:05:13</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/d3332581da/</id>
<title>Fix order of config.m4 to be in alpha...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-12 20:57:58

Fix order of config.m4 to be in alphabetical order

  INSERTED    DELETED
         1          1 config.m4
         1          1 TOTAL over 1 changed file

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -1,13 +1,13 @@
 dnl Set configuration variables
 dnl
 define(__PHAROS_VERSION__, 12)dnl
 dnl
+define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
 define(__API_ENDPOINT__, http://archive.org)dnl
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
 define(__AWK_EXT__, 0)dnl
-define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
 define(__CACHE_DB__, /home/user/pharos/db/cache.dat)dnl
 define(__CACHE_ENABLED__, 0)dnl
 define(__CGIPATH__, /~user/pharos)dnl
 define(__CMD_AWK__, /usr/bin/awk)dnl
 define(__CMD_CURL__, /usr/bin/curl)dnl

]]></content>
<updated>2025-04-12 20:57:58</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/20fcfb6f6d/</id>
<title>Declare private variables in api.awk ...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-12 20:53:31

  Declare private variables in api.awk Remove unnecessary line
  continuation characters.

  INSERTED    DELETED
         1          1 src/api.awk
         5          5 src/download/index.dcgi.m4
         2          2 src/list/index.dcgi.m4
         2          2 src/lists/index.dcgi.m4
         5          5 src/search/index.dcgi.m4
         5          5 src/web.awk
        20         20 TOTAL over 6 changed files

Index: src/api.awk
==================================================================
--- src/api.awk
+++ src/api.awk
@@ -1,6 +1,6 @@
-function api_request(url, method, output, data) {
+function api_request(url, method, output, data,    curlcfg, retval) {
     retval = ""
     curlcfg = gettemp()
     printf "--connect-timeout 10\n" > curlcfg
     printf "--max-redirs 0\n" >> curlcfg
     if (url ~ /^https:/) {

Index: src/download/index.dcgi.m4
==================================================================
--- src/download/index.dcgi.m4
+++ src/download/index.dcgi.m4
@@ -8,11 +8,11 @@
 include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
-function main(cmd, dir, files, file_size, format, iaout, is_archive, \
+function main(cmd, dir, files, file_size, format, iaout, is_archive,
     is_proxy, item_server, label, mtime, name, source, url)
 {
     dir = parts[2]
     item_id = parts[3]
     if (dir == "download") {
@@ -59,13 +59,13 @@
         }
     }
     close(cmd)
 
     for (i = 1; i <= files; i++) {
-        label = sprintf("%s %s %s", \
-            shorten_left(name[i], 40), \
-            strftime("%Y-%m-%d %H:%M", mtime[i]), \
+        label = sprintf("%s %s %s",
+            shorten_left(name[i], 40),
+            strftime("%Y-%m-%d %H:%M", mtime[i]),
             human_size(file_size[i]))
         url = sprintf("http://%s%s/%s", item_server, dir, name[i])
         if (is_proxy) {
             if (file_size[i] > max_bin_size * size_mb) {
                 printf "[h|%s|URL:%s|%s|%s]\n", label, uri_encode(url),
@@ -78,11 +78,11 @@
             printf "[h|%s|URL:%s|%s|%s]\n", label, uri_encode(url),
                 server, port
         }
         is_archive = detect_archive(url)
         if (is_archive) {
-            url = sprintf("http://%s/view_archive.php?archive=%s/%s", \
+            url = sprintf("http://%s/view_archive.php?archive=%s/%s",
                 item_server, dir, name[i])
             printf "[h|%s (View Contents)|URL:%s|%s|%s]\n",
                 shorten_left(name[i], 40), uri_encode(url),
                 server, port
         }

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -157,14 +157,14 @@
             title = $3
         } else if ($1 == ".response.body.hits.hits[]._score") {
             # the _score field happens to be toward the end of each item
             if (length(title) > 0) {
                 if (length(creator) > 0) {
-                    label = sprintf("[%s] %s by %s", mediatype[type], \
+                    label = sprintf("[%s] %s by %s", mediatype[type],
                         gph_encode(shorten(title, 40)), shorten(creator, 18))
                 } else {
-                    label = sprintf("[%s] %s", mediatype[type], \
+                    label = sprintf("[%s] %s", mediatype[type],
                         gph_encode(shorten(title, 58)))
                 }
                 printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath, id,
                     server, port >>output
                 count++

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -46,11 +46,11 @@
     while ((cmd | getline) > 0) {
         if ($1 == ".value[]" && $2 == "o") {
             # add information for previous list
             if (!is_private && length(name) > 0 && item_count > 0) {
                 label = shorten_left(name, 50)
-                item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", \
+                item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", 
                     item_count, label, cgipath, search, id, server, port)
                 record = label "\t" id "\t" item
                 count++
                 records[count] = record
             }
@@ -72,11 +72,11 @@
     close(cmd)
 
     # add information for previous list
     if (!is_private && length(name) > 0 && item_count > 0) {
         label = shorten_left(name, 50)
-        item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", \
+        item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]",
             item_count, label, cgipath, search, id, server, port)
         record = label "\t" id "\t" item
         count++
         records[count] = record
     }

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -89,15 +89,15 @@
 
     # remove anyfield, a hobgoblin of consistency
     searchstr = search
     gsub(/anyfield:/, "", searchstr)
 
-    url = sprintf("%s/advancedsearch.php?q=%s&output=json&rows=%d&page=%d", \
+    url = sprintf("%s/advancedsearch.php?q=%s&output=json&rows=%d&page=%d",
         api_endpoint, searchstr, rows, page)
     if (length(order) > 0) {
         split(order, parts, " ")
-        url = url sprintf("&sort%%5B0%%5D=%s %s", order_names[parts[1]], \
+        url = url sprintf("&sort%%5B0%%5D=%s %s", order_names[parts[1]],
             parts[2])
     }
     for (i = 1; i <= fields; i++) {
         url = url sprintf("&fl%%5B%d%%5D=%s", i-1, field[i])
     }
@@ -130,11 +130,11 @@
     } else {
         pages = int(numfound / rows)
         if (numfound % rows != 0) {
             pages++
         }
-        printf "# %s search results, page %d of %d\n", numfound, \
+        printf "# %s search results, page %d of %d\n", numfound,
             page, pages >>output
         print "" >>output
     }
 
     # format search results as a gopher directory (menu)
@@ -163,14 +163,14 @@
         } else if ($1 == ".response.docs[].title" && $2 == "s") {
             # the title field happens to be toward the end of each item
             title = $3
             count++
             if (length(creator) > 0) {
-                label = sprintf("[%s] %s by %s", mediatype[type], \
+                label = sprintf("[%s] %s by %s", mediatype[type],
                     gph_encode(shorten(title, 40)), shorten(creator, 18))
             } else {
-                label = sprintf("[%s] %s", mediatype[type], \
+                label = sprintf("[%s] %s", mediatype[type],
                     gph_encode(shorten(title, 58)))
             }
             if (type == "collection") {
                 printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
                     label, cgipath, id, server, port >>output

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -1,7 +1,7 @@
-function dump(search, type,     base, cmd, curlcfg, is_html, is_image, \
-    label, limit, link, marker, parts, prefix, proto, relative, root,  \
+function dump(search, type,     base, cmd, curlcfg, is_html, is_image,
+    label, limit, link, marker, parts, prefix, proto, relative, root,
     url)
 {
     url = search
     gsub(/%3F/, "?", url)
 
@@ -42,11 +42,11 @@
     }
 
     # Use strings command to guard webdump from binary input.
     # Use "strings -a" to avoid security pitfalls.
 
-    cmd = sprintf("%s -K %s 2>&1 | %s -a -n 3 | %s -ilr -w 60", \
+    cmd = sprintf("%s -K %s 2>&1 | %s -a -n 3 | %s -ilr -w 60",
         cmd_curl, curlcfg, cmd_strings, cmd_webdump)
 
     # Parse base out of original URL.
     # Use this to convert relative links to full URLs.
     # webdump has the -b option for this.
@@ -122,11 +122,11 @@
 function print_html(output, html,    cmd, marker, work) {
     work = gettemp()
     gsub(/\\n/, "<br>", html)
     print html >work
     close(work)
-    cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work, \
+    cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work,
         cmd_webdump)
     marker = 999999
     while ((cmd | getline) > 0) {
         gsub(/\t/, "        ")
         gsub(/\\t/, "        ")
@@ -172,11 +172,11 @@
 
 
 # Print the webdump references section, translating archive.org URLs to
 # pharos URLs
 
-function print_ref_pharos(output, str,     id, label, link, prefix, \
+function print_ref_pharos(output, str,     id, label, link, prefix,
     relative, token)
 {
     if (match(str, /^ [0-9]+\. /)) {
         prefix = substr(str, 0, RLENGTH)
         link = substr(str, RLENGTH+1)

]]></content>
<updated>2025-04-12 20:53:31</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/f60b57c050/</id>
<title>Add server-side cache using SQLite, d...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-04-12 20:46:41

  Add server-side cache using SQLite, disabled by default in
  config.m4. Cache accounts, item details, lists, and searches. 
  Add option for non-standard features from gawk and mawk,
  disabled by default in config.m4  Bump to version 12.

  INSERTED    DELETED
         5          1 config.m4
         3          0 readme.txt
        35         22 src/account/index.dcgi.m4
        59          0 src/cache.awk
         4          0 src/config.awk
        47         35 src/details/index.dcgi.m4
        24         14 src/list/index.dcgi.m4
        19          7 src/lists/index.dcgi.m4
        30         18 src/search/index.dcgi.m4
        22          0 src/sqlite.awk
        20         12 src/util.awk
         8          6 src/web.awk
       276        115 TOTAL over 12 changed files

Index: config.m4
==================================================================
--- config.m4
+++ config.m4
@@ -1,19 +1,23 @@
 dnl Set configuration variables
 dnl
-define(__PHAROS_VERSION__, 11)dnl
+define(__PHAROS_VERSION__, 12)dnl
 dnl
 define(__API_ENDPOINT__, http://archive.org)dnl
 define(__API_SSL_ENDPOINT__, https://archive.org)dnl
+define(__AWK_EXT__, 0)dnl
 define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
+define(__CACHE_DB__, /home/user/pharos/db/cache.dat)dnl
+define(__CACHE_ENABLED__, 0)dnl
 define(__CGIPATH__, /~user/pharos)dnl
 define(__CMD_AWK__, /usr/bin/awk)dnl
 define(__CMD_CURL__, /usr/bin/curl)dnl
 define(__CMD_ENV__, /usr/bin/env)dnl
 define(__CMD_JSON2TSV__, /usr/local/bin/json2tsv)dnl
 define(__CMD_MKTEMP__, /bin/mktemp)dnl
 define(__CMD_RM__, rm)dnl
+define(__CMD_SQLITE__, /usr/bin/sqlite3)dnl
 define(__CMD_STRINGS__, /bin/busybox strings)dnl
 define(__CMD_WEBDUMP__, /usr/local/bin/webdump)dnl
 define(__CMD_XARGS__, /usr/bin/xargs)dnl
 define(__GEOMYIDAE_VERSION__, 0.96)dnl
 define(__MAX_BIN_SIZE__, 10)dnl

Index: readme.txt
==================================================================
--- readme.txt
+++ readme.txt
@@ -31,10 +31,13 @@
   * webdump <gopher://codemadness.org/1/phlog/webdump/>
 
 Configuration
 =============
 To set configuration variables, edit config.m4
+
+AWK_EXT allows use of non-standard features in gawk and mawk
+CACHE_ENABLED caches content in sqlite to reduce API calls
 
 Installation
 ============
 Installation depends on m4.
 

Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ src/account/index.dcgi.m4
@@ -5,20 +5,31 @@
 #
 # Show details for an account
 
 include(src/config.awk)
 incl(src/api.awk)
+incl(src/cache.awk)
 incl(src/cgi.awk)
+incl(src/sqlite.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 
 function main(     acct, cmd, col, cols, descr, dir, email, iaout, id,
-    item_server, item_size, thumb, title, type, url)
+    item_server, item_size, output, signature, str, thumb, title,
+    type, url)
 {
     acct = parts[3]
     email = search
 
+    signature = sprintf("account/%s/%s", acct, email)
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
+
+    output = cache_begin()
     iaout = gettemp()
 
     url = api_endpoint "/metadata/" acct
     api_request(url, "GET", iaout)
     
@@ -63,44 +74,46 @@
         }
     }
     close(cmd)
 
     if (length(id) == 0) {
-        print_not_found(url)
+        print_not_found(output, url)
+        cache_end()
         unlink(iaout)
         return
     }
 
-    print "Account: " acct
+    print "Account: " acct >>output
     if (length(thumb) > 0) {
         url = sprintf("http://%s%s/%s", item_server, dir, thumb)
         printf "[I|Thumbnail|%s/raw/%%09%s|%s|%s]\n", cgipath, url,
-            server, port
+            server, port >>output
     }
-    print_html(descr)
+    print_html(output, descr)
 
     if (length(email) > 0) {
         printf "[1|Uploads|%s/search/%%09uploader:%s|%s|%s]\n",
-            cgipath, email, server, port
+            cgipath, email, server, port >>output
     }
     printf "[1|Items|%s/search/%%09anyfield:%s|%s|%s]\n", cgipath,
-        acct, server, port
-    printf "[1|Lists|%s/lists/%%09%s|%s|%s]\n", cgipath, acct,
-        server, port
-
-    print ""
-    printf "%-20s %s\n", "Identifier:", id
-    if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size
-    }
-    printf "%-20s %s\n", "Media Type:", type
-    
-    print ""
-    printf "[h|Account web page|URL:%s/details/%s|%s|%s]\n",
-        api_ssl_endpoint, uri_encode(id), server, port
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
-
+        acct, server, port >>output
+    printf "[1|Lists|%s/lists/%%09%s|%s|%s]\n", cgipath, acct,
+        server, port >>output
+
+    print "" >>output
+    printf "%-20s %s\n", "Identifier:", id >>output
+    if (item_size > 0) {
+        printf "%-20s %d\n", "Item Size:", item_size >>output
+    }
+    printf "%-20s %s\n", "Media Type:", type >>output
+    
+    print "" >>output
+    printf "[h|Account web page|URL:%s/details/%s|%s|%s]\n",
+        api_ssl_endpoint, uri_encode(id), server, port >>output
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
+
+    cache_end()
     unlink(iaout)
     exit 0
 }
 
 BEGIN {

ADDED   src/cache.awk
Index: src/cache.awk
==================================================================
--- /dev/null
+++ src/cache.awk
@@ -0,0 +1,59 @@
+# cache or memo-ize to reduce Internet Archive API calls
+
+function cache_begin() {
+    if (cache_enabled || !awk_ext) {
+        cache_output = gettemp()
+    } else {
+        # awk_ext micro-optimization for gawk and mawk:
+        # when cache is disabled, print directly to stdout
+        cache_output = "/dev/stdout"
+    }
+    return retval
+}
+
+function cache_end(    value) {
+    if (cache_enabled || !awk_ext) {
+        close(cache_output)
+        value = read_file(cache_output)
+        print value
+        cache_set_value(value)
+        unlink(cache_output)
+    }
+    return
+}
+
+function cache_init(signature,    retval, sql) {
+    if (!cache_enabled) {
+        return
+    }
+    cache_signature = signature
+    sql = "CREATE TABLE IF NOT EXISTS cache ( \
+        id TEXT PRIMARY KEY,                  \
+        epoch INTEGER,                        \
+        result TEXT)"
+    sqlite_exec(cache_db, sql)
+    retval = cache_get_value()
+    return retval
+}
+
+function cache_get_value(    retval, sql) {
+    if (!cache_enabled) {
+        return retval
+    }
+    sql = sprintf("SELECT result FROM cache WHERE id = '%s'",
+        sqlite_escape(cache_signature))
+    retval = sqlite_exec(cache_db, sql)
+    return retval
+}
+
+function cache_set_value(value,    sql) {
+    if (!cache_enabled) {
+        return
+    }
+    sql = sprintf("REPLACE INTO cache(id, epoch, result) \
+        VALUES('%s', unixepoch(), '%s')",
+        sqlite_escape(cache_signature),
+        sqlite_escape(value))
+    sqlite_exec(cache_db, sql)
+    return
+}

Index: src/config.awk
==================================================================
--- src/config.awk
+++ src/config.awk
@@ -1,15 +1,19 @@
 function config_init() {
     agent = "__AGENT__"
     api_endpoint = "__API_ENDPOINT__"
     api_ssl_endpoint = "__API_SSL_ENDPOINT__"
+    awk_ext = __AWK_EXT__
+    cache_db = "__CACHE_DB__"
+    cache_enabled = __CACHE_ENABLED__
     cgipath = "__CGIPATH__"
     cmd_curl = "__CMD_CURL__"
     cmd_enc = "__CMD_ENV__"
     cmd_json2tsv = "__CMD_JSON2TSV__"
     cmd_mktemp = "__CMD_MKTEMP__"
     cmd_rm = "__CMD_RM__"
+    cmd_sqlite = "__CMD_SQLITE__"
     cmd_strings = "__CMD_STRINGS__"
     cmd_webdump = "__CMD_WEBDUMP__"
     cmd_xargs = "__CMD_XARGS__"
     geomyidae_version = __GEOMYIDAE_VERSION__
     max_bin_size = __MAX_BIN_SIZE__

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -5,22 +5,32 @@
 #
 # Show details for an item
 
 include(src/config.awk)
 incl(src/api.awk)
+incl(src/cache.awk)
 incl(src/cgi.awk)
+incl(src/sqlite.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 
-function main(     add_date, col, cols, cmd, creator, descr, dir, i, \
-    iaout, id, item_id, item_server, item_size, label, language,     \
-    license, pub_date, scanner, thumb, title, topic, topics, type,   \
-    uploader_account, uploader_email, url)
+function main(     add_date, col, cols, cmd, creator, descr, dir, i,
+    iaout, id, item_id, item_server, item_size, label, language,
+    license, output, pub_date, scanner, signature, str, thumb, title,
+    topic, topics, type, uploader_account, uploader_email, url)
 {
     item_id = parts[3]
+
+    signature = "details/" item_id
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
 
     iaout = gettemp()
+    output = cache_begin()
 
     url = api_endpoint "/metadata/" item_id
     api_request(url, "GET", iaout)
     
     # format search results as a gopher directory (menu)
@@ -99,67 +109,68 @@
         }
     }
     close(cmd)
 
     if (length(id) == 0) {
-        print_not_found(url)
+        print_not_found(output, url)
+        cache_end()
         unlink(iaout)
         return
     }
 
-    print(shorten(title, 70))
+    print shorten(title, 70) >>output
     if (creators == 1) {
         label = "by " shorten(creator[1], 70)
         printf "[1|%s|%s/search/%%09creator:(%s)|%s|%s]\n", label,
-            cgipath, creator[1], server, port
+            cgipath, creator[1], server, port >>output
     } else if (creators > 1) {
-        printf "\nby:\n"
+        printf "\nby:\n" >>output
         for (i = 1; i <= creators; i++) {
             label = shorten(creator[i], 70)
             printf "[1|%s|%s/search/%%09creator:(%s)|%s|%s]\n", label,
-                cgipath, creator[i], server, port
+                cgipath, creator[i], server, port >>output
         }
-        printf "\n"
+        printf "\n" >>output
     }
     if (length(thumb) > 0) {
         url = sprintf("http://%s%s/%s", item_server, dir, thumb)
         printf "[I|Thumbnail|%s/raw/%%09%s|%s|%s]\n",
-            cgipath, url, server, port
+            cgipath, url, server, port >>output
     }
 
     printf "[1|Download|%s/download/%s|%s|%s]\n", cgipath,
-        item_id, server, port
-    print ""
+        item_id, server, port >>output
+    print "" >>output
 
-    print_html(descr)
+    print_html(output, descr)
 
-    print ""
+    print "" >>output
     if (length(add_date) > 0) {
-        printf "%-20s %s\n", "Date Added:", add_date
+        printf "%-20s %s\n", "Date Added:", add_date >>output
     }
     if (pub_date != add_date) {
-        printf "%-20s %s\n", "Date Published:", pub_date
+        printf "%-20s %s\n", "Date Published:", pub_date >>output
     }
-    printf "%-20s %s\n", "Identifier:", id
+    printf "%-20s %s\n", "Identifier:", id >>output
     if (item_size > 0) {
-        printf "%-20s %d\n", "Item Size:", item_size
+        printf "%-20s %d\n", "Item Size:", item_size >>output
     }
     if (length(language) > 0) {
-        printf "%-20s %s\n", "Language:", language
+        printf "%-20s %s\n", "Language:", language >>output
     }
     if (length(license) > 0) {
-        printf "%-20s %s\n", "License:", license
+        printf "%-20s %s\n", "License:", license >>output
     }
-    printf "%-20s %s\n", "Media Type:", type
+    printf "%-20s %s\n", "Media Type:", type >>output
     
     if (topics > 0) {
-        print ""
-        print "# Topics"
+        print "" >>output
+        print "# Topics" >>output
         for (i = 1; i <= topics; i++) {
             label = shorten(topic[i], 40)
             printf "[1|%s|%s/search/%%09subject:(%s)|%s|%s]\n", label,
-                cgipath, topic[i], server, port
+                cgipath, topic[i], server, port >>output
         }
     }
 
     # scrape uploader name from item web page HTML
     url = api_ssl_endpoint "/details/" item_id
@@ -172,36 +183,37 @@
         }
     }
     close(iaout)
 
     if (cols > 0) {
-        print ""
-        print "# Collections"
+        print "" >>output
+        print "# Collections" >>output
         for (i = 1; i <= cols; i++) {
             label = shorten(col[i], 40)
             printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
-                label, cgipath, col[i], server, port
+                label, cgipath, col[i], server, port >>output
         }
     }
 
-    print ""
-    print "# Uploaded by"
+    print "" >>output
+    print "# Uploaded by" >>output
     if (length(uploader_account) > 0) {
         label = shorten(uploader_account, 70)
         printf "[1|%s|%s/account/%s%%09%s|%s|%s]\n", label, cgipath,
-            uploader_account, uploader_email, server, port
+            uploader_account, uploader_email, server, port >>output
     } else {
         label = shorten(uploader_email, 70)
         printf "[1|%s|%s/search/%%09uploader:%s|%s|%s]\n", label,
-            cgipath, uploader_email, server, port
+            cgipath, uploader_email, server, port >>output
     }
 
-    print ""
+    print "" >>output
     printf "[h|Item web page|URL:%s/details/%s|%s|%s]\n",
-        api_ssl_endpoint, uri_encode(id), server, port
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+        api_ssl_endpoint, uri_encode(id), server, port >>output
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
 
+    cache_end()
     unlink(iaout)
     return
 }
 
 BEGIN {

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -5,17 +5,19 @@
 #
 # Show a specific list
 
 include(src/config.awk)
 incl(src/api.awk)
+incl(src/cache.awk)
 incl(src/cgi.awk)
+incl(src/sqlite.awk)
 incl(src/util.awk)
 
 function main(     acct, client_url, cmd, count, creator, iaout, id,
     is_private, items, label, list_id, name, name_slug, numfound,
-    order, order_name, order_names, order_param, page, pages, rows,
-    query, sort_param, title, type, url)
+    order, order_name, order_names, order_param, output, page, pages,
+    rows, query, sort_param, signature, str, title, type, url)
 {
     order_names["creator"] = "creatorSorter"
     order_names["date"] = "date"
     order_names["title"] = "titleSorter"
     order_names["week"] = "week"
@@ -41,13 +43,18 @@
 
     split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
 
-    print acct "'s Lists"
-    print ""
+    signature = sprintf("list/%s", input)
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
 
+    output = cache_begin()
     iaout = gettemp()
 
     url = api_ssl_endpoint "/services/users/" acct "/lists/" list_id
     api_request(url, "GET", iaout)
 
@@ -117,12 +124,14 @@
         pages++
     }
 
     # format as a gopher directory (menu)
 
-    printf "# List: %s, page %d of %d\n", name, page, pages
-    print ""
+    print acct "'s Lists" >>output
+    print "" >>output
+    printf "# List: %s, page %d of %d\n", name, page, pages >>output
+    print "" >>output
 
     cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
     FS = "\t"
     count = 0
     creator = ""
@@ -155,11 +164,11 @@
                 } else {
                     label = sprintf("[%s] %s", mediatype[type], \
                         gph_encode(shorten(title, 58)))
                 }
                 printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath, id,
-                    server, port
+                    server, port >>output
                 count++
             }
             creator = ""
             descr = ""
             id = ""
@@ -166,38 +175,39 @@
             type = ""
         }
     }
     close(cmd)
 
-    print ""
+    print "" >>output
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
         printf "[1|[<<] Page %d|%s/list/page%d/rows%d/%s%%09%s/%d|%s|%s]\n",
             page - 1, cgipath, page - 1, rows, sort_param,
-            acct, list_id, server, port
+            acct, list_id, server, port >>output
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
         printf "[1|[>>] Page %d|%s/list/page%d/rows%d/%s%%09%s/%d|%s|%s]\n",
             page + 1, cgipath, page + 1, rows, sort_param,
-            acct, list_id, server, port
+            acct, list_id, server, port >>output
     }
 
     # only show "sort" if there's more than one item to sort
     if (numfound > 1) {
         printf "[1|[^v] Sort|%s/listsort/%%09%s/%d|%s|%s]\n", cgipath,
-            acct, list_id, server, port
+            acct, list_id, server, port >>output
     }
 
     printf "[1|Account %s|%s/account/%s|%s|%s]\n", acct, cgipath,
-        acct, server, port
+        acct, server, port >>output
  
-    print ""
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+    print "" >>output
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
 
+    cache_end()
     unlink(iaout)
     exit 0
 }
 
 BEGIN {

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -5,16 +5,27 @@
 #
 # Show a list of a user's lists
 
 include(src/config.awk)
 incl(src/api.awk)
+incl(src/cache.awk)
 incl(src/cgi.awk)
+incl(src/sqlite.awk)
 incl(src/util.awk)
 
-function main(     cmd, count, fields, iaout, i, id, is_private, item, \
-    item_count, item_id, label, name, record, records, url)
+function main(     cmd, count, fields, iaout, i, id, is_private, item,
+    item_count, item_id, label, name, output, record, records,
+    signature, str, url)
 {
+    signature = sprintf("%s/lists", search)
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
+
+    output = cache_begin()
     iaout = gettemp()
 
     url = api_ssl_endpoint "/services/users/" search "/lists"
     api_request(url, "GET", iaout)
 
@@ -71,23 +82,24 @@
     }
 
     # sort lists by label and id
     hsort(records, count)
 
-    print search "'s Lists"
-    print ""
+    print search "'s Lists" >>output
+    print "" >>output
 
     for (i = 1; i <= count; i++) {
         record = records[i]
         split(record, fields, /\t/)
         item = fields[3]
-        print item
+        print item >>output
     }
 
-    print ""
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+    print "" >>output
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
 
+    cache_end()
     unlink(iaout)
     return
 }
 
 BEGIN {

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -5,16 +5,18 @@
 #
 # Show search results
 
 include(src/config.awk)
 incl(src/api.awk)
+incl(src/cache.awk)
 incl(src/cgi.awk)
+incl(src/sqlite.awk)
 incl(src/util.awk)
 
-function main(search,     cmd, count, creator, descr, field, fields, i, \
-    iaout, id, jsout, label, numfound, order, order_names, page, rows,  \
-    searchstr, sort_param, title, type, url)
+function main(search,     cmd, count, creator, descr, field, fields, i,
+    iaout, id, jsout, label, numfound, order, order_names, output,
+    page, rows, searchstr, signature, sort_param, str, title, type, url)
 {
     order_names["addeddate"] = "addeddate"
     order_names["collection_size"] = "collection_size"
     order_names["createddate"] = "createddate"
     order_names["creator"] = "creatorSorter"
@@ -63,10 +65,18 @@
             order = "nav_order desc"
             sort_param = "sort" order
         }
     }
 
+    signature = sprintf("search/%s", input)
+    str = cache_init(signature)
+    if (length(str) > 0) {
+        print str
+        return
+    }
+
+    output = cache_begin()
     iaout = gettemp()
     jsout = gettemp()
 
     # get search results
 
@@ -107,25 +117,26 @@
 
     if (search ~ /^@/) {
         numfound++
     }
     if (numfound == 0) {
-        print "Your search did not match any items in the Archive."
-        print "Try different keywords or a more general search."
-        print ""
-        printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+        print "Your search did not match any items in the Archive." >>output
+        print "Try different keywords or a more general search." >>output
+        print "" >>output
+        printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
+        cache_end()
         unlink(jsout)
         unlink(iaout)
         return
     } else {
         pages = int(numfound / rows)
         if (numfound % rows != 0) {
             pages++
         }
         printf "# %s search results, page %d of %d\n", numfound, \
-            page, pages
-        print ""
+            page, pages >>output
+        print "" >>output
     }
 
     # format search results as a gopher directory (menu)
     FS = "\t"
     creator = ""
@@ -135,11 +146,11 @@
     type = ""
     count = 0
 
     if (search ~ /^@/) {
         printf "[1|Account %s|%s/account/%s|%s|%s]\n", search, cgipath,
-           search, server, port
+           search, server, port >>output
     }
 
     while ((getline <jsout) > 0) {
         if ($1 == ".response.docs[].creator" && $2 == "s") {
             creator = $3
@@ -160,53 +171,54 @@
                 label = sprintf("[%s] %s", mediatype[type], \
                     gph_encode(shorten(title, 58)))
             }
             if (type == "collection") {
                 printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
-                    label, cgipath, id, server, port
+                    label, cgipath, id, server, port >>output
             } else {
                 printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
-                   id, server, port
+                   id, server, port >>output
             }
             creator = ""
             descr = ""
             id = ""
             type = ""
         }
     }
     close(jsout)
 
-    print ""
+    print "" >>output
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
         printf "[1|[<<] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
             page - 1, cgipath, page - 1, rows, sort_param, search,
-            server, port
+            server, port >>output
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
         printf "[1|[>>] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
             page + 1, cgipath, page + 1, rows, sort_param, search,
-            server, port
+            server, port >>output
     }
 
     # only show "sort" if there's more than one item to sort
     if (count > 1) {
         printf "[1|[^v] Sort|%s/sort/%%09%s|%s|%s]\n", cgipath, search,
-            server, port
+            server, port >>output
     }
 
     # only show "search within list" if there's multiple pages of results
     if (numfound > rows) {
         printf "[1|[\\/] Filter results|%s/wizard/step1/%s|%s|%s]\n",
-            cgipath, search, server, port
+            cgipath, search, server, port >>output
     }
 
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
 
+    cache_end()
     unlink(jsout)
     unlink(iaout)
     exit 0
 }
 

ADDED   src/sqlite.awk
Index: src/sqlite.awk
==================================================================
--- /dev/null
+++ src/sqlite.awk
@@ -0,0 +1,22 @@
+function sqlite_exec(db, query,    retval, sqlcfg) {
+    retval = ""
+    sqlcfg = gettemp()
+    
+    printf ".timeout 1000\n" >sqlcfg
+    printf "PRAGMA encoding=\"UTF-8\";\n" >>sqlcfg
+    printf "%s;\n", query >>sqlcfg
+    close(sqlcfg)
+    cmd = sprintf("%s -batch -line -safe -init \"%s\" \"%s\" .quit 2>&1",
+        cmd_sqlite, sqlcfg, db)
+    while ((cmd | getline) > 0) {
+        retval = retval $0 "\n"
+    }
+    close(cmd)
+    unlink(sqlcfg)
+    return retval
+}
+
+function sqlite_escape(str) {
+    gsub(/'/, "''", str)
+    return str
+}

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -110,26 +110,34 @@
         retval = sprintf("%dB", bytes)
     }
     return retval
 }
 
-function print_not_found(url) {
-    print "Item cannot be found"
-    print ""
-    print "Items may be taken down for various reasons,"
-    print "including by decision of the uploader or"
-    print "due to a violation of the Terms of Use."
-    print ""
-    printf "[h|Metadata|URL:%s|%s|%s]\n", url, server, port
-    print ""
+function print_not_found(output, url) {
+    print "Item cannot be found" >>output
+    print "" >>output
+    print "Items may be taken down for various reasons," >>output
+    print "including by decision of the uploader or" >>output
+    print "due to a violation of the Terms of Use." >>output
+    print "" >>output
+    printf "[h|Metadata|URL:%s|%s|%s]\n", url, server, port >>output
+    print "" >>output
     url = api_ssl_endpoint "/about/terms.php"
     printf "[0|Terms of Use|%s/text/%%09%s|%s|%s]\n", cgipath,
-        url, server, port
-    print ""
-    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+        url, server, port >>output
+    print "" >>output
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port >>output
     return
 }
+
+function read_file(name,    retval) {
+    while ((getline <name) > 0) {
+        retval = retval $0 "\n"
+    }
+    close(name)
+    return retval
+}
 
 function shorten_left(str, len) {
     if (length(str) > len) {
         retval = "..." substr(str, 3 + length(str) - len)
     } else {

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -117,11 +117,11 @@
     unlink(curlcfg)
 
     return
 }
 
-function print_html(html,    cmd, marker, work) {
+function print_html(output, html,    cmd, marker, work) {
     work = gettemp()
     gsub(/\\n/, "<br>", html)
     print html >work
     close(work)
     cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work, \
@@ -132,13 +132,13 @@
         gsub(/\\t/, "        ")
         if (NR < marker) {
             if ($0 ~ /^References$/) {
                 marker = NR
             }
-            print
+            print >>output
         } else {
-            print_ref_pharos($0)
+            print_ref_pharos(output, $0)
         }
     }
     close(cmd)
     unlink(work)
     return
@@ -172,11 +172,13 @@
 
 
 # Print the webdump references section, translating archive.org URLs to
 # pharos URLs
 
-function print_ref_pharos(str,     id, label, link, prefix, relative, token) {
+function print_ref_pharos(output, str,     id, label, link, prefix, \
+    relative, token)
+{
     if (match(str, /^ [0-9]+\. /)) {
         prefix = substr(str, 0, RLENGTH)
         link = substr(str, RLENGTH+1)
 
         id = ""
@@ -190,14 +192,14 @@
         if (length(id) > 0) {
             label = prefix id
             printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
                 id, server, port
         } else {
-             print str
+             print str >>output
         }
     } else {
-        print str
+        print str >>output
     }
     return
 }
 
 function web_init() {

]]></content>
<updated>2025-04-12 20:46:41</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/e553b1bd81/</id>
<title>When formatting HTML content, replace...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-01-19 20:43:07

  When formatting HTML content, replace tab escapes with eight
  spaces.  Prior to this commit, only tab characters were being
  replaced with eight spaces.

  INSERTED    DELETED
         1          0 src/web.awk
         1          0 TOTAL over 1 changed file

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -127,10 +127,11 @@
     cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work, \
         cmd_webdump)
     marker = 999999
     while ((cmd | getline) > 0) {
         gsub(/\t/, "        ")
+        gsub(/\\t/, "        ")
         if (NR < marker) {
             if ($0 ~ /^References$/) {
                 marker = NR
             }
             print

]]></content>
<updated>2025-01-19 20:43:07</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/21f26cd9d1/</id>
<title>If an item has multiple creators, lis...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-01-19 20:38:11

  If an item has multiple creators, list them in the item details.
   Prior to this commit, the details would ignore the creator
  field when an item had multiple creators.

  INSERTED    DELETED
        18          5 src/details/index.dcgi.m4
        18          5 TOTAL over 1 changed file

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -27,11 +27,12 @@
     cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
     FS = "\t"
     cols = 0
     delete col
     add_date = ""
-    creator = ""
+    creators = 0
+    delete creator
     descr = ""
     dir = ""
     id = ""
     item_server = ""
     item_size = 0
@@ -62,11 +63,15 @@
             col[cols] = $3
         } else if ($1 == ".metadata.collection[]" && $2 == "s") {
             cols++
             col[cols] = $3
         } else if ($1 == ".metadata.creator" && $2 == "s") {
-            creator = $3
+            creators++
+            creator[creators] = $3
+        } else if ($1 == ".metadata.creator[]" && $2 == "s") {
+            creators++
+            creator[creators] = $3
         } else if ($1 == ".metadata.description" && $2 == "s") {
             descr = $3
         } else if ($1 == ".metadata.identifier" && $2 == "s") {
             id = $3
         } else if ($1 == ".metadata.language" && $2 == "s") {
@@ -100,14 +105,22 @@
         unlink(iaout)
         return
     }
 
     print(shorten(title, 70))
-    if (length(creator) > 0) {
-        label = "by " shorten(creator, 70)
+    if (creators == 1) {
+        label = "by " shorten(creator[1], 70)
         printf "[1|%s|%s/search/%%09creator:(%s)|%s|%s]\n", label,
-            cgipath, creator, server, port
+            cgipath, creator[1], server, port
+    } else if (creators > 1) {
+        printf "\nby:\n"
+        for (i = 1; i <= creators; i++) {
+            label = shorten(creator[i], 70)
+            printf "[1|%s|%s/search/%%09creator:(%s)|%s|%s]\n", label,
+                cgipath, creator[i], server, port
+        }
+        printf "\n"
     }
     if (length(thumb) > 0) {
         url = sprintf("http://%s%s/%s", item_server, dir, thumb)
         printf "[I|Thumbnail|%s/raw/%%09%s|%s|%s]\n",
             cgipath, url, server, port

]]></content>
<updated>2025-01-19 20:38:11</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/93dfabc4c2/</id>
<title>Fix the This Just In links to sort by...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2025-01-02 18:20:13

  Fix the This Just In links to sort by added date instead of
  public date.

  INSERTED    DELETED
         1          1 src/audio/index.gph.m4
         1          1 src/books/index.gph.m4
         1          1 src/images/index.gph.m4
         1          1 src/software/index.gph.m4
         1          1 src/video/index.gph.m4
         5          5 TOTAL over 5 changed files

Index: src/audio/index.gph.m4
==================================================================
--- src/audio/index.gph.m4
+++ src/audio/index.gph.m4
@@ -1,10 +1,10 @@
 include(config.m4)dnl
 # Audio
 
 [1|All Audio|__CGIPATH__/search/sortweek desc/%09mediatype:audio|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:audio|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:audio|__SERVER__|__PORT__]
 [1|Live Music Archive|__CGIPATH__/search/sortweek desc/%09collection:etree AND mediatype:collection|__SERVER__|__PORT__]
 [1|Librivox Free Audiobooks|__CGIPATH__/search/sortweek desc/%09collection:librivoxaudio|__SERVER__|__PORT__]
 [1|Grateful Dead|__CGIPATH__/search/sortweek desc/%09collection:GratefulDead|__SERVER__|__PORT__]
 [1|Netlabels|__CGIPATH__/search/sortweek desc/%09collection:netlabels AND mediatype:collection|__SERVER__|__PORT__]
 [1|Old Time Radio|__CGIPATH__/search/sortweek desc/%09collection:oldtimeradio|__SERVER__|__PORT__]

Index: src/books/index.gph.m4
==================================================================
--- src/books/index.gph.m4
+++ src/books/index.gph.m4
@@ -1,11 +1,11 @@
 include(config.m4)dnl
 # Books
 
 [1|All Books|__CGIPATH__/search/sortweek desc/%09collection:books AND -access-restricted-item:true|__SERVER__|__PORT__]
 [1|All Texts|__CGIPATH__/search/sortweek desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
 [1|Smithsonian Libraries|__CGIPATH__/search/sortweek desc/%09collection:smithsonian|__SERVER__|__PORT__]
 [1|FEDLINK|__CGIPATH__/search/sortweek desc/%09collection:fedlink AND mediatype:collection|__SERVER__|__PORT__]
 [1|Genealogy|__CGIPATH__/search/sortweek desc/%09collection:genealogy AND mediatype:collection|__SERVER__|__PORT__]
 [1|Lincoln Collection|__CGIPATH__/search/sortweek desc/%09collection:lincolncollection|__SERVER__|__PORT__]
 [1|American Libraries|__CGIPATH__/search/sortweek desc/%09collection:americana AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]

Index: src/images/index.gph.m4
==================================================================
--- src/images/index.gph.m4
+++ src/images/index.gph.m4
@@ -1,10 +1,10 @@
 include(config.m4)dnl
 # Images
 
 [1|All Images|__CGIPATH__/search/sortweek desc/%09mediatype:image|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:image|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:image|__SERVER__|__PORT__]
 [1|Metropolitan Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:metropolitanmuseumofart-gallery|__SERVER__|__PORT__]
 [1|Cleveland Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:clevelandart|__SERVER__|__PORT__]
 [1|Flickr Commons|__CGIPATH__/search/sortweek desc/%09collection:flickrcommons|__SERVER__|__PORT__]
 [1|Occupy Wallstreet Flickr|__CGIPATH__/search/sortweek desc/%09collection:flickr-ows|__SERVER__|__PORT__]
 [1|Cover Art|__CGIPATH__/search/sortweek desc/%09collection:coverartarchive|__SERVER__|__PORT__]

Index: src/software/index.gph.m4
==================================================================
--- src/software/index.gph.m4
+++ src/software/index.gph.m4
@@ -1,9 +1,9 @@
 include(config.m4)dnl
 
 [1|All Software|__CGIPATH__/search/sortweek desc/%09mediatype:software|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:software|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:software|__SERVER__|__PORT__]
 [1|Internet Arcade|__CGIPATH__/search/sortweek desc/%09collection:internetarcade|__SERVER__|__PORT__]
 [1|Console Living Room|__CGIPATH__/search/sortweek desc/%09collection:consolelivingroom|__SERVER__|__PORT__]
 [1|Old School Emulation|__CGIPATH__/search/sortweek desc/%09collection:tosec|__SERVER__|__PORT__]
 [1|MS-DOS Games|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_games|__SERVER__|__PORT__]
 [1|Historical Software|__CGIPATH__/search/sortweek desc/%09collection:historicalsoftware|__SERVER__|__PORT__]

Index: src/video/index.gph.m4
==================================================================
--- src/video/index.gph.m4
+++ src/video/index.gph.m4
@@ -1,10 +1,10 @@
 include(config.m4)dnl
 # Video
 
 [1|All Video|__CGIPATH__/search/sortweek desc/%09mediatype:movies|__SERVER__|__PORT__]
-[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:movies|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortaddeddate desc/%09mediatype:movies|__SERVER__|__PORT__]
 [1|Prelinger Archives|__CGIPATH__/search/sortweek desc/%09collection:prelinger|__SERVER__|__PORT__]
 [1|Democracy Now!|__CGIPATH__/search/sortweek desc/%09collection:democracy_now_vid|__SERVER__|__PORT__]
 [1|Occupy Wall Street|__CGIPATH__/search/sortweek desc/%09collection:occupywallstreet|__SERVER__|__PORT__]
 [1|TV NSA Clip Library|__CGIPATH__/search/sortweek desc/%09collection:nsa|__SERVER__|__PORT__]
 [1|Animation And Cartoons|__CGIPATH__/search/sortweek desc/%09collection:animationandcartoons|__SERVER__|__PORT__]

]]></content>
<updated>2025-01-02 18:20:13</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/80e7307e75/</id>
<title>Preserve search result sort order thr...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-10-06 22:35:40

  Preserve search result sort order through page navigation.  To
  keep it simple, use one single sort order in the search results
  instead of nested sort orders.  For consistency with web site,
  sort by "creatorSorter", "date", and "titleSorter" instead of
  "creator", "publicdate", and "title".

  INSERTED    DELETED
        33         17 src/search/index.dcgi.m4
         2          2 src/sort/index.dcgi.m4
        35         19 TOTAL over 2 changed files

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -9,28 +9,39 @@
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(search,     cmd, count, creator, descr, field, fields, i, \
-    iaout, id, jsout, label, numfound, order, orders, page, rows,       \
-    searchstr, title, type, url)
+    iaout, id, jsout, label, numfound, order, order_names, page, rows,  \
+    searchstr, sort_param, title, type, url)
 {
+    order_names["addeddate"] = "addeddate"
+    order_names["collection_size"] = "collection_size"
+    order_names["createddate"] = "createddate"
+    order_names["creator"] = "creatorSorter"
+    order_names["date"] = "date"
+    order_names["downloads"] = "downloads"
+    order_names["nav_order"] = "nav_order"
+    order_names["random"] = "random"
+    order_names["title"] = "titleSorter"
+    order_names["week"] = "week"
+
     rows = 15
     page = 1
-    delete order
-    orders = 0
+    order = ""
 
     # parse out page number and sort orders
     for (i in parts) {
         if (parts[i] ~ /^rows[0-9][0-9]*$/) {
             rows = substr(parts[i], 5)
         } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
             page = substr(parts[i], 5)
         } else if (parts[i] ~ /^sort/) {
-            orders++
-            str = substr(parts[i], 5)
-            order[orders] = str
+            if (length(order) == 0) {
+                sort_param = parts[i]
+                order = substr(parts[i], 5)
+            }
         }
     }
 
     # special case for when the search term is an archive.org details URL
     if (match(search, /^https:\/\/(www\.)?archive\.org\/details\//)) {
@@ -40,18 +51,19 @@
 
     # remove quotes from search string, since it gets quoted later
     gsub(/"/, "", search)
 
     # default sort orders if none were specified
-    if (orders == 0) {
-        orders = 1
+    if (length(order) == 0) {
         if (search == "mediatype:collection" ||
             search == "mediatype:(collection)")
         {
-            order[1] = "collection_size desc"
+            order = "collection_size desc"
+            sort_param = "sort" order
         } else {
-            order[1] = "nav_order desc"
+            order = "nav_order desc"
+            sort_param = "sort" order
         }
     }
 
     iaout = gettemp()
     jsout = gettemp()
@@ -69,12 +81,14 @@
     searchstr = search
     gsub(/anyfield:/, "", searchstr)
 
     url = sprintf("%s/advancedsearch.php?q=%s&output=json&rows=%d&page=%d", \
         api_endpoint, searchstr, rows, page)
-    for (i = 1; i <= orders; i++) {
-        url = url sprintf("&sort%%5B%d%%5D=%s", i-1, order[i])
+    if (length(order) > 0) {
+        split(order, parts, " ")
+        url = url sprintf("&sort%%5B0%%5D=%s %s", order_names[parts[1]], \
+            parts[2])
     }
     for (i = 1; i <= fields; i++) {
         url = url sprintf("&fl%%5B%d%%5D=%s", i-1, field[i])
     }
     api_request(url, "GET", iaout)
@@ -163,18 +177,20 @@
 
     print ""
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
-        printf "[1|[<<] Page %d|%s/search/page%d/rows%d/%%09%s|%s|%s]\n",
-            page - 1, cgipath, page - 1, rows, search, server, port
+        printf "[1|[<<] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
+            page - 1, cgipath, page - 1, rows, sort_param, search,
+            server, port
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
-        printf "[1|[>>] Page %d|%s/search/page%d/rows%d/%%09%s|%s|%s]\n",
-            page + 1, cgipath, page + 1, rows, search, server, port
+        printf "[1|[>>] Page %d|%s/search/page%d/rows%d/%s%%09%s|%s|%s]\n",
+            page + 1, cgipath, page + 1, rows, sort_param, search,
+            server, port
     }
 
     # only show "sort" if there's more than one item to sort
     if (count > 1) {
         printf "[1|[^v] Sort|%s/sort/%%09%s|%s|%s]\n", cgipath, search,

Index: src/sort/index.dcgi.m4
==================================================================
--- src/sort/index.dcgi.m4
+++ src/sort/index.dcgi.m4
@@ -32,13 +32,13 @@
     lbl[11] = "Date created [^]"
     opt[11] = "createddate asc"
     lbl[12] = "Date created [v]"
     opt[12] = "createddate desc"
     lbl[13] = "Date published [^]"
-    opt[13] = "publicdate asc"
+    opt[13] = "date asc"
     lbl[14] = "Date published [v]"
-    opt[14] = "publicdate desc"
+    opt[14] = "date desc"
     lbl[15] = "Creator [^]"
     opt[15] = "creator asc"
     lbl[16] = "Creator [v]"
     opt[16] = "creator desc"
     lbl[17] = "Random"

]]></content>
<updated>2024-10-06 22:35:40</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/8c9e89b529/</id>
<title>Preserve user list sort order through...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-10-06 22:07:27

  Preserve user list sort order through page navigation. Use
  "normal" sort order parameters, which makes selectors easier to
  read.

  INSERTED    DELETED
        13         11 src/list/index.dcgi.m4
         8          8 src/listsort/index.dcgi.m4
        21         19 TOTAL over 2 changed files

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -11,29 +11,31 @@
 incl(src/util.awk)
 
 function main(     acct, client_url, cmd, count, creator, iaout, id,
     is_private, items, label, list_id, name, name_slug, numfound,
     order, order_name, order_names, order_param, page, pages, rows,
-    query, title, type, url)
+    query, sort_param, title, type, url)
 {
-    order_names["creatorSorter"] = creator
+    order_names["creator"] = "creatorSorter"
     order_names["date"] = "date"
-    order_names["titleSorter"] = "title"
+    order_names["title"] = "titleSorter"
     order_names["week"] = "week"
 
     rows = 15
     page = 1
     order = ""
+    sort_param = ""
 
     # parse out page number and sort order
     for (i in parts) {
         if (parts[i] ~ /^rows[0-9][0-9]*$/) {
             rows = substr(parts[i], 5)
         } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
             page = substr(parts[i], 5)
         } else if (parts[i] ~ /^sort/) {
             if (length(order) == 0) {
+                sort_param = parts[i]
                 order = substr(parts[i], 5)
             }
         }
     }
 
@@ -88,19 +90,19 @@
     gsub(/%20/, "-", name_slug)
     client_url = api_ssl_endpoint "/details/" acct "/lists/" list_id \
         "/" name_slug
     order_param = ""
     if (length(order) > 0) {
-        split(order, parts, ":")
+        split(order, parts, " ")
         order_name = order_names[parts[1]]
         if (length(order_name) > 0) {
             if (parts[2] == "desc") {
-                client_url = client_url "?-" order_name
+                client_url = client_url "?-" parts[1]
             } else {
-                client_url = client_url "?" order_name
+                client_url = client_url "?" parts[1]
             }
-            order_param = "&sort=" uri_encode(order)
+            order_param = "&sort=" uri_encode(order_name ":" parts[2])
         }
     }
     url = api_ssl_endpoint "/services/search/beta/page_production/" \
         "?user_query=identifier:(" query ")"                        \
         "&hits_per_page=" rows                                      \
@@ -168,19 +170,19 @@
 
     print ""
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
-        printf "[1|[<<] Page %d|%s/list/page%d/rows%d/%%09%s/%d|%s|%s]\n",
-            page - 1, cgipath, page - 1, rows,
+        printf "[1|[<<] Page %d|%s/list/page%d/rows%d/%s%%09%s/%d|%s|%s]\n",
+            page - 1, cgipath, page - 1, rows, sort_param,
             acct, list_id, server, port
     }
 
     # only show "next page" if the current page is completely full
     if (count == rows) {
-        printf "[1|[>>] Page %d|%s/list/page%d/rows%d/%%09%s/%d|%s|%s]\n",
-            page + 1, cgipath, page + 1, rows,
+        printf "[1|[>>] Page %d|%s/list/page%d/rows%d/%s%%09%s/%d|%s|%s]\n",
+            page + 1, cgipath, page + 1, rows, sort_param,
             acct, list_id, server, port
     }
 
     # only show "sort" if there's more than one item to sort
     if (numfound > 1) {

Index: src/listsort/index.dcgi.m4
==================================================================
--- src/listsort/index.dcgi.m4
+++ src/listsort/index.dcgi.m4
@@ -10,25 +10,25 @@
 
 function main(     acct, i, lbl, list_id, opt) {
     lbl[1] = "Relevance"
     opt[1] = ""
     lbl[2] = "Weekly views [^]"
-    opt[2] = "week:asc"
+    opt[2] = "week asc"
     lbl[3] = "Weekly views [v]"
-    opt[3] = "week:desc"
+    opt[3] = "week desc"
     lbl[4] = "Title [^]"
-    opt[4] = "titleSorter:asc"
+    opt[4] = "title asc"
     lbl[5] = "Title [v]"
-    opt[5] = "titleSorter:desc"
+    opt[5] = "title desc"
     lbl[6] = "Date published [^]"
-    opt[6] = "date:asc"
+    opt[6] = "date asc"
     lbl[7] = "Date published [v]"
-    opt[7] = "date:desc"
+    opt[7] = "date desc"
     lbl[8] = "Creator [^]"
-    opt[8] = "creatorSorter:asc"
+    opt[8] = "creator asc"
     lbl[9] = "Creator [v]"
-    opt[9] = "creatorSorter:desc"
+    opt[9] = "creator desc"
 
     split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
 

]]></content>
<updated>2024-10-06 22:07:27</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/9d3e9ac34f/</id>
<title>Normalize indentation in make.sh</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-10-06 21:49:51

Normalize indentation in make.sh

  INSERTED    DELETED
        17         17 make.sh
        17         17 TOTAL over 1 changed file

Index: make.sh
==================================================================
--- make.sh
+++ make.sh
@@ -10,27 +10,27 @@
     m4 $in >$out && chmod a+rx $out
 }
 
 # PLAIN
 for f in index.gph              \
-	account/index.dcgi      \
-	audio/index.gph         \
-	books/index.gph         \
-	images/index.gph        \
-	details/index.dcgi      \
-	download/index.dcgi     \
-	list/index.dcgi         \
-	lists/index.dcgi        \
-	listsort/index.dcgi     \
-        raw/index.cgi           \
-	search/index.dcgi       \
-	software/index.gph      \
-	sort/index.dcgi         \
-	video/index.gph         \
-        wizard/step1/index.dcgi \
-        wizard/step2/index.dcgi \
-        wizard/step3/index.dcgi
+    account/index.dcgi      \
+    audio/index.gph         \
+    books/index.gph         \
+    images/index.gph        \
+    details/index.dcgi      \
+    download/index.dcgi     \
+    list/index.dcgi         \
+    lists/index.dcgi        \
+    listsort/index.dcgi     \
+    raw/index.cgi           \
+    search/index.dcgi       \
+    software/index.gph      \
+    sort/index.dcgi         \
+    video/index.gph         \
+    wizard/step1/index.dcgi \
+    wizard/step2/index.dcgi \
+    wizard/step3/index.dcgi
 do
     build $SRC ${SRC}/${f}.m4 ${DESTDIR}/$f
 done
 
 # WEIRD

]]></content>
<updated>2024-10-06 21:49:51</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/c51e0eeec2/</id>
<title>Add option to change user list sort order</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-10-06 21:49:20

Add option to change user list sort order

  INSERTED    DELETED
         1          0 make.sh
 pharos/listsort/index.dcgi
        34          3 src/list/index.dcgi.m4
        54          0 src/listsort/index.dcgi.m4
        89          3 TOTAL over 3 changed files

Index: make.sh
==================================================================
--- make.sh
+++ make.sh
@@ -18,10 +18,11 @@
 	images/index.gph        \
 	details/index.dcgi      \
 	download/index.dcgi     \
 	list/index.dcgi         \
 	lists/index.dcgi        \
+	listsort/index.dcgi     \
         raw/index.cgi           \
 	search/index.dcgi       \
 	software/index.gph      \
 	sort/index.dcgi         \
 	video/index.gph         \

ADDED   pharos/listsort/index.dcgi
Index: pharos/listsort/index.dcgi
==================================================================
--- /dev/null
+++ pharos/listsort/index.dcgi

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -10,21 +10,32 @@
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(     acct, client_url, cmd, count, creator, iaout, id,
     is_private, items, label, list_id, name, name_slug, numfound,
-    page, pages, rows, query, title, type, url)
+    order, order_name, order_names, order_param, page, pages, rows,
+    query, title, type, url)
 {
+    order_names["creatorSorter"] = creator
+    order_names["date"] = "date"
+    order_names["titleSorter"] = "title"
+    order_names["week"] = "week"
+
     rows = 15
     page = 1
+    order = ""
 
-    # parse out page number
+    # parse out page number and sort order
     for (i in parts) {
         if (parts[i] ~ /^rows[0-9][0-9]*$/) {
             rows = substr(parts[i], 5)
         } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
             page = substr(parts[i], 5)
+        } else if (parts[i] ~ /^sort/) {
+            if (length(order) == 0) {
+                order = substr(parts[i], 5)
+            }
         }
     }
 
     split(search, parts, "/")
     acct = parts[1]
@@ -75,14 +86,28 @@
 
     name_slug = uri_encode(name)
     gsub(/%20/, "-", name_slug)
     client_url = api_ssl_endpoint "/details/" acct "/lists/" list_id \
         "/" name_slug
+    order_param = ""
+    if (length(order) > 0) {
+        split(order, parts, ":")
+        order_name = order_names[parts[1]]
+        if (length(order_name) > 0) {
+            if (parts[2] == "desc") {
+                client_url = client_url "?-" order_name
+            } else {
+                client_url = client_url "?" order_name
+            }
+            order_param = "&sort=" uri_encode(order)
+        }
+    }
     url = api_ssl_endpoint "/services/search/beta/page_production/" \
         "?user_query=identifier:(" query ")"                        \
         "&hits_per_page=" rows                                      \
         "&page=" page                                               \
+        order_param                                                 \
         "&aggregations=false"                                       \
         "&client_url=" client_url
     api_request(url, "GET", iaout)
 
     pages = int(numfound / rows)
@@ -117,11 +142,11 @@
             type = $3
         } else if ($1 == ".response.body.hits.hits[].fields.title" &&
             $2 == "s")
         {
             title = $3
-        } else if ($1 == ".response.body.hits.hits[]._score" && $2 == "a") {
+        } else if ($1 == ".response.body.hits.hits[]._score") {
             # the _score field happens to be toward the end of each item
             if (length(title) > 0) {
                 if (length(creator) > 0) {
                     label = sprintf("[%s] %s by %s", mediatype[type], \
                         gph_encode(shorten(title, 40)), shorten(creator, 18))
@@ -154,10 +179,16 @@
     if (count == rows) {
         printf "[1|[>>] Page %d|%s/list/page%d/rows%d/%%09%s/%d|%s|%s]\n",
             page + 1, cgipath, page + 1, rows,
             acct, list_id, server, port
     }
+
+    # only show "sort" if there's more than one item to sort
+    if (numfound > 1) {
+        printf "[1|[^v] Sort|%s/listsort/%%09%s/%d|%s|%s]\n", cgipath,
+            acct, list_id, server, port
+    }
 
     printf "[1|Account %s|%s/account/%s|%s|%s]\n", acct, cgipath,
         acct, server, port
  
     print ""

ADDED   src/listsort/index.dcgi.m4
Index: src/listsort/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/listsort/index.dcgi.m4
@@ -0,0 +1,54 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# listsort/index.dcgi
+#
+# Change list sort order
+
+include(src/config.awk)
+incl(src/cgi.awk)
+
+function main(     acct, i, lbl, list_id, opt) {
+    lbl[1] = "Relevance"
+    opt[1] = ""
+    lbl[2] = "Weekly views [^]"
+    opt[2] = "week:asc"
+    lbl[3] = "Weekly views [v]"
+    opt[3] = "week:desc"
+    lbl[4] = "Title [^]"
+    opt[4] = "titleSorter:asc"
+    lbl[5] = "Title [v]"
+    opt[5] = "titleSorter:desc"
+    lbl[6] = "Date published [^]"
+    opt[6] = "date:asc"
+    lbl[7] = "Date published [v]"
+    opt[7] = "date:desc"
+    lbl[8] = "Creator [^]"
+    opt[8] = "creatorSorter:asc"
+    lbl[9] = "Creator [v]"
+    opt[9] = "creatorSorter:desc"
+
+    split(search, parts, "/")
+    acct = parts[1]
+    list_id = parts[2]
+
+    print "# Sort by"
+    print ""
+    for (i = 1; i < 10; i++) {
+        if (length(opt[i]) == 0) {
+            printf "[1|%s|%s/list/%%09%s/%d|%s|%s]\n",
+                lbl[i], cgipath, acct, list_id, server, port
+        } else {
+            printf "[1|%s|%s/list/sort%s%%09%s/%d|%s|%s]\n",
+                lbl[i], cgipath, opt[i], acct, list_id, server, port
+        }
+    }
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

]]></content>
<updated>2024-10-06 21:49:20</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/6a710e594c/</id>
<title>Put newline before next page navigati...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-10-05 23:06:44

  Put newline before next page navigation to separate it from the
  list.

  INSERTED    DELETED
         2          1 src/list/index.dcgi.m4
         2          1 TOTAL over 1 changed file

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -138,10 +138,12 @@
             id = ""
             type = ""
         }
     }
     close(cmd)
+
+    print ""
 
     # only show "page back" if the user is past page 1
     if (page > 1) {
         printf "[1|[<<] Page %d|%s/list/page%d/rows%d/%%09%s/%d|%s|%s]\n",
             page - 1, cgipath, page - 1, rows,
@@ -153,11 +155,10 @@
         printf "[1|[>>] Page %d|%s/list/page%d/rows%d/%%09%s/%d|%s|%s]\n",
             page + 1, cgipath, page + 1, rows,
             acct, list_id, server, port
     }
 
-    print ""
     printf "[1|Account %s|%s/account/%s|%s|%s]\n", acct, cgipath,
         acct, server, port
  
     print ""
     printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port

]]></content>
<updated>2024-10-05 23:06:44</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/816c6df39c/</id>
<title>Improve the display of user lists. Sh...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-10-05 22:44:03

  Improve the display of user lists. Show item creator, title, etc
  rather than the bare item id. Paginate list items. Add "back
  link" to account that owns the list.

  INSERTED    DELETED
       119         13 src/list/index.dcgi.m4
         1         10 src/search/index.dcgi.m4
        10          0 src/util.awk
       130         23 TOTAL over 3 changed files

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -8,14 +8,27 @@
 include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
-function main(     acct, cmd, count, iaout, id, is_private, item_id,
-    label, list_id, name, parts, url)
+function main(     acct, client_url, cmd, count, creator, iaout, id,
+    is_private, items, label, list_id, name, name_slug, numfound,
+    page, pages, rows, query, title, type, url)
 {
-    count = split(search, parts, "/")
+    rows = 15
+    page = 1
+
+    # parse out page number
+    for (i in parts) {
+        if (parts[i] ~ /^rows[0-9][0-9]*$/) {
+            rows = substr(parts[i], 5)
+        } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
+            page = substr(parts[i], 5)
+        }
+    }
+
+    split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
 
     print acct "'s Lists"
     print ""
@@ -23,39 +36,131 @@
     iaout = gettemp()
 
     url = api_ssl_endpoint "/services/users/" acct "/lists/" list_id
     api_request(url, "GET", iaout)
 
-    # format list as a gopher directory (menu)
+    # fetch identifiers of list members
+
     cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
     FS = "\t"
-    name = ""
-    id = 0
+    id = ""
     is_private = 0
-    item_id = ""
+    name = ""
+    numfound = 0
+    query = ""
 
     while ((cmd | getline) > 0) {
         if ($1 == ".value.list_name" && $2 == "s") {
             name = $3
-            print "# List: " name
-            id = 0
             is_private = 0
         } else if ($1 == ".value.is_private" && $2 == "b") {
             if ($3 == "true") {
                 is_private = 1
             }
         } else if ($1 == ".value.members[].identifier" && $2 == "s") {
             if (!is_private) {
-                item_id = $3
-                label = shorten_left(item_id, 60)
-                printf "[1|%s|%s/details/%s|%s|%s]\n", label,
-                    cgipath, item_id, server, port
+                id = $3
+                numfound++
+                if (length(query) == 0) {
+                    query = id
+                } else {
+                    query = query "+OR+" id
+                }
+            }
+        }
+    }
+    close(cmd)
+    unlink(iaout)
+
+    # get metadata of list member items
+
+    name_slug = uri_encode(name)
+    gsub(/%20/, "-", name_slug)
+    client_url = api_ssl_endpoint "/details/" acct "/lists/" list_id \
+        "/" name_slug
+    url = api_ssl_endpoint "/services/search/beta/page_production/" \
+        "?user_query=identifier:(" query ")"                        \
+        "&hits_per_page=" rows                                      \
+        "&page=" page                                               \
+        "&aggregations=false"                                       \
+        "&client_url=" client_url
+    api_request(url, "GET", iaout)
+
+    pages = int(numfound / rows)
+    if (numfound % rows != 0) {
+        pages++
+    }
+
+    # format as a gopher directory (menu)
+
+    printf "# List: %s, page %d of %d\n", name, page, pages
+    print ""
+
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    count = 0
+    creator = ""
+    id = ""
+    title = ""
+    type = ""
+    while ((cmd | getline) > 0) {
+        if ($1 == ".response.body.hits.hits[].fields.creator[]" &&
+            $2 == "s" && length(creator) == 0)
+        {
+            creator = $3
+        } else if ($1 == ".response.body.hits.hits[].fields.identifier" &&
+            $2 == "s")
+        {
+            id = $3
+        } else if ($1 == ".response.body.hits.hits[].fields.mediatype" &&
+            $2 == "s")
+        {
+            type = $3
+        } else if ($1 == ".response.body.hits.hits[].fields.title" &&
+            $2 == "s")
+        {
+            title = $3
+        } else if ($1 == ".response.body.hits.hits[]._score" && $2 == "a") {
+            # the _score field happens to be toward the end of each item
+            if (length(title) > 0) {
+                if (length(creator) > 0) {
+                    label = sprintf("[%s] %s by %s", mediatype[type], \
+                        gph_encode(shorten(title, 40)), shorten(creator, 18))
+                } else {
+                    label = sprintf("[%s] %s", mediatype[type], \
+                        gph_encode(shorten(title, 58)))
+                }
+                printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath, id,
+                    server, port
+                count++
             }
+            creator = ""
+            descr = ""
+            id = ""
+            type = ""
         }
     }
     close(cmd)
 
+    # only show "page back" if the user is past page 1
+    if (page > 1) {
+        printf "[1|[<<] Page %d|%s/list/page%d/rows%d/%%09%s/%d|%s|%s]\n",
+            page - 1, cgipath, page - 1, rows,
+            acct, list_id, server, port
+    }
+
+    # only show "next page" if the current page is completely full
+    if (count == rows) {
+        printf "[1|[>>] Page %d|%s/list/page%d/rows%d/%%09%s/%d|%s|%s]\n",
+            page + 1, cgipath, page + 1, rows,
+            acct, list_id, server, port
+    }
+
+    print ""
+    printf "[1|Account %s|%s/account/%s|%s|%s]\n", acct, cgipath,
+        acct, server, port
+ 
     print ""
     printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
 
     unlink(iaout)
     exit 0
@@ -64,7 +169,8 @@
 BEGIN {
     config_init()
 
     cgi_init()
     uri_encode_init()
+    util_init()
     main()
 }

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -12,20 +12,10 @@
 
 function main(search,     cmd, count, creator, descr, field, fields, i, \
     iaout, id, jsout, label, numfound, order, orders, page, rows,       \
     searchstr, title, type, url)
 {
-    mediatype["audio"]      = "aud"
-    mediatype["collection"] = "col"
-    mediatype["data"]       = "dat"
-    mediatype["etree"]      = "aud"
-    mediatype["image"]      = "img"
-    mediatype["movies"]     = "mov"
-    mediatype["software"]   = "bin"
-    mediatype["texts"]      = "txt"
-    mediatype["web"]        = "web"
-
     rows = 15
     page = 1
     delete order
     orders = 0
 
@@ -144,10 +134,11 @@
         } else if ($1 == ".response.docs[].identifier" && $2 == "s") {
             id = $3
         } else if ($1 == ".response.docs[].mediatype" && $2 == "s") {
             type = $3
         } else if ($1 == ".response.docs[].title" && $2 == "s") {
+            # the title field happens to be toward the end of each item
             title = $3
             count++
             if (length(creator) > 0) {
                 label = sprintf("[%s] %s by %s", mediatype[type], \
                     gph_encode(shorten(title, 40)), shorten(creator, 18))

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -166,10 +166,20 @@
         "Attribution-NonCommercial 4.0 International"
     licenseurl["https://creativecommons.org/licenses/by-nc-sa/4.0/"] = \
         "Attribution-NonCommercial-ShareAlike 4.0 International"
     licenseurl["https://creativecommons.org/licenses/by-nc-nd/4.0/"] = \
         "Attribution-NonCommercial-NoDerivs 4.0 International"
+
+    mediatype["audio"]      = "aud"
+    mediatype["collection"] = "col"
+    mediatype["data"]       = "dat"
+    mediatype["etree"]      = "aud"
+    mediatype["image"]      = "img"
+    mediatype["movies"]     = "mov"
+    mediatype["software"]   = "bin"
+    mediatype["texts"]      = "txt"
+    mediatype["web"]        = "web"
 
     size_kb = 1024
     size_mb = 1024 * 1024
     size_gb = 1024 * 1024 * 1024
     return

]]></content>
<updated>2024-10-05 22:44:03</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/fd80671eb8/</id>
<title>Fix indentation error</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-10-05 22:04:00

Fix indentation error

  INSERTED    DELETED
         1          1 src/search/index.dcgi.m4
         1          1 TOTAL over 1 changed file

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -35,11 +35,11 @@
             rows = substr(parts[i], 5)
         } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
             page = substr(parts[i], 5)
         } else if (parts[i] ~ /^sort/) {
             orders++
-        str = substr(parts[i], 5)
+            str = substr(parts[i], 5)
             order[orders] = str
         }
     }
 
     # special case for when the search term is an archive.org details URL

]]></content>
<updated>2024-10-05 22:04:00</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/ebcb3f11b3/</id>
<title>When displaying a user's list of list...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-09-29 18:39:15

When displaying a user's list of lists, sort by name and id.

  INSERTED    DELETED
        33         12 src/lists/index.dcgi.m4
        45          0 src/util.awk
        78         12 TOTAL over 2 changed files

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -8,37 +8,42 @@
 include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
-function main(     cmd, iaout, id, is_private, item_count, item_id, \
-    label, name, url)
+function main(     cmd, count, fields, iaout, i, id, is_private, item, \
+    item_count, item_id, label, name, record, records, url)
 {
-    print search "'s Lists"
-    print ""
-
     iaout = gettemp()
 
     url = api_ssl_endpoint "/services/users/" search "/lists"
     api_request(url, "GET", iaout)
 
     # format list as a gopher directory (menu)
     cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
     FS = "\t"
-    name = ""
+    count = 0
+    delete fields[0]
     id = 0
     is_private = 0
+    item = ""
     item_count = 0
     item_id = ""
+    name = ""
+    record = ""
+    delete records[0]
 
     while ((cmd | getline) > 0) {
         if ($1 == ".value[]" && $2 == "o") {
-            # print information for previous list
+            # add information for previous list
             if (!is_private && length(name) > 0 && item_count > 0) {
                 label = shorten_left(name, 50)
-                printf "[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]\n",
-                    item_count, label, cgipath, search, id, server, port
+                item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", \
+                    item_count, label, cgipath, search, id, server, port)
+                record = label "\t" id "\t" item
+                count++
+                records[count] = record
             }
         } else if ($1 == ".value[].list_name" && $2 == "s") {
             name = $3
             id = 0
             is_private = 0
@@ -53,15 +58,31 @@
             item_count++
         }
     }
     close(cmd)
 
-    # print information for previous list
+    # add information for previous list
     if (!is_private && length(name) > 0 && item_count > 0) {
         label = shorten_left(name, 50)
-        printf "[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]\n",
-            item_count, label, cgipath, search, id, server, port
+        item = sprintf("[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]", \
+            item_count, label, cgipath, search, id, server, port)
+        record = label "\t" id "\t" item
+        count++
+        records[count] = record
+    }
+
+    # sort lists by label and id
+    hsort(records, count)
+
+    print search "'s Lists"
+    print ""
+
+    for (i = 1; i <= count; i++) {
+        record = records[i]
+        split(record, fields, /\t/)
+        item = fields[3]
+        print item
     }
 
     print ""
     printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
 

Index: src/util.awk
==================================================================
--- src/util.awk
+++ src/util.awk
@@ -51,10 +51,55 @@
         print "Error: mktemp failed, no tmpfile"
         exit
     }
     return retval
 }
+
+# heapsort
+#
+# Unstable, and unlike merge and quicksort it relies on random-access
+# so has poorer cache performance.
+#
+# Advantage over quicksort is that its worst-case same as avg: O(n log n)
+#
+# This presentation based on http://dada.perl.it/shootout/heapsort.lua5.html
+#
+# From: <https://raw.githubusercontent.com/
+# dubiousjim/awkenough/refs/heads/master/library.awk>
+#
+# Requires 1-based numerically indexed arrays.
+
+function hsort(A, n,   c, p, t, i) {
+    if (!n) {
+        n = 1
+        while (n in A) n++
+        n--
+    }
+    i = int(n/2) + 1
+    while (1) {
+        if (i > 1) {
+            i--
+            t = A[i]
+        } else {
+            t = A[n]
+            A[n] = A[1]
+            n--
+            if (n == 1) {
+                A[1] = t
+                return
+            }
+        }
+        for (p = i; (c = 2*p) <= n; p = c) {
+            if ((c < n) && (A[c] < A[c+1]))
+                c++
+            if (t < A[c])
+                A[p] = A[c]
+            else break
+        }
+        A[p] = t
+    }
+}
 
 function human_size(bytes) {
     if (bytes > size_gb) {
         retval = sprintf("%.1fG", bytes / size_gb)
     } else if (bytes > size_mb) { 

]]></content>
<updated>2024-09-29 18:39:15</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/635e5e3f04/</id>
<title>Fix broken gopher items caused by wro...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-19 02:33:45

  Fix broken gopher items caused by wrong format code.  The port
  should be formatted with %s not %d because it isn't in Geomyidae
  gophermaps the port isn't necessarily numeric.

  INSERTED    DELETED
         4          4 src/details/index.dcgi.m4
         4          4 TOTAL over 1 changed file

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -102,20 +102,20 @@
     }
 
     print(shorten(title, 70))
     if (length(creator) > 0) {
         label = "by " shorten(creator, 70)
-        printf "[1|%s|%s/search/%%09creator:(%s)|%s|%d]\n", label,
+        printf "[1|%s|%s/search/%%09creator:(%s)|%s|%s]\n", label,
             cgipath, creator, server, port
     }
     if (length(thumb) > 0) {
         url = sprintf("http://%s%s/%s", item_server, dir, thumb)
-        printf "[I|Thumbnail|%s/raw/%%09%s|%s|%d]\n",
+        printf "[I|Thumbnail|%s/raw/%%09%s|%s|%s]\n",
             cgipath, url, server, port
     }
 
-    printf "[1|Download|%s/download/%s|%s|%d]\n", cgipath,
+    printf "[1|Download|%s/download/%s|%s|%s]\n", cgipath,
         item_id, server, port
     print ""
 
     print_html(descr)
 
@@ -183,11 +183,11 @@
     }
 
     print ""
     printf "[h|Item web page|URL:%s/details/%s|%s|%s]\n",
         api_ssl_endpoint, uri_encode(id), server, port
-    printf "[1|PHAROS|%s|%s|%d]\n", cgipath, server, port
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
 
     unlink(iaout)
     return
 }
 

]]></content>
<updated>2024-08-19 02:33:45</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/4f746b35ce/</id>
<title>Add Zines category to Books menu</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-18 21:32:30

Add Zines category to Books menu

  INSERTED    DELETED
         1          0 src/books/index.gph.m4
         1          0 TOTAL over 1 changed file

Index: src/books/index.gph.m4
==================================================================
--- src/books/index.gph.m4
+++ src/books/index.gph.m4
@@ -18,5 +18,6 @@
 [1|Additional Collections|__CGIPATH__/search/sortweek desc/%09collection:additional_collections AND mediatype:collection|__SERVER__|__PORT__]
 [1|The Magazine Rack|__CGIPATH__/search/sortweek desc/%09collection:magazine_rack AND mediatype:collection|__SERVER__|__PORT__]
 [1|The Pulp Magazine Archive|__CGIPATH__/search/sortweek desc/%09collection:pulpmagazinearchive|__SERVER__|__PORT__]
 [1|Newspapers|__CGIPATH__/search/sortweek desc/%09collection:newspapers AND mediatype:collection|__SERVER__|__PORT__]
 [1|Comic Books & Graphic Novels|__CGIPATH__/search/sortweek desc/%09collection:comics AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Zines|__CGIPATH__/search/sortweek desc/%09collection:zines AND -access-restricted-item:true|__SERVER__|__PORT__]

]]></content>
<updated>2024-08-18 21:32:30</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/28d511a80c/</id>
<title>Move src/config.m4 to config.m4  This...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-13 15:21:05

  Move src/config.m4 to config.m4  This emphasizes that the
  configuration file is separate from the source code, and makes
  it easier to find and edit.  This also simplifies make.sh just a
  little bit.

  INSERTED    DELETED
        29          0 config.m4
         2          2 make.sh
         1          1 readme.txt
         1          1 src/account/index.dcgi.m4
         0         29 src/config.m4
         1          1 src/details/index.dcgi.m4
         1          1 src/download/index.dcgi.m4
         1          1 src/list/index.dcgi.m4
         1          1 src/lists/index.dcgi.m4
         1          1 src/raw/index.cgi.m4
         1          1 src/search/index.dcgi.m4
         1          1 src/sort/index.dcgi.m4
         1          1 src/wizard/step1/index.dcgi.m4
         1          1 src/wizard/step2/index.dcgi.m4
         1          1 src/wizard/step3/index.dcgi.m4
        43         43 TOTAL over 15 changed files

ADDED   config.m4
Index: config.m4
==================================================================
--- /dev/null
+++ config.m4
@@ -0,0 +1,29 @@
+dnl Set configuration variables
+dnl
+define(__PHAROS_VERSION__, 11)dnl
+dnl
+define(__API_ENDPOINT__, http://archive.org)dnl
+define(__API_SSL_ENDPOINT__, https://archive.org)dnl
+define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
+define(__CGIPATH__, /~user/pharos)dnl
+define(__CMD_AWK__, /usr/bin/awk)dnl
+define(__CMD_CURL__, /usr/bin/curl)dnl
+define(__CMD_ENV__, /usr/bin/env)dnl
+define(__CMD_JSON2TSV__, /usr/local/bin/json2tsv)dnl
+define(__CMD_MKTEMP__, /bin/mktemp)dnl
+define(__CMD_RM__, rm)dnl
+define(__CMD_STRINGS__, /bin/busybox strings)dnl
+define(__CMD_WEBDUMP__, /usr/local/bin/webdump)dnl
+define(__CMD_XARGS__, /usr/bin/xargs)dnl
+define(__GEOMYIDAE_VERSION__, 0.96)dnl
+define(__MAX_BIN_SIZE__, 10)dnl
+define(__MAX_TXT_SIZE__, 1)dnl
+define(__SERVER__, server)dnl
+define(__PORT__, port)dnl
+dnl
+dnl Set up M4 to work with AWK code
+dnl
+define(m4_substr, defn(substr))dnl
+undefine(substr)dnl
+changecom(`/*', `*/')dnl
+define(incl,`ifdef(paste,paste($1),undivert($1))')dnl

Index: make.sh
==================================================================
--- make.sh
+++ make.sh
@@ -4,12 +4,12 @@
 
 build() {
     dir=$1
     in=$2
     out=$3
-    echo m4 -I $dir $in \>$out && chmod a+rx $out
-    m4 -I $dir $in >$out && chmod a+rx $out
+    echo "m4 $in >$out && chmod a+rx $out"
+    m4 $in >$out && chmod a+rx $out
 }
 
 # PLAIN
 for f in index.gph              \
 	account/index.dcgi      \

Index: readme.txt
==================================================================
--- readme.txt
+++ readme.txt
@@ -30,11 +30,11 @@
   * json2tsv <gopher://codemadness.org/1/phlog/json2tsv/>
   * webdump <gopher://codemadness.org/1/phlog/webdump/>
 
 Configuration
 =============
-To set configuration variables, edit src/config.m4
+To set configuration variables, edit config.m4
 
 Installation
 ============
 Installation depends on m4.
 

Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ src/account/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # account/index.dcgi
 #
 # Show details for an account
 
-include(config.awk)
+include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 

DELETED src/config.m4
Index: src/config.m4
==================================================================
--- src/config.m4
+++ /dev/null
@@ -1,29 +0,0 @@
-dnl Set configuration variables
-dnl
-define(__PHAROS_VERSION__, 11)dnl
-dnl
-define(__API_ENDPOINT__, http://archive.org)dnl
-define(__API_SSL_ENDPOINT__, https://archive.org)dnl
-define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
-define(__CGIPATH__, /~user/pharos)dnl
-define(__CMD_AWK__, /usr/bin/awk)dnl
-define(__CMD_CURL__, /usr/bin/curl)dnl
-define(__CMD_ENV__, /usr/bin/env)dnl
-define(__CMD_JSON2TSV__, /usr/local/bin/json2tsv)dnl
-define(__CMD_MKTEMP__, /bin/mktemp)dnl
-define(__CMD_RM__, rm)dnl
-define(__CMD_STRINGS__, /bin/busybox strings)dnl
-define(__CMD_WEBDUMP__, /usr/local/bin/webdump)dnl
-define(__CMD_XARGS__, /usr/bin/xargs)dnl
-define(__GEOMYIDAE_VERSION__, 0.96)dnl
-define(__MAX_BIN_SIZE__, 10)dnl
-define(__MAX_TXT_SIZE__, 1)dnl
-define(__SERVER__, server)dnl
-define(__PORT__, port)dnl
-dnl
-dnl Set up M4 to work with AWK code
-dnl
-define(m4_substr, defn(substr))dnl
-undefine(substr)dnl
-changecom(`/*', `*/')dnl
-define(incl,`ifdef(paste,paste($1),undivert($1))')dnl

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # details/index.dcgi
 #
 # Show details for an item
 
-include(config.awk)
+include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 

Index: src/download/index.dcgi.m4
==================================================================
--- src/download/index.dcgi.m4
+++ src/download/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # download/index.dcgi
 #
 # Show file downloads using either direct http or gopher proxy links
 
-include(config.awk)
+include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(cmd, dir, files, file_size, format, iaout, is_archive, \

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # list/index.dcgi
 #
 # Show a specific list
 
-include(config.awk)
+include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(     acct, cmd, count, iaout, id, is_private, item_id,

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # lists/index.dcgi
 #
 # Show a list of a user's lists
 
-include(config.awk)
+include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(     cmd, iaout, id, is_private, item_count, item_id, \

Index: src/raw/index.cgi.m4
==================================================================
--- src/raw/index.cgi.m4
+++ src/raw/index.cgi.m4
@@ -15,11 +15,11 @@
 #
 # text/index.cgi
 #
 # Show text content scraped from HTML document
 
-include(config.awk)
+include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 incl(src/web.awk)
 

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # search/index.dcgi
 #
 # Show search results
 
-include(config.awk)
+include(src/config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(search,     cmd, count, creator, descr, field, fields, i, \

Index: src/sort/index.dcgi.m4
==================================================================
--- src/sort/index.dcgi.m4
+++ src/sort/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # sort/index.dcgi
 #
 # Change search sort order
 
-include(config.awk)
+include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     i, lbl, opt) {
     lbl[1]  = "Default [^]"
     opt[1]  = "nav_order asc"

Index: src/wizard/step1/index.dcgi.m4
==================================================================
--- src/wizard/step1/index.dcgi.m4
+++ src/wizard/step1/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # wizard/step1/index.dcgi
 #
 # Select field to filter/search by
 
-include(config.awk)
+include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     i, lbl, opt, searchstr) {
     searchstr = parts[4]
 

Index: src/wizard/step2/index.dcgi.m4
==================================================================
--- src/wizard/step2/index.dcgi.m4
+++ src/wizard/step2/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # wizard/step2/index.dcgi
 #
 # Select mediatype to filter/search by
 
-include(config.awk)
+include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     field, newsearch, searchstr) {
     field = parts[4]
     searchstr = parts[5]

Index: src/wizard/step3/index.dcgi.m4
==================================================================
--- src/wizard/step3/index.dcgi.m4
+++ src/wizard/step3/index.dcgi.m4
@@ -3,11 +3,11 @@
 
 # wizard/step3/index.dcgi
 #
 # Apply new search terms
 
-include(config.awk)
+include(src/config.awk)
 incl(src/cgi.awk)
 
 function main(     field, label, newsearch, op, searchstr, value) {
     field = parts[4]
     searchstr = parts[5]

]]></content>
<updated>2024-08-13 15:21:05</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/a4446184b4/</id>
<title>Remove unnecessary $0 argument from p...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 23:36:34

Remove unnecessary $0 argument from print command.

  INSERTED    DELETED
         2          2 src/web.awk
         2          2 TOTAL over 1 changed file

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -104,11 +104,11 @@
         if (NR < marker) {
             if ($0 ~ /^References$/) {
                 marker = NR
             }
             if (type == TYPE_TEXT) {
-                print $0
+                print
             }
         } else {
             print_ref_full($0, base, proto, root)
         }
     }
@@ -131,11 +131,11 @@
         gsub(/\t/, "        ")
         if (NR < marker) {
             if ($0 ~ /^References$/) {
                 marker = NR
             }
-            print $0
+            print
         } else {
             print_ref_pharos($0)
         }
     }
     close(cmd)

]]></content>
<updated>2024-08-05 23:36:34</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/1d4e024c68/</id>
<title>Change print_html() to convert archiv...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 23:35:09

  Change print_html() to convert archive.org to pharos links in
  the webdump References section.

  INSERTED    DELETED
        67         20 src/web.awk
        67         20 TOTAL over 1 changed file

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -107,52 +107,99 @@
             }
             if (type == TYPE_TEXT) {
                 print $0
             }
         } else {
-            if (match($0, /^ [0-9]+\. /)) {
-                prefix = substr($0, 0, RLENGTH)
-                link = substr($0, RLENGTH+1)
-                if (link !~ /^[a-z]+:/) {
-                    # convert relative link to full URL
-                    relative = link
-                    if (relative ~ /^\/\//) {
-                        link = proto relative
-                    } else if (relative ~ /^\//) {
-                        link = root relative
-                    } else {
-                        link = base "/" relative
-                    }
-               }
-               print prefix link
-            } else {
-               print $0
-            }
+            print_ref_full($0, base, proto, root)
         }
     }
     close(cmd)
 
     unlink(curlcfg)
 
     return
 }
 
-function print_html(html,    cmd, work) {
+function print_html(html,    cmd, marker, work) {
     work = gettemp()
     gsub(/\\n/, "<br>", html)
     print html >work
     close(work)
     cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work, \
         cmd_webdump)
+    marker = 999999
     while ((cmd | getline) > 0) {
         gsub(/\t/, "        ")
-        print
+        if (NR < marker) {
+            if ($0 ~ /^References$/) {
+                marker = NR
+            }
+            print $0
+        } else {
+            print_ref_pharos($0)
+        }
     }
     close(cmd)
     unlink(work)
     return
 }
+
+# Print the webdump references section, converting relative URLs
+# to full URLs
+
+function print_ref_full(str, base, proto, root,     link, prefix, relative) {
+    if (match(str, /^ [0-9]+\. /)) {
+        prefix = substr(str, 0, RLENGTH)
+        link = substr(str, RLENGTH+1)
+        # convert relative links to full URLs
+        if (link !~ /^[a-z]+:/) {
+            # convert relative link to full URL
+            relative = link
+            if (relative ~ /^\/\//) {
+                link = proto relative
+            } else if (relative ~ /^\//) {
+                link = root relative
+            } else {
+                link = base "/" relative
+            }
+        }
+        print prefix link
+    } else {
+        print str
+    }
+    return
+}
+
+
+# Print the webdump references section, translating archive.org URLs to
+# pharos URLs
+
+function print_ref_pharos(str,     id, label, link, prefix, relative, token) {
+    if (match(str, /^ [0-9]+\. /)) {
+        prefix = substr(str, 0, RLENGTH)
+        link = substr(str, RLENGTH+1)
+
+        id = ""
+        if (match(link, /https?:\/\/(www\.)?archive\.org\/details\//)) {
+            token = substr(link, RSTART+RLENGTH)
+            id = substr(token, 1, length(token) - 7)
+            if (match(id, /[?\/ ]/)) {
+                id = substr(id, 1, RSTART-1)
+            }
+        }
+        if (length(id) > 0) {
+            label = prefix id
+            printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
+                id, server, port
+        } else {
+             print str
+        }
+    } else {
+        print str
+    }
+    return
+}
 
 function web_init() {
     TYPE_HEADERS = 2
     TYPE_LINKS = 1
     TYPE_RAW = 9

]]></content>
<updated>2024-08-05 23:35:09</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/351ce65b44/</id>
<title>Webdump formats tables using tab char...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 21:30:19

  Webdump formats tables using tab characters. Tab characters have
  special meaning in gophermaps. Replace them with 8 spaces in
  function print_html()

  INSERTED    DELETED
         1          0 src/web.awk
         1          0 TOTAL over 1 changed file

Index: src/web.awk
==================================================================
--- src/web.awk
+++ src/web.awk
@@ -142,10 +142,11 @@
     print html >work
     close(work)
     cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work, \
         cmd_webdump)
     while ((cmd | getline) > 0) {
+        gsub(/\t/, "        ")
         print
     }
     close(cmd)
     unlink(work)
     return

]]></content>
<updated>2024-08-05 21:30:19</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/16461e9da5/</id>
<title>Truncate long items in user lists.</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 16:28:51

Truncate long items in user lists.

  INSERTED    DELETED
         3          2 src/list/index.dcgi.m4
         8          5 src/lists/index.dcgi.m4
        11          7 TOTAL over 2 changed files

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -9,11 +9,11 @@
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(     acct, cmd, count, iaout, id, is_private, item_id,
-    list_id, name, parts, url)
+    label, list_id, name, parts, url)
 {
     count = split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
 
@@ -44,11 +44,12 @@
                 is_private = 1
             }
         } else if ($1 == ".value.members[].identifier" && $2 == "s") {
             if (!is_private) {
                 item_id = $3
-                printf "[1|%s|%s/details/%s|%s|%s]\n", item_id,
+                label = shorten_left(item_id, 60)
+                printf "[1|%s|%s/details/%s|%s|%s]\n", label,
                     cgipath, item_id, server, port
             }
         }
     }
     close(cmd)

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -8,11 +8,12 @@
 include(config.awk)
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
-function main(     cmd, iaout, id, is_private, item_count, item_id, name, url)
+function main(     cmd, iaout, id, is_private, item_count, item_id, \
+    label, name, url)
 {
     print search "'s Lists"
     print ""
 
     iaout = gettemp()
@@ -31,12 +32,13 @@
 
     while ((cmd | getline) > 0) {
         if ($1 == ".value[]" && $2 == "o") {
             # print information for previous list
             if (!is_private && length(name) > 0 && item_count > 0) {
-                printf "[1|%4d Items: %-40s|%s/list/%%09%s/%d|%s|%s]\n",
-                    item_count, name, cgipath, search, id, server, port
+                label = shorten_left(name, 50)
+                printf "[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]\n",
+                    item_count, label, cgipath, search, id, server, port
             }
         } else if ($1 == ".value[].list_name" && $2 == "s") {
             name = $3
             id = 0
             is_private = 0
@@ -53,12 +55,13 @@
     }
     close(cmd)
 
     # print information for previous list
     if (!is_private && length(name) > 0 && item_count > 0) {
-        printf "[1|%4d Items: %-40s|%s/list/%%09%s/%d|%s|%s]\n",
-            item_count, name, cgipath, search, id, server, port
+        label = shorten_left(name, 50)
+        printf "[1|%4d Items: %-50s|%s/list/%%09%s/%d|%s|%s]\n",
+            item_count, label, cgipath, search, id, server, port
     }
 
     print ""
     printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
 

]]></content>
<updated>2024-08-05 16:28:51</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/a46aa098c9/</id>
<title>Remove duplicate "name" argument to f...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 16:16:34

  Remove duplicate "name" argument to function main() in
  list/index.dcgi

  INSERTED    DELETED
         1          1 src/list/index.dcgi.m4
         1          1 TOTAL over 1 changed file

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -9,11 +9,11 @@
 incl(src/api.awk)
 incl(src/cgi.awk)
 incl(src/util.awk)
 
 function main(     acct, cmd, count, iaout, id, is_private, item_id,
-    name, list_id, name, parts, url)
+    list_id, name, parts, url)
 {
     count = split(search, parts, "/")
     acct = parts[1]
     list_id = parts[2]
 

]]></content>
<updated>2024-08-05 16:16:34</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/707f61518c/</id>
<title>Add missing newlines in the account a...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 02:33:38

  Add missing newlines in the account and item details. Typos left
  over from splitting pharos into multiple scripts.

  INSERTED    DELETED
         1          1 src/account/index.dcgi.m4
         5          5 src/details/index.dcgi.m4
         6          6 TOTAL over 2 changed files

Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ src/account/index.dcgi.m4
@@ -90,11 +90,11 @@
     print ""
     printf "%-20s %s\n", "Identifier:", id
     if (item_size > 0) {
         printf "%-20s %d\n", "Item Size:", item_size
     }
-    printf "%-20s %s", "Media Type:", type
+    printf "%-20s %s\n", "Media Type:", type
     
     print ""
     printf "[h|Account web page|URL:%s/details/%s|%s|%s]\n",
         api_ssl_endpoint, uri_encode(id), server, port
     printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -124,21 +124,21 @@
         printf "%-20s %s\n", "Date Added:", add_date
     }
     if (pub_date != add_date) {
         printf "%-20s %s\n", "Date Published:", pub_date
     }
-    printf "%-20s %s", "Identifier:", id
+    printf "%-20s %s\n", "Identifier:", id
     if (item_size > 0) {
-        printf "%-20s %d", "Item Size:", item_size
+        printf "%-20s %d\n", "Item Size:", item_size
     }
     if (length(language) > 0) {
-        printf "%-20s %s", "Language:", language
+        printf "%-20s %s\n", "Language:", language
     }
     if (length(license) > 0) {
-        printf "%-20s %s", "License:", license
+        printf "%-20s %s\n", "License:", license
     }
-    printf "%-20s %s", "Media Type:", type
+    printf "%-20s %s\n", "Media Type:", type
     
     if (topics > 0) {
         print ""
         print "# Topics"
         for (i = 1; i <= topics; i++) {

]]></content>
<updated>2024-08-05 02:33:38</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/6b25b84bd7/</id>
<title>Escape vertical bar pipe | symbols in...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 02:22:23

Escape vertical bar pipe | symbols in search results.

  INSERTED    DELETED
         6          0 src/cgi.awk
         3          2 src/search/index.dcgi.m4
         9          2 TOTAL over 2 changed files

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -50,10 +50,16 @@
     split(path, parts, "/")
     topdir = parts[2]
 
     return
 }
+
+function gph_encode(str) {
+    retval = str
+    gsub(/\|/, "\\|", retval)
+    return retval
+}
 
 function uri_encode_init(     i, c) {
     for (i = 0; i <= 255; i++) {
         c = sprintf("%c", i)
         uri_encode_ord[c] = i

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -148,13 +148,14 @@
         } else if ($1 == ".response.docs[].title" && $2 == "s") {
             title = $3
             count++
             if (length(creator) > 0) {
                 label = sprintf("[%s] %s by %s", mediatype[type], \
-                    shorten(title, 40), shorten(creator, 18))
+                    gph_encode(shorten(title, 40)), shorten(creator, 18))
             } else {
-                label = sprintf("[%s] %s", mediatype[type], shorten(title, 58))
+                label = sprintf("[%s] %s", mediatype[type], \
+                    gph_encode(shorten(title, 58)))
             }
             if (type == "collection") {
                 printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
                     label, cgipath, id, server, port
             } else {

]]></content>
<updated>2024-08-05 02:22:23</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/94e85071a3/</id>
<title>Fix broken gophermap item in details/...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 00:38:25

Fix broken gophermap item in details/index.dcgi

  INSERTED    DELETED
         1          1 src/details/index.dcgi.m4
         1          1 TOTAL over 1 changed file

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -102,11 +102,11 @@
     }
 
     print(shorten(title, 70))
     if (length(creator) > 0) {
         label = "by " shorten(creator, 70)
-        printf "[1%s|%s/search/%%09creator:(%s)|%s|%d]\n", label,
+        printf "[1|%s|%s/search/%%09creator:(%s)|%s|%d]\n", label,
             cgipath, creator, server, port
     }
     if (length(thumb) > 0) {
         url = sprintf("http://%s%s/%s", item_server, dir, thumb)
         printf "[I|Thumbnail|%s/raw/%%09%s|%s|%d]\n",

]]></content>
<updated>2024-08-05 00:38:25</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/2b8f84da38/</id>
<title>Make sure to include input in selecto...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 00:34:07

  Make sure to include input in selector on goeomyidae version
  0.69 Also avoid making a double path separator in the selector.

  INSERTED    DELETED
         3          2 src/cgi.awk
         3          2 TOTAL over 1 changed file

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -13,14 +13,15 @@
         # remove leading text up through beginning of cgipath
         if (match(path, cgipath)) {
             selector = substr(path, RSTART)
         }
 
-        # remove trailing text from beinning of script name
+        # remove trailing text from beginning of script name
         if (match(selector, ENVIRON["SCRIPT_NAME"])) {
-            selector = substr(selector, 1, RSTART)
+            selector = substr(selector, 1, RSTART-1)
         }
+        selector = selector input
     } else {
         input = traversal
     }
 
     if (length(search) == 0 && match(input, /%09.*/)) {

]]></content>
<updated>2024-08-05 00:34:07</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/3e80d75ddb/</id>
<title>Work around missing selector on geomy...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-05 00:21:49

Work around missing selector on geomyidea version 0.69

  INSERTED    DELETED
        13          0 src/cgi.awk
        13          0 TOTAL over 1 changed file

Index: src/cgi.awk
==================================================================
--- src/cgi.awk
+++ src/cgi.awk
@@ -4,10 +4,23 @@
     traversal = ARGV[5]
     selector = ARGV[6]
 
     if (geomyidae_version < 0.96) {
         input = arguments
+
+        # geomyidae 0.69 doesn't populate selector, so do it manually
+        path = ENVIRON["PATH_TRANSLATED"]
+
+        # remove leading text up through beginning of cgipath
+        if (match(path, cgipath)) {
+            selector = substr(path, RSTART)
+        }
+
+        # remove trailing text from beinning of script name
+        if (match(selector, ENVIRON["SCRIPT_NAME"])) {
+            selector = substr(selector, 1, RSTART)
+        }
     } else {
         input = traversal
     }
 
     if (length(search) == 0 && match(input, /%09.*/)) {

]]></content>
<updated>2024-08-05 00:21:49</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/329cb37032/</id>
<title>Define macro incl() to use paste() on...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-04 23:57:54

  Define macro incl() to use paste() on BSD m4 and undivert() on
  GNU m4.

  INSERTED    DELETED
         4          4 src/account/index.dcgi.m4
         1          0 src/config.m4
         4          4 src/details/index.dcgi.m4
         3          3 src/download/index.dcgi.m4
         3          3 src/list/index.dcgi.m4
         3          3 src/lists/index.dcgi.m4
         4          4 src/raw/index.cgi.m4
         3          3 src/search/index.dcgi.m4
         1          1 src/sort/index.dcgi.m4
         1          1 src/wizard/step1/index.dcgi.m4
         1          1 src/wizard/step2/index.dcgi.m4
         1          1 src/wizard/step3/index.dcgi.m4
        29         28 TOTAL over 12 changed files

Index: src/account/index.dcgi.m4
==================================================================
--- src/account/index.dcgi.m4
+++ src/account/index.dcgi.m4
@@ -4,14 +4,14 @@
 # account/index.dcgi
 #
 # Show details for an account
 
 include(config.awk)
-undivert(api.awk)
-undivert(cgi.awk)
-undivert(util.awk)
-undivert(web.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
+incl(src/web.awk)
 
 function main(     acct, cmd, col, cols, descr, dir, email, iaout, id,
     item_server, item_size, thumb, title, type, url)
 {
     acct = parts[3]

Index: src/config.m4
==================================================================
--- src/config.m4
+++ src/config.m4
@@ -24,5 +24,6 @@
 dnl Set up M4 to work with AWK code
 dnl
 define(m4_substr, defn(substr))dnl
 undefine(substr)dnl
 changecom(`/*', `*/')dnl
+define(incl,`ifdef(paste,paste($1),undivert($1))')dnl

Index: src/details/index.dcgi.m4
==================================================================
--- src/details/index.dcgi.m4
+++ src/details/index.dcgi.m4
@@ -4,14 +4,14 @@
 # details/index.dcgi
 #
 # Show details for an item
 
 include(config.awk)
-undivert(api.awk)
-undivert(cgi.awk)
-undivert(util.awk)
-undivert(web.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
+incl(src/web.awk)
 
 function main(     add_date, col, cols, cmd, creator, descr, dir, i, \
     iaout, id, item_id, item_server, item_size, label, language,     \
     license, pub_date, scanner, thumb, title, topic, topics, type,   \
     uploader_account, uploader_email, url)

Index: src/download/index.dcgi.m4
==================================================================
--- src/download/index.dcgi.m4
+++ src/download/index.dcgi.m4
@@ -4,13 +4,13 @@
 # download/index.dcgi
 #
 # Show file downloads using either direct http or gopher proxy links
 
 include(config.awk)
-undivert(api.awk)
-undivert(cgi.awk)
-undivert(util.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
 
 function main(cmd, dir, files, file_size, format, iaout, is_archive, \
     is_proxy, item_server, label, mtime, name, source, url)
 {
     dir = parts[2]

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -4,13 +4,13 @@
 # list/index.dcgi
 #
 # Show a specific list
 
 include(config.awk)
-undivert(api.awk)
-undivert(cgi.awk)
-undivert(util.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
 
 function main(     acct, cmd, count, iaout, id, is_private, item_id,
     name, list_id, name, parts, url)
 {
     count = split(search, parts, "/")

Index: src/lists/index.dcgi.m4
==================================================================
--- src/lists/index.dcgi.m4
+++ src/lists/index.dcgi.m4
@@ -4,13 +4,13 @@
 # lists/index.dcgi
 #
 # Show a list of a user's lists
 
 include(config.awk)
-undivert(api.awk)
-undivert(cgi.awk)
-undivert(util.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
 
 function main(     cmd, iaout, id, is_private, item_count, item_id, name, url)
 {
     print search "'s Lists"
     print ""

Index: src/raw/index.cgi.m4
==================================================================
--- src/raw/index.cgi.m4
+++ src/raw/index.cgi.m4
@@ -16,14 +16,14 @@
 # text/index.cgi
 #
 # Show text content scraped from HTML document
 
 include(config.awk)
-undivert(api.awk)
-undivert(cgi.awk)
-undivert(util.awk)
-undivert(web.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
+incl(src/web.awk)
 
 function main() {
     if (path == "/debug/") {
         dump(search, TYPE_HEADERS)
     } else if (path == "/raw/") {

Index: src/search/index.dcgi.m4
==================================================================
--- src/search/index.dcgi.m4
+++ src/search/index.dcgi.m4
@@ -4,13 +4,13 @@
 # search/index.dcgi
 #
 # Show search results
 
 include(config.awk)
-undivert(api.awk)
-undivert(cgi.awk)
-undivert(util.awk)
+incl(src/api.awk)
+incl(src/cgi.awk)
+incl(src/util.awk)
 
 function main(search,     cmd, count, creator, descr, field, fields, i, \
     iaout, id, jsout, label, numfound, order, orders, page, rows,       \
     searchstr, title, type, url)
 {

Index: src/sort/index.dcgi.m4
==================================================================
--- src/sort/index.dcgi.m4
+++ src/sort/index.dcgi.m4
@@ -4,11 +4,11 @@
 # sort/index.dcgi
 #
 # Change search sort order
 
 include(config.awk)
-undivert(cgi.awk)
+incl(src/cgi.awk)
 
 function main(     i, lbl, opt) {
     lbl[1]  = "Default [^]"
     opt[1]  = "nav_order asc"
     lbl[2]  = "Default [v]"

Index: src/wizard/step1/index.dcgi.m4
==================================================================
--- src/wizard/step1/index.dcgi.m4
+++ src/wizard/step1/index.dcgi.m4
@@ -4,11 +4,11 @@
 # wizard/step1/index.dcgi
 #
 # Select field to filter/search by
 
 include(config.awk)
-undivert(cgi.awk)
+incl(src/cgi.awk)
 
 function main(     i, lbl, opt, searchstr) {
     searchstr = parts[4]
 
     lbl[1]  = "Any field contains"

Index: src/wizard/step2/index.dcgi.m4
==================================================================
--- src/wizard/step2/index.dcgi.m4
+++ src/wizard/step2/index.dcgi.m4
@@ -4,11 +4,11 @@
 # wizard/step2/index.dcgi
 #
 # Select mediatype to filter/search by
 
 include(config.awk)
-undivert(cgi.awk)
+incl(src/cgi.awk)
 
 function main(     field, newsearch, searchstr) {
     field = parts[4]
     searchstr = parts[5]
 

Index: src/wizard/step3/index.dcgi.m4
==================================================================
--- src/wizard/step3/index.dcgi.m4
+++ src/wizard/step3/index.dcgi.m4
@@ -4,11 +4,11 @@
 # wizard/step3/index.dcgi
 #
 # Apply new search terms
 
 include(config.awk)
-undivert(cgi.awk)
+incl(src/cgi.awk)
 
 function main(     field, label, newsearch, op, searchstr, value) {
     field = parts[4]
     searchstr = parts[5]
     value = search

]]></content>
<updated>2024-08-04 23:57:54</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/1c961bcf9a/</id>
<title>Replace Makefile with two small shell...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-04 23:53:17

  Replace Makefile with two small shell scripts. It's more
  K.I.S.S. than dealing with BSD vs GNU make.

  INSERTED    DELETED
         0         58 Makefile
        27          0 clean.sh
        40          0 make.sh
         3          3 readme.txt
        70         61 TOTAL over 4 changed files

DELETED Makefile
Index: Makefile
==================================================================
--- Makefile
+++ /dev/null
@@ -1,58 +0,0 @@
-DESTDIR = pharos
-SRC = src
-
-CGIS =	$(DESTDIR)/debug/index.cgi \
-	$(DESTDIR)/raw/index.cgi   \
-	$(DESTDIR)/text/index.cgi
-
-DCGIS =	$(DESTDIR)/account/index.dcgi      \
-	$(DESTDIR)/details/index.dcgi      \
-	$(DESTDIR)/direct/index.dcgi       \
-	$(DESTDIR)/download/index.dcgi     \
-	$(DESTDIR)/links/index.dcgi        \
-	$(DESTDIR)/list/index.dcgi         \
-	$(DESTDIR)/lists/index.dcgi        \
-	$(DESTDIR)/search/index.dcgi       \
-	$(DESTDIR)/sort/index.dcgi         \
-        $(DESTDIR)/wizard/step1/index.dcgi \
-        $(DESTDIR)/wizard/step2/index.dcgi \
-        $(DESTDIR)/wizard/step3/index.dcgi
-
-GOPHERMAPS = $(DESTDIR)/index.gph     \
-	$(DESTDIR)/audio/index.gph    \
-	$(DESTDIR)/books/index.gph    \
-	$(DESTDIR)/images/index.gph   \
-	$(DESTDIR)/software/index.gph \
-	$(DESTDIR)/video/index.gph
-
-all: $(DESTDIR) $(CGIS) $(DCGIS) $(GOPHERMAPS)
-
-$(DESTDIR)/debug/index.cgi: $(SRC)/raw/index.cgi.m4
-	m4 -I $(SRC) $< >$@
-	chmod a+rx $@
-
-$(DESTDIR)/text/index.cgi: $(SRC)/raw/index.cgi.m4
-	m4 -I $(SRC) $< >$@
-	chmod a+rx $@
-
-$(DESTDIR)/direct/index.dcgi: $(SRC)/download/index.dcgi.m4
-	m4 -I $(SRC) $< >$@
-	chmod a+rx $@
-
-$(DESTDIR)/links/index.dcgi: $(SRC)/raw/index.cgi.m4
-	m4 -I $(SRC) $< >$@
-	chmod a+rx $@
-
-$(DESTDIR)/%.cgi: $(SRC)/%.cgi.m4
-	m4 -I $(SRC) $< >$@
-	chmod a+rx $@
-
-$(DESTDIR)/%.dcgi: $(SRC)/%.dcgi.m4
-	m4 -I $(SRC) $< >$@
-	chmod a+rx $@
-
-$(DESTDIR)/%.gph: $(SRC)/%.gph.m4
-	m4 -I $(SRC) $< >$@
-
-clean:
-	rm -f $(CGIS) $(DCGIS) $(GOPHERMAPS)

ADDED   clean.sh
Index: clean.sh
==================================================================
--- /dev/null
+++ clean.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+set -x
+cd pharos
+rm -f index.gph             \
+    account/index.dcgi      \
+    audio/index.gph         \
+    books/index.gph         \
+    images/index.gph        \
+    debug/index.cgi         \
+    details/index.dcgi      \
+    direct/index.dcgi       \
+    download/index.dcgi     \
+    links/index.dcgi        \
+    list/index.dcgi         \
+    lists/index.dcgi        \
+    raw/index.cgi           \
+    search/index.dcgi       \
+    software/index.gph      \
+    sort/index.dcgi         \
+    text/index.cgi          \
+    video/index.gph         \
+    wizard/step1/index.dcgi \
+    wizard/step2/index.dcgi \
+    wizard/step3/index.dcgi
+set +x
+cd -
+exit 0

ADDED   make.sh
Index: make.sh
==================================================================
--- /dev/null
+++ make.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+DESTDIR=pharos
+SRC=src
+
+build() {
+    dir=$1
+    in=$2
+    out=$3
+    echo m4 -I $dir $in \>$out && chmod a+rx $out
+    m4 -I $dir $in >$out && chmod a+rx $out
+}
+
+# PLAIN
+for f in index.gph              \
+	account/index.dcgi      \
+	audio/index.gph         \
+	books/index.gph         \
+	images/index.gph        \
+	details/index.dcgi      \
+	download/index.dcgi     \
+	list/index.dcgi         \
+	lists/index.dcgi        \
+        raw/index.cgi           \
+	search/index.dcgi       \
+	software/index.gph      \
+	sort/index.dcgi         \
+	video/index.gph         \
+        wizard/step1/index.dcgi \
+        wizard/step2/index.dcgi \
+        wizard/step3/index.dcgi
+do
+    build $SRC ${SRC}/${f}.m4 ${DESTDIR}/$f
+done
+
+# WEIRD
+build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/debug/index.cgi
+build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/text/index.cgi
+build $SRC ${SRC}/download/index.dcgi.m4 ${DESTDIR}/direct/index.dcgi
+build $SRC ${SRC}/raw/index.cgi.m4 ${DESTDIR}/links/index.dcgi
+exit 0

Index: readme.txt
==================================================================
--- readme.txt
+++ readme.txt
@@ -34,11 +34,11 @@
 =============
 To set configuration variables, edit src/config.m4
 
 Installation
 ============
-Installation depends on make and m4.
+Installation depends on m4.
 
-$ make clean
-$ make
+$ sh clean.sh
+$ sh make.sh
 
 Copy pharos/ into place

]]></content>
<updated>2024-08-04 23:53:17</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/1a3675c579/</id>
<title>Remove /logout from the source code link</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-04 23:29:37

Remove /logout from the source code link

  INSERTED    DELETED
         1          1 src/index.gph.m4
         1          1 TOTAL over 1 changed file

Index: src/index.gph.m4
==================================================================
--- src/index.gph.m4
+++ src/index.gph.m4
@@ -21,6 +21,6 @@
 [1|Video|__CGIPATH__/video/|__SERVER__|__PORT__]
 [1|Audio|__CGIPATH__/audio/|__SERVER__|__PORT__]
 [1|Software|__CGIPATH__/software/|__SERVER__|__PORT__]
 [1|Images|__CGIPATH__/images/|__SERVER__|__PORT__]
 [0|About PHAROS|__CGIPATH__/about.txt|__SERVER__|__PORT__]
-[h|Source Code|URL:https://chiselapp.com/user/bencollver/repository/pharos/logout|__SERVER__|__PORT__]
+[h|Source Code|URL:https://chiselapp.com/user/bencollver/repository/pharos|__SERVER__|__PORT__]

]]></content>
<updated>2024-08-04 23:29:37</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/bb9dd0b07e/</id>
<title>Add empty files to make sure the dest...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-04 22:33:57

  Add empty files to make sure the destination directories get
  created.

  INSERTED    DELETED
 pharos/account/index.dcgi
 pharos/audio/index.gph
 pharos/books/index.gph
 pharos/debug/index.cgi
 pharos/details/index.dcgi
 pharos/direct/index.dcgi
 pharos/download/index.dcgi
 pharos/images/index.gph
 pharos/index.gph
 pharos/links/index.dcgi
 pharos/list/index.dcgi
 pharos/lists/index.dcgi
 pharos/raw/index.cgi
 pharos/search/index.dcgi
 pharos/software/index.gph
 pharos/sort/index.dcgi
 pharos/text/index.cgi
 pharos/video/index.gph
 pharos/wizard/step1/index.dcgi
 pharos/wizard/step2/index.dcgi
 pharos/wizard/step3/index.dcgi
         0          0 TOTAL over 0 changed files

ADDED   pharos/account/index.dcgi
Index: pharos/account/index.dcgi
==================================================================
--- /dev/null
+++ pharos/account/index.dcgi

ADDED   pharos/audio/index.gph
Index: pharos/audio/index.gph
==================================================================
--- /dev/null
+++ pharos/audio/index.gph

ADDED   pharos/books/index.gph
Index: pharos/books/index.gph
==================================================================
--- /dev/null
+++ pharos/books/index.gph

ADDED   pharos/debug/index.cgi
Index: pharos/debug/index.cgi
==================================================================
--- /dev/null
+++ pharos/debug/index.cgi

ADDED   pharos/details/index.dcgi
Index: pharos/details/index.dcgi
==================================================================
--- /dev/null
+++ pharos/details/index.dcgi

ADDED   pharos/direct/index.dcgi
Index: pharos/direct/index.dcgi
==================================================================
--- /dev/null
+++ pharos/direct/index.dcgi

ADDED   pharos/download/index.dcgi
Index: pharos/download/index.dcgi
==================================================================
--- /dev/null
+++ pharos/download/index.dcgi

ADDED   pharos/images/index.gph
Index: pharos/images/index.gph
==================================================================
--- /dev/null
+++ pharos/images/index.gph

ADDED   pharos/index.gph
Index: pharos/index.gph
==================================================================
--- /dev/null
+++ pharos/index.gph

ADDED   pharos/links/index.dcgi
Index: pharos/links/index.dcgi
==================================================================
--- /dev/null
+++ pharos/links/index.dcgi

ADDED   pharos/list/index.dcgi
Index: pharos/list/index.dcgi
==================================================================
--- /dev/null
+++ pharos/list/index.dcgi

ADDED   pharos/lists/index.dcgi
Index: pharos/lists/index.dcgi
==================================================================
--- /dev/null
+++ pharos/lists/index.dcgi

ADDED   pharos/raw/index.cgi
Index: pharos/raw/index.cgi
==================================================================
--- /dev/null
+++ pharos/raw/index.cgi

ADDED   pharos/search/index.dcgi
Index: pharos/search/index.dcgi
==================================================================
--- /dev/null
+++ pharos/search/index.dcgi

ADDED   pharos/software/index.gph
Index: pharos/software/index.gph
==================================================================
--- /dev/null
+++ pharos/software/index.gph

ADDED   pharos/sort/index.dcgi
Index: pharos/sort/index.dcgi
==================================================================
--- /dev/null
+++ pharos/sort/index.dcgi

ADDED   pharos/text/index.cgi
Index: pharos/text/index.cgi
==================================================================
--- /dev/null
+++ pharos/text/index.cgi

ADDED   pharos/video/index.gph
Index: pharos/video/index.gph
==================================================================
--- /dev/null
+++ pharos/video/index.gph

ADDED   pharos/wizard/step1/index.dcgi
Index: pharos/wizard/step1/index.dcgi
==================================================================
--- /dev/null
+++ pharos/wizard/step1/index.dcgi

ADDED   pharos/wizard/step2/index.dcgi
Index: pharos/wizard/step2/index.dcgi
==================================================================
--- /dev/null
+++ pharos/wizard/step2/index.dcgi

ADDED   pharos/wizard/step3/index.dcgi
Index: pharos/wizard/step3/index.dcgi
==================================================================
--- /dev/null
+++ pharos/wizard/step3/index.dcgi

]]></content>
<updated>2024-08-04 22:33:57</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/cc2ec0bce4/</id>
<title>Correct comment to descript *list/ind...</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-04 22:11:56

  Correct comment to descript *list/index.dcgi* NOT lists. Remove
  unused code block left over from lists/index.dcgi

  INSERTED    DELETED
         2          7 src/list/index.dcgi.m4
         2          7 TOTAL over 1 changed file

Index: src/list/index.dcgi.m4
==================================================================
--- src/list/index.dcgi.m4
+++ src/list/index.dcgi.m4
@@ -1,9 +1,9 @@
 include(config.m4)dnl
 #!__CMD_AWK__ -f
 
-# lists/index.dcgi
+# list/index.dcgi
 #
 # Show a specific list
 
 include(config.awk)
 undivert(api.awk)
@@ -32,16 +32,11 @@
     id = 0
     is_private = 0
     item_id = ""
 
     while ((cmd | getline) > 0) {
-        if ($1 == ".value[]" && $2 == "o") {
-            if (!is_private && length(name) > 0 && item_count > 0) {
-                printf "[1%4d Items: %-40s|%s/lists/%%09%s/%d|%s|%s]\n",
-                    item_count, name, cgipath, search, id, server, port
-            }
-        } else if ($1 == ".value.list_name" && $2 == "s") {
+        if ($1 == ".value.list_name" && $2 == "s") {
             name = $3
             print "# List: " name
             id = 0
             is_private = 0
         } else if ($1 == ".value.is_private" && $2 == "b") {

]]></content>
<updated>2024-08-04 22:11:56</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/d9b7917834/</id>
<title>Initial commit of pharos version 11</title>
<author><name>ben</name></author>
<content type="text"><![CDATA[
Check-in by ben on 2024-08-04 22:03:15

Initial commit of pharos version 11

  INSERTED    DELETED
        58          0 Makefile
        36          0 pharos/about.txt
        10          0 pharos/wizard/index.gph
        44          0 readme.txt
       112          0 src/account/index.dcgi.m4
        24          0 src/api.awk
        21          0 src/audio/index.gph.m4
        22          0 src/books/index.gph.m4
        74          0 src/cgi.awk
        20          0 src/config.awk
        28          0 src/config.m4
       200          0 src/details/index.dcgi.m4
       105          0 src/download/index.dcgi.m4
        14          0 src/images/index.gph.m4
        26          0 src/index.gph.m4
        74          0 src/list/index.dcgi.m4
        75          0 src/lists/index.dcgi.m4
        46          0 src/raw/index.cgi.m4
       212          0 src/search/index.dcgi.m4
        27          0 src/software/index.gph.m4
        61          0 src/sort/index.dcgi.m4
       131          0 src/util.awk
        23          0 src/video/index.gph.m4
       159          0 src/web.awk
        67          0 src/wizard/step1/index.dcgi.m4
        51          0 src/wizard/step2/index.dcgi.m4
        44          0 src/wizard/step3/index.dcgi.m4
      1764          0 TOTAL over 27 changed files

ADDED   Makefile
Index: Makefile
==================================================================
--- /dev/null
+++ Makefile
@@ -0,0 +1,58 @@
+DESTDIR = pharos
+SRC = src
+
+CGIS =	$(DESTDIR)/debug/index.cgi \
+	$(DESTDIR)/raw/index.cgi   \
+	$(DESTDIR)/text/index.cgi
+
+DCGIS =	$(DESTDIR)/account/index.dcgi      \
+	$(DESTDIR)/details/index.dcgi      \
+	$(DESTDIR)/direct/index.dcgi       \
+	$(DESTDIR)/download/index.dcgi     \
+	$(DESTDIR)/links/index.dcgi        \
+	$(DESTDIR)/list/index.dcgi         \
+	$(DESTDIR)/lists/index.dcgi        \
+	$(DESTDIR)/search/index.dcgi       \
+	$(DESTDIR)/sort/index.dcgi         \
+        $(DESTDIR)/wizard/step1/index.dcgi \
+        $(DESTDIR)/wizard/step2/index.dcgi \
+        $(DESTDIR)/wizard/step3/index.dcgi
+
+GOPHERMAPS = $(DESTDIR)/index.gph     \
+	$(DESTDIR)/audio/index.gph    \
+	$(DESTDIR)/books/index.gph    \
+	$(DESTDIR)/images/index.gph   \
+	$(DESTDIR)/software/index.gph \
+	$(DESTDIR)/video/index.gph
+
+all: $(DESTDIR) $(CGIS) $(DCGIS) $(GOPHERMAPS)
+
+$(DESTDIR)/debug/index.cgi: $(SRC)/raw/index.cgi.m4
+	m4 -I $(SRC) $< >$@
+	chmod a+rx $@
+
+$(DESTDIR)/text/index.cgi: $(SRC)/raw/index.cgi.m4
+	m4 -I $(SRC) $< >$@
+	chmod a+rx $@
+
+$(DESTDIR)/direct/index.dcgi: $(SRC)/download/index.dcgi.m4
+	m4 -I $(SRC) $< >$@
+	chmod a+rx $@
+
+$(DESTDIR)/links/index.dcgi: $(SRC)/raw/index.cgi.m4
+	m4 -I $(SRC) $< >$@
+	chmod a+rx $@
+
+$(DESTDIR)/%.cgi: $(SRC)/%.cgi.m4
+	m4 -I $(SRC) $< >$@
+	chmod a+rx $@
+
+$(DESTDIR)/%.dcgi: $(SRC)/%.dcgi.m4
+	m4 -I $(SRC) $< >$@
+	chmod a+rx $@
+
+$(DESTDIR)/%.gph: $(SRC)/%.gph.m4
+	m4 -I $(SRC) $< >$@
+
+clean:
+	rm -f $(CGIS) $(DCGIS) $(GOPHERMAPS)

ADDED   pharos/about.txt
Index: pharos/about.txt
==================================================================
--- /dev/null
+++ pharos/about.txt
@@ -0,0 +1,36 @@
+# About PHAROS
+
+Proxy Internet Archive to gopher.
+
+# Search
+
+Example: To find Indian music, excluding commercial samples.
+
+    description:(indian) AND -collection:samples_only AND 
+         mediatype:audio AND subject:music
+
+# Fields
+
+* date:
+  YYYY-MM-DD or [YYYY-MM-DD TO YYYY-MM-DD]
+* mediatype:
+  audio, collection, data, image, movies, software, texts, or web
+
+# Media type key for search results
+
+* [aud] = audio or etree
+* [col] = collection
+* [dat] = data
+* [img] = image
+* [mov] = movies
+* [bin] = software
+* [txt] = texts
+* [web] = web
+
+See also: <https://archive.org/advancedsearch.php>
+
+# Limits
+
+This service does not work in the lagrange browser.
+Lagrange URI encodes the search string and also
+mangles items that are not URI encoded.

ADDED   pharos/wizard/index.gph
Index: pharos/wizard/index.gph
==================================================================
--- /dev/null
+++ pharos/wizard/index.gph
@@ -0,0 +1,10 @@
+             ,    _
+            /|   | |
+          _/_\_  >_<
+         .-\-/.   |
+        /  | | \_ |
+        \ \| |\__(/
+        /(`---')  |
+       / /     \  |
+    _.'  \'-'  /  |
+    `----'`=-='   '    hjw

ADDED   readme.txt
Index: readme.txt
==================================================================
--- /dev/null
+++ readme.txt
@@ -0,0 +1,44 @@
+PHAROS by Ben Collver <bencollver@tilde.pink>
+=============================================
+* Description
+* Requirements
+* Configuration
+* Installation
+
+Description
+===========
+Pharos is a gopher front-end to the Internet Archive written in AWK.
+It is named after the light-house island near Alexandria.
+
+For a description and screenshots from an earlier version, see my
+post linked below.
+
+<gopher://tilde.pink/1/~bencollver/log/
+2024-07-30-pharos-gopher-frontend-to-internet-archive/>
+
+<https://gopher.tildeverse.org/tilde.pink/1/~bencollver/log/
+2024-07-30-pharos-gopher-frontend-to-internet-archive/>
+
+Requirements
+============
+* Runs under the Geomyidae gopher server.
+  <gopher://bitreich.org/1/scm/geomyidae/log.gph>
+* Written in AWK.  Tested using busybox awk.
+* Commands used:
+  * env, mktemp, rm, strings, xargs
+  * curl <https://curl.haxx.se/>
+  * json2tsv <gopher://codemadness.org/1/phlog/json2tsv/>
+  * webdump <gopher://codemadness.org/1/phlog/webdump/>
+
+Configuration
+=============
+To set configuration variables, edit src/config.m4
+
+Installation
+============
+Installation depends on make and m4.
+
+$ make clean
+$ make
+
+Copy pharos/ into place

ADDED   src/account/index.dcgi.m4
Index: src/account/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/account/index.dcgi.m4
@@ -0,0 +1,112 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# account/index.dcgi
+#
+# Show details for an account
+
+include(config.awk)
+undivert(api.awk)
+undivert(cgi.awk)
+undivert(util.awk)
+undivert(web.awk)
+
+function main(     acct, cmd, col, cols, descr, dir, email, iaout, id,
+    item_server, item_size, thumb, title, type, url)
+{
+    acct = parts[3]
+    email = search
+
+    iaout = gettemp()
+
+    url = api_endpoint "/metadata/" acct
+    api_request(url, "GET", iaout)
+    
+    # format search results as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    cols = 0
+    delete col
+    descr = ""
+    dir = ""
+    id = ""
+    item_server = ""
+    item_size = 0
+    title = ""
+    thumb = ""
+    type = ""
+    while ((cmd | getline) > 0) {
+        if ($1 == ".dir" && $2 == "s") {
+            dir = $3
+        } else if ($1 == ".files[].name" && $2 == "s") {
+            if ($3 == "__ia_thumb.jpg") {
+                thumb = $3
+            }
+        } else if ($1 == ".item_size" && $2 == "n") {
+            item_size = $3
+        } else if ($1 == ".metadata.collection" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.collection[]" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.description" && $2 == "s") {
+            descr = $3
+        } else if ($1 == ".metadata.identifier" && $2 == "s") {
+            id = $3
+        } else if ($1 == ".metadata.mediatype" && $2 == "s") {
+            type = $3
+        } else if ($1 == ".metadata.title" && $2 == "s") {
+            title = $3
+        } else if ($1 == ".server" && $2 == "s") {
+            item_server = $3
+        }
+    }
+    close(cmd)
+
+    if (length(id) == 0) {
+        print_not_found(url)
+        unlink(iaout)
+        return
+    }
+
+    print "Account: " acct
+    if (length(thumb) > 0) {
+        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
+        printf "[I|Thumbnail|%s/raw/%%09%s|%s|%s]\n", cgipath, url,
+            server, port
+    }
+    print_html(descr)
+
+    if (length(email) > 0) {
+        printf "[1|Uploads|%s/search/%%09uploader:%s|%s|%s]\n",
+            cgipath, email, server, port
+    }
+    printf "[1|Items|%s/search/%%09anyfield:%s|%s|%s]\n", cgipath,
+        acct, server, port
+    printf "[1|Lists|%s/lists/%%09%s|%s|%s]\n", cgipath, acct,
+        server, port
+
+    print ""
+    printf "%-20s %s\n", "Identifier:", id
+    if (item_size > 0) {
+        printf "%-20s %d\n", "Item Size:", item_size
+    }
+    printf "%-20s %s", "Media Type:", type
+    
+    print ""
+    printf "[h|Account web page|URL:%s/details/%s|%s|%s]\n",
+        api_ssl_endpoint, uri_encode(id), server, port
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    main()
+}

ADDED   src/api.awk
Index: src/api.awk
==================================================================
--- /dev/null
+++ src/api.awk
@@ -0,0 +1,24 @@
+function api_request(url, method, output, data) {
+    retval = ""
+    curlcfg = gettemp()
+    printf "--connect-timeout 10\n" > curlcfg
+    printf "--max-redirs 0\n" >> curlcfg
+    if (url ~ /^https:/) {
+        printf "--proto =https\n" >> curlcfg
+    } else {
+        printf "--proto =http\n" >> curlcfg
+    }
+    printf "--show-error\n" >> curlcfg
+    printf "--silent\n" >> curlcfg
+    printf "--url %s\n", uri_encode(url) >> curlcfg
+    printf "--user-agent %s\n", agent >> curlcfg
+    close(curlcfg)
+    cmd = sprintf("%s -K %s 2>&1", cmd_curl, curlcfg)
+    while ((cmd | getline) > 0) {
+        print $0 >>output
+    }
+    close(output)
+    close(cmd)
+    unlink(curlcfg)
+    return retval
+}

ADDED   src/audio/index.gph.m4
Index: src/audio/index.gph.m4
==================================================================
--- /dev/null
+++ src/audio/index.gph.m4
@@ -0,0 +1,21 @@
+include(config.m4)dnl
+# Audio
+
+[1|All Audio|__CGIPATH__/search/sortweek desc/%09mediatype:audio|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:audio|__SERVER__|__PORT__]
+[1|Live Music Archive|__CGIPATH__/search/sortweek desc/%09collection:etree AND mediatype:collection|__SERVER__|__PORT__]
+[1|Librivox Free Audiobooks|__CGIPATH__/search/sortweek desc/%09collection:librivoxaudio|__SERVER__|__PORT__]
+[1|Grateful Dead|__CGIPATH__/search/sortweek desc/%09collection:GratefulDead|__SERVER__|__PORT__]
+[1|Netlabels|__CGIPATH__/search/sortweek desc/%09collection:netlabels AND mediatype:collection|__SERVER__|__PORT__]
+[1|Old Time Radio|__CGIPATH__/search/sortweek desc/%09collection:oldtimeradio|__SERVER__|__PORT__]
+[1|78 RPMs & Cylinder Recordings|__CGIPATH__/search/sortweek desc/%09collection:78rpm AND mediatype:collection|__SERVER__|__PORT__]
+[1|Audio Books & Poetry|__CGIPATH__/search/sortweek desc/%09collection:audio_bookspoetry|__SERVER__|__PORT__]
+[1|Computers, Technology & Science|__CGIPATH__/search/sortweek desc/%09collection:audio_tech|__SERVER__|__PORT__]
+[1|Music, Arts & Culture|__CGIPATH__/search/sortweek desc/%09collection:audio_music AND mediatype:collection|__SERVER__|__PORT__]
+[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:audio_news|__SERVER__|__PORT__]
+[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:audio_religion|__SERVER__|__PORT__]
+[1|Podcasts|__CGIPATH__/search/sortweek desc/%09collection:podcasts|__SERVER__|__PORT__]
+[1|Radio News Archive|__CGIPATH__/search/sortweek desc/%09collection:radio|__SERVER__|__PORT__]
+[1|Long Playing Records|__CGIPATH__/search/sortweek desc/%09collection:album_recordings|__SERVER__|__PORT__]
+[1|Various Cassette Tapes|__CGIPATH__/search/sortweek desc/%09collection:cassettetapes|__SERVER__|__PORT__]
+[1|Audiophile CD Collection|__CGIPATH__/search/sortweek desc/%09collection:acdc AND -collection:samples_only|__SERVER__|__PORT__]

ADDED   src/books/index.gph.m4
Index: src/books/index.gph.m4
==================================================================
--- /dev/null
+++ src/books/index.gph.m4
@@ -0,0 +1,22 @@
+include(config.m4)dnl
+# Books
+
+[1|All Books|__CGIPATH__/search/sortweek desc/%09collection:books AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|All Texts|__CGIPATH__/search/sortweek desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:texts AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Smithsonian Libraries|__CGIPATH__/search/sortweek desc/%09collection:smithsonian|__SERVER__|__PORT__]
+[1|FEDLINK|__CGIPATH__/search/sortweek desc/%09collection:fedlink AND mediatype:collection|__SERVER__|__PORT__]
+[1|Genealogy|__CGIPATH__/search/sortweek desc/%09collection:genealogy AND mediatype:collection|__SERVER__|__PORT__]
+[1|Lincoln Collection|__CGIPATH__/search/sortweek desc/%09collection:lincolncollection|__SERVER__|__PORT__]
+[1|American Libraries|__CGIPATH__/search/sortweek desc/%09collection:americana AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Canadian Libraries|__CGIPATH__/search/sortweek desc/%09collection:toronto AND mediatype:collection AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Universal Library|__CGIPATH__/search/sortweek desc/%09collection:universallibrary|__SERVER__|__PORT__]
+[1|Project Gutenberg|__CGIPATH__/search/sortweek desc/%09collection:gutenberg|__SERVER__|__PORT__]
+[1|Children's Library|__CGIPATH__/search/sortweek desc/%09collection:iacl AND -access-restricted-item:true|__SERVER__|__PORT__]
+[1|Biodiversity Heritage Library|__CGIPATH__/search/sortweek desc/%09collection:biodiversity AND mediatype:collection|__SERVER__|__PORT__]
+[1|Books By Language|__CGIPATH__/search/sortweek desc/%09collection:booksbylanguage AND mediatype:collection|__SERVER__|__PORT__]
+[1|Additional Collections|__CGIPATH__/search/sortweek desc/%09collection:additional_collections AND mediatype:collection|__SERVER__|__PORT__]
+[1|The Magazine Rack|__CGIPATH__/search/sortweek desc/%09collection:magazine_rack AND mediatype:collection|__SERVER__|__PORT__]
+[1|The Pulp Magazine Archive|__CGIPATH__/search/sortweek desc/%09collection:pulpmagazinearchive|__SERVER__|__PORT__]
+[1|Newspapers|__CGIPATH__/search/sortweek desc/%09collection:newspapers AND mediatype:collection|__SERVER__|__PORT__]
+[1|Comic Books & Graphic Novels|__CGIPATH__/search/sortweek desc/%09collection:comics AND -access-restricted-item:true|__SERVER__|__PORT__]

ADDED   src/cgi.awk
Index: src/cgi.awk
==================================================================
--- /dev/null
+++ src/cgi.awk
@@ -0,0 +1,74 @@
+function cgi_init() {
+    search = ARGV[1]
+    arguments = ARGV[2]
+    traversal = ARGV[5]
+    selector = ARGV[6]
+
+    if (geomyidae_version < 0.96) {
+        input = arguments
+    } else {
+        input = traversal
+    }
+
+    if (length(search) == 0 && match(input, /%09.*/)) {
+        # This is a hack to include a search in the URL.
+        # everything before %09 is considered arguments
+        # everything after %09 is considered the search
+        search = substr(input, RSTART+3, RLENGTH-3)
+        args = substr(input, 0, RSTART-1)
+    } else {
+        args = input
+    }
+
+    # query is everything after ? in the gopher selector
+    # this is NOT the same as the gopher search string
+    query = args
+
+    # parse the path out of the selector
+    path = selector
+    if (substr(path, 1, length(cgipath)) == cgipath) {
+        path = substr(path, length(cgipath) + 1)
+    }
+    if (match(path, /%09/)) {
+        path = substr(path, 1, RSTART-1)
+    }
+
+    split(path, parts, "/")
+    topdir = parts[2]
+
+    return
+}
+
+function uri_encode_init(     i, c) {
+    for (i = 0; i <= 255; i++) {
+        c = sprintf("%c", i)
+        uri_encode_ord[c] = i
+        uri_encode_tab[i] = c
+    }
+
+    # Percent encode only control characters so that the higher unicode
+    # block characters are left plainly visible.
+    for (i = 0; i < 33; i++) {
+        uri_encode_tab[i] = sprintf("%%%02X", i)
+    }
+
+    # Percent encode higher unicode block characters too
+    for (i = 128; i <= 255; i++) {
+         uri_encode_tab[i] = sprintf("%%%02X", i)
+    }
+
+    # DEL
+    uri_encode_tab[127] = sprintf("%%%02X", 127)
+
+    return
+}
+
+function uri_encode(str,     i, c, len) {
+    len = length(str)
+    retval = ""
+    for (i = 1; i <= len; i++) {
+        c = substr(str, i, 1)
+        retval = retval uri_encode_tab[uri_encode_ord[c]]
+    }
+    return retval
+}

ADDED   src/config.awk
Index: src/config.awk
==================================================================
--- /dev/null
+++ src/config.awk
@@ -0,0 +1,20 @@
+function config_init() {
+    agent = "__AGENT__"
+    api_endpoint = "__API_ENDPOINT__"
+    api_ssl_endpoint = "__API_SSL_ENDPOINT__"
+    cgipath = "__CGIPATH__"
+    cmd_curl = "__CMD_CURL__"
+    cmd_enc = "__CMD_ENV__"
+    cmd_json2tsv = "__CMD_JSON2TSV__"
+    cmd_mktemp = "__CMD_MKTEMP__"
+    cmd_rm = "__CMD_RM__"
+    cmd_strings = "__CMD_STRINGS__"
+    cmd_webdump = "__CMD_WEBDUMP__"
+    cmd_xargs = "__CMD_XARGS__"
+    geomyidae_version = __GEOMYIDAE_VERSION__
+    max_bin_size = __MAX_BIN_SIZE__
+    max_txt_size = __MAX_TXT_SIZE__
+    server = "__SERVER__"
+    port = "__PORT__"
+    return
+}

ADDED   src/config.m4
Index: src/config.m4
==================================================================
--- /dev/null
+++ src/config.m4
@@ -0,0 +1,28 @@
+dnl Set configuration variables
+dnl
+define(__PHAROS_VERSION__, 11)dnl
+dnl
+define(__API_ENDPOINT__, http://archive.org)dnl
+define(__API_SSL_ENDPOINT__, https://archive.org)dnl
+define(__AGENT__, Lynx/2.9.0dev.10 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.1.1w)dnl
+define(__CGIPATH__, /~user/pharos)dnl
+define(__CMD_AWK__, /usr/bin/awk)dnl
+define(__CMD_CURL__, /usr/bin/curl)dnl
+define(__CMD_ENV__, /usr/bin/env)dnl
+define(__CMD_JSON2TSV__, /usr/local/bin/json2tsv)dnl
+define(__CMD_MKTEMP__, /bin/mktemp)dnl
+define(__CMD_RM__, rm)dnl
+define(__CMD_STRINGS__, /bin/busybox strings)dnl
+define(__CMD_WEBDUMP__, /usr/local/bin/webdump)dnl
+define(__CMD_XARGS__, /usr/bin/xargs)dnl
+define(__GEOMYIDAE_VERSION__, 0.96)dnl
+define(__MAX_BIN_SIZE__, 10)dnl
+define(__MAX_TXT_SIZE__, 1)dnl
+define(__SERVER__, server)dnl
+define(__PORT__, port)dnl
+dnl
+dnl Set up M4 to work with AWK code
+dnl
+define(m4_substr, defn(substr))dnl
+undefine(substr)dnl
+changecom(`/*', `*/')dnl

ADDED   src/details/index.dcgi.m4
Index: src/details/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/details/index.dcgi.m4
@@ -0,0 +1,200 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# details/index.dcgi
+#
+# Show details for an item
+
+include(config.awk)
+undivert(api.awk)
+undivert(cgi.awk)
+undivert(util.awk)
+undivert(web.awk)
+
+function main(     add_date, col, cols, cmd, creator, descr, dir, i, \
+    iaout, id, item_id, item_server, item_size, label, language,     \
+    license, pub_date, scanner, thumb, title, topic, topics, type,   \
+    uploader_account, uploader_email, url)
+{
+    item_id = parts[3]
+
+    iaout = gettemp()
+
+    url = api_endpoint "/metadata/" item_id
+    api_request(url, "GET", iaout)
+    
+    # format search results as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    cols = 0
+    delete col
+    add_date = ""
+    creator = ""
+    descr = ""
+    dir = ""
+    id = ""
+    item_server = ""
+    item_size = 0
+    language = ""
+    license = ""
+    pub_date = ""
+    scanner = ""
+    thumb = ""
+    title = ""
+    topics = 0
+    delete topic
+    type = ""
+    uploader_account = ""
+    uploader_email = ""
+    while ((cmd | getline) > 0) {
+        if ($1 == ".dir" && $2 == "s") {
+            dir = $3
+        } else if ($1 == ".files[].name" && $2 == "s") {
+            if ($3 == "__ia_thumb.jpg") {
+                thumb = $3
+            }
+        } else if ($1 == ".item_size" && $2 == "n") {
+            item_size = $3
+        } else if ($1 == ".metadata.addeddate" && $2 == "s") {
+            added_date = $3
+        } else if ($1 == ".metadata.collection" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.collection[]" && $2 == "s") {
+            cols++
+            col[cols] = $3
+        } else if ($1 == ".metadata.creator" && $2 == "s") {
+            creator = $3
+        } else if ($1 == ".metadata.description" && $2 == "s") {
+            descr = $3
+        } else if ($1 == ".metadata.identifier" && $2 == "s") {
+            id = $3
+        } else if ($1 == ".metadata.language" && $2 == "s") {
+            language = $3
+        } else if ($1 == ".metadata.license" && $2 == "s") {
+            license = licenseurl[$3]
+        } else if ($1 == ".metadata.mediatype" && $2 == "s") {
+            type = $3
+        } else if ($1 == ".metadata.publicdate" && $2 == "s") {
+            pub_date = $3
+        } else if ($1 == ".metadata.scanner" && $2 == "s") {
+            scanner = $3
+        } else if ($1 == ".metadata.subject" && $2 == "s") {
+            topics++
+            topic[topics] = $3
+        } else if ($1 == ".metadata.subject[]" && $2 == "s") {
+            topics++
+            topic[topics] = $3
+        } else if ($1 == ".metadata.title" && $2 == "s") {
+            title = $3
+        } else if ($1 == ".metadata.uploader" && $2 == "s") {
+            uploader_email = $3
+        } else if ($1 == ".server" && $2 == "s") {
+            item_server = $3
+        }
+    }
+    close(cmd)
+
+    if (length(id) == 0) {
+        print_not_found(url)
+        unlink(iaout)
+        return
+    }
+
+    print(shorten(title, 70))
+    if (length(creator) > 0) {
+        label = "by " shorten(creator, 70)
+        printf "[1%s|%s/search/%%09creator:(%s)|%s|%d]\n", label,
+            cgipath, creator, server, port
+    }
+    if (length(thumb) > 0) {
+        url = sprintf("http://%s%s/%s", item_server, dir, thumb)
+        printf "[I|Thumbnail|%s/raw/%%09%s|%s|%d]\n",
+            cgipath, url, server, port
+    }
+
+    printf "[1|Download|%s/download/%s|%s|%d]\n", cgipath,
+        item_id, server, port
+    print ""
+
+    print_html(descr)
+
+    print ""
+    if (length(add_date) > 0) {
+        printf "%-20s %s\n", "Date Added:", add_date
+    }
+    if (pub_date != add_date) {
+        printf "%-20s %s\n", "Date Published:", pub_date
+    }
+    printf "%-20s %s", "Identifier:", id
+    if (item_size > 0) {
+        printf "%-20s %d", "Item Size:", item_size
+    }
+    if (length(language) > 0) {
+        printf "%-20s %s", "Language:", language
+    }
+    if (length(license) > 0) {
+        printf "%-20s %s", "License:", license
+    }
+    printf "%-20s %s", "Media Type:", type
+    
+    if (topics > 0) {
+        print ""
+        print "# Topics"
+        for (i = 1; i <= topics; i++) {
+            label = shorten(topic[i], 40)
+            printf "[1|%s|%s/search/%%09subject:(%s)|%s|%s]\n", label,
+                cgipath, topic[i], server, port
+        }
+    }
+
+    # scrape uploader name from item web page HTML
+    url = api_ssl_endpoint "/details/" item_id
+    api_request(url, "GET", iaout)
+    while ((getline <iaout) > 0) {
+        if (/item-upload-info__uploader-name/ &&
+            match($0, /\/details\/[^"]*"/))
+        {
+            uploader_account = substr($0, RSTART+9, RLENGTH-10)
+        }
+    }
+    close(iaout)
+
+    if (cols > 0) {
+        print ""
+        print "# Collections"
+        for (i = 1; i <= cols; i++) {
+            label = shorten(col[i], 40)
+            printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
+                label, cgipath, col[i], server, port
+        }
+    }
+
+    print ""
+    print "# Uploaded by"
+    if (length(uploader_account) > 0) {
+        label = shorten(uploader_account, 70)
+        printf "[1|%s|%s/account/%s%%09%s|%s|%s]\n", label, cgipath,
+            uploader_account, uploader_email, server, port
+    } else {
+        label = shorten(uploader_email, 70)
+        printf "[1|%s|%s/search/%%09uploader:%s|%s|%s]\n", label,
+            cgipath, uploader_email, server, port
+    }
+
+    print ""
+    printf "[h|Item web page|URL:%s/details/%s|%s|%s]\n",
+        api_ssl_endpoint, uri_encode(id), server, port
+    printf "[1|PHAROS|%s|%s|%d]\n", cgipath, server, port
+
+    unlink(iaout)
+    return
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    main()
+}

ADDED   src/download/index.dcgi.m4
Index: src/download/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/download/index.dcgi.m4
@@ -0,0 +1,105 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# download/index.dcgi
+#
+# Show file downloads using either direct http or gopher proxy links
+
+include(config.awk)
+undivert(api.awk)
+undivert(cgi.awk)
+undivert(util.awk)
+
+function main(cmd, dir, files, file_size, format, iaout, is_archive, \
+    is_proxy, item_server, label, mtime, name, source, url)
+{
+    dir = parts[2]
+    item_id = parts[3]
+    if (dir == "download") {
+        is_proxy = 1
+    } else {
+        # dir == "direct"
+        is_proxy = 0
+    }
+
+    iaout = gettemp()
+
+    url = api_endpoint "/metadata/" item_id
+    api_request(url, "GET", iaout)
+
+    # format search results as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    dir = ""
+    files = 0
+    delete format
+    item_server = ""
+    delete mtime
+    delete name
+    delete file_size
+    delete source
+
+    while ((cmd | getline) > 0) {
+        if ($1 == ".dir" && $2 == "s") {
+            dir = $3
+        } else if ($1 == ".files[]" && $2 == "o") {
+            files++
+        } else if ($1 == ".files[].format" && $2 == "s") {
+            format[files] = $3
+        } else if ($1 == ".files[].mtime" && $2 == "s") {
+            mtime[files] = $3
+        } else if ($1 == ".files[].name" && $2 == "s") {
+            name[files] = $3
+        } else if ($1 == ".files[].size" && $2 == "s") {
+            file_size[files] = $3
+        } else if ($1 == ".files[].source" && $2 == "s") {
+            source[files] = $3
+        } else if ($1 == ".server" && $2 == "s") {
+            item_server = $3
+        }
+    }
+    close(cmd)
+
+    for (i = 1; i <= files; i++) {
+        label = sprintf("%s %s %s", \
+            shorten_left(name[i], 40), \
+            strftime("%Y-%m-%d %H:%M", mtime[i]), \
+            human_size(file_size[i]))
+        url = sprintf("http://%s%s/%s", item_server, dir, name[i])
+        if (is_proxy) {
+            if (file_size[i] > max_bin_size * size_mb) {
+                printf "[h|%s|URL:%s|%s|%s]\n", label, uri_encode(url),
+                    server, port
+            } else {
+                printf "[1|%s|%s/links/%%09%s|%s|%s]\n", label, cgipath,
+                    url, server, port
+            }
+        } else {
+            printf "[h|%s|URL:%s|%s|%s]\n", label, uri_encode(url),
+                server, port
+        }
+        is_archive = detect_archive(url)
+        if (is_archive) {
+            url = sprintf("http://%s/view_archive.php?archive=%s/%s", \
+                item_server, dir, name[i])
+            printf "[h|%s (View Contents)|URL:%s|%s|%s]\n",
+                shorten_left(name[i], 40), uri_encode(url),
+                server, port
+        }
+    }
+
+    printf "[1|Downloads via http|%s/direct/%s|%s|%s]\n", cgipath,
+        item_id, server, port
+
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    util_init()
+    main()
+}

ADDED   src/images/index.gph.m4
Index: src/images/index.gph.m4
==================================================================
--- /dev/null
+++ src/images/index.gph.m4
@@ -0,0 +1,14 @@
+include(config.m4)dnl
+# Images
+
+[1|All Images|__CGIPATH__/search/sortweek desc/%09mediatype:image|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:image|__SERVER__|__PORT__]
+[1|Metropolitan Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:metropolitanmuseumofart-gallery|__SERVER__|__PORT__]
+[1|Cleveland Museum of Art|__CGIPATH__/search/sortweek desc/%09collection:clevelandart|__SERVER__|__PORT__]
+[1|Flickr Commons|__CGIPATH__/search/sortweek desc/%09collection:flickrcommons|__SERVER__|__PORT__]
+[1|Occupy Wallstreet Flickr|__CGIPATH__/search/sortweek desc/%09collection:flickr-ows|__SERVER__|__PORT__]
+[1|Cover Art|__CGIPATH__/search/sortweek desc/%09collection:coverartarchive|__SERVER__|__PORT__]
+[1|USGS Maps|__CGIPATH__/search/sortweek desc/%09collection:maps_usgs|__SERVER__|__PORT__]
+[1|NASA Images|__CGIPATH__/search/sortweek desc/%09collection:nasa|__SERVER__|__PORT__]
+[1|Solar System Collection|__CGIPATH__/search/sortweek desc/%09collection:solarsystemcollection|__SERVER__|__PORT__]
+[1|Ames Research Center|__CGIPATH__/search/sortweek desc/%09collection:amesresearchcenterimagelibrary|__SERVER__|__PORT__]

ADDED   src/index.gph.m4
Index: src/index.gph.m4
==================================================================
--- /dev/null
+++ src/index.gph.m4
@@ -0,0 +1,26 @@
+include(config.m4)dnl
+        .n.                     |
+       /___\          _.---.  \ _ /
+       [|||]         (_._ ) )--;_) =-
+       [___]           '---'.__,' \
+       }-=-{                    |
+       |-" |
+       |.-"|                p
+~^=~^~-|_.-|~^-~^~ ~^~ -^~^~|\ ~^-~^~-
+^   .=.| _.|__  ^       ~  /| \
+ ~ /:. \" _|_/\    ~      /_|__\  ^
+.-/::.  |   |""|-._    ^   ~~~~
+  `===-'-----'""`  '-.              ~
+       PHAROS    __.-'      ^
+
+Gopher proxy to Internet Archive.
+
+[7|Search|__CGIPATH__/search/|__SERVER__|__PORT__]
+[1|Advanced Search|__CGIPATH__/wizard/step1|__SERVER__|__PORT__]
+[1|Books|__CGIPATH__/books/|__SERVER__|__PORT__]
+[1|Video|__CGIPATH__/video/|__SERVER__|__PORT__]
+[1|Audio|__CGIPATH__/audio/|__SERVER__|__PORT__]
+[1|Software|__CGIPATH__/software/|__SERVER__|__PORT__]
+[1|Images|__CGIPATH__/images/|__SERVER__|__PORT__]
+[0|About PHAROS|__CGIPATH__/about.txt|__SERVER__|__PORT__]
+[h|Source Code|URL:https://chiselapp.com/user/bencollver/repository/pharos/logout|__SERVER__|__PORT__]

ADDED   src/list/index.dcgi.m4
Index: src/list/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/list/index.dcgi.m4
@@ -0,0 +1,74 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# lists/index.dcgi
+#
+# Show a specific list
+
+include(config.awk)
+undivert(api.awk)
+undivert(cgi.awk)
+undivert(util.awk)
+
+function main(     acct, cmd, count, iaout, id, is_private, item_id,
+    name, list_id, name, parts, url)
+{
+    count = split(search, parts, "/")
+    acct = parts[1]
+    list_id = parts[2]
+
+    print acct "'s Lists"
+    print ""
+
+    iaout = gettemp()
+
+    url = api_ssl_endpoint "/services/users/" acct "/lists/" list_id
+    api_request(url, "GET", iaout)
+
+    # format list as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    name = ""
+    id = 0
+    is_private = 0
+    item_id = ""
+
+    while ((cmd | getline) > 0) {
+        if ($1 == ".value[]" && $2 == "o") {
+            if (!is_private && length(name) > 0 && item_count > 0) {
+                printf "[1%4d Items: %-40s|%s/lists/%%09%s/%d|%s|%s]\n",
+                    item_count, name, cgipath, search, id, server, port
+            }
+        } else if ($1 == ".value.list_name" && $2 == "s") {
+            name = $3
+            print "# List: " name
+            id = 0
+            is_private = 0
+        } else if ($1 == ".value.is_private" && $2 == "b") {
+            if ($3 == "true") {
+                is_private = 1
+            }
+        } else if ($1 == ".value.members[].identifier" && $2 == "s") {
+            if (!is_private) {
+                item_id = $3
+                printf "[1|%s|%s/details/%s|%s|%s]\n", item_id,
+                    cgipath, item_id, server, port
+            }
+        }
+    }
+    close(cmd)
+
+    print ""
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    main()
+}

ADDED   src/lists/index.dcgi.m4
Index: src/lists/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/lists/index.dcgi.m4
@@ -0,0 +1,75 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# lists/index.dcgi
+#
+# Show a list of a user's lists
+
+include(config.awk)
+undivert(api.awk)
+undivert(cgi.awk)
+undivert(util.awk)
+
+function main(     cmd, iaout, id, is_private, item_count, item_id, name, url)
+{
+    print search "'s Lists"
+    print ""
+
+    iaout = gettemp()
+
+    url = api_ssl_endpoint "/services/users/" search "/lists"
+    api_request(url, "GET", iaout)
+
+    # format list as a gopher directory (menu)
+    cmd = sprintf("%s <%s 2>&1", cmd_json2tsv, iaout)
+    FS = "\t"
+    name = ""
+    id = 0
+    is_private = 0
+    item_count = 0
+    item_id = ""
+
+    while ((cmd | getline) > 0) {
+        if ($1 == ".value[]" && $2 == "o") {
+            # print information for previous list
+            if (!is_private && length(name) > 0 && item_count > 0) {
+                printf "[1|%4d Items: %-40s|%s/list/%%09%s/%d|%s|%s]\n",
+                    item_count, name, cgipath, search, id, server, port
+            }
+        } else if ($1 == ".value[].list_name" && $2 == "s") {
+            name = $3
+            id = 0
+            is_private = 0
+            item_count = 0
+        } else if ($1 == ".value[].is_private" && $2 == "b") {
+            if ($3 == "true") {
+                is_private = 1
+            }
+        } else if ($1 == ".value[].id" && $2 == "n") {
+            id = $3
+        } else if ($1 == ".value[].members[].identifier" && $2 == "s") {
+            item_count++
+        }
+    }
+    close(cmd)
+
+    # print information for previous list
+    if (!is_private && length(name) > 0 && item_count > 0) {
+        printf "[1|%4d Items: %-40s|%s/list/%%09%s/%d|%s|%s]\n",
+            item_count, name, cgipath, search, id, server, port
+    }
+
+    print ""
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+
+    unlink(iaout)
+    return
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    main()
+}

ADDED   src/raw/index.cgi.m4
Index: src/raw/index.cgi.m4
==================================================================
--- /dev/null
+++ src/raw/index.cgi.m4
@@ -0,0 +1,46 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# debug/index.cgi
+#
+# Show HTTP headers from curl fetching a URL
+#
+# links/index.dcgi
+#
+# Show download links, plus links scraped from HTML document
+#
+# raw/index.cgi
+#
+# Show raw bytes from binary document
+#
+# text/index.cgi
+#
+# Show text content scraped from HTML document
+
+include(config.awk)
+undivert(api.awk)
+undivert(cgi.awk)
+undivert(util.awk)
+undivert(web.awk)
+
+function main() {
+    if (path == "/debug/") {
+        dump(search, TYPE_HEADERS)
+    } else if (path == "/raw/") {
+        dump(search, TYPE_RAW)
+    } else if (path == "/text/") {
+        dump(search, TYPE_TEXT)
+    } else if (path == "/links/") {
+        dump(search, TYPE_LINKS)
+    } 
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    web_init()
+    main()
+}

ADDED   src/search/index.dcgi.m4
Index: src/search/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/search/index.dcgi.m4
@@ -0,0 +1,212 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# search/index.dcgi
+#
+# Show search results
+
+include(config.awk)
+undivert(api.awk)
+undivert(cgi.awk)
+undivert(util.awk)
+
+function main(search,     cmd, count, creator, descr, field, fields, i, \
+    iaout, id, jsout, label, numfound, order, orders, page, rows,       \
+    searchstr, title, type, url)
+{
+    mediatype["audio"]      = "aud"
+    mediatype["collection"] = "col"
+    mediatype["data"]       = "dat"
+    mediatype["etree"]      = "aud"
+    mediatype["image"]      = "img"
+    mediatype["movies"]     = "mov"
+    mediatype["software"]   = "bin"
+    mediatype["texts"]      = "txt"
+    mediatype["web"]        = "web"
+
+    rows = 15
+    page = 1
+    delete order
+    orders = 0
+
+    # parse out page number and sort orders
+    for (i in parts) {
+        if (parts[i] ~ /^rows[0-9][0-9]*$/) {
+            rows = substr(parts[i], 5)
+        } else if (parts[i] ~ /^page[0-9][0-9]*$/) {
+            page = substr(parts[i], 5)
+        } else if (parts[i] ~ /^sort/) {
+            orders++
+        str = substr(parts[i], 5)
+            order[orders] = str
+        }
+    }
+
+    # special case for when the search term is an archive.org details URL
+    if (match(search, /^https:\/\/(www\.)?archive\.org\/details\//)) {
+        id = substr(search, RLENGTH+1)
+        search = "identifier:" id
+    }
+
+    # remove quotes from search string, since it gets quoted later
+    gsub(/"/, "", search)
+
+    # default sort orders if none were specified
+    if (orders == 0) {
+        orders = 1
+        if (search == "mediatype:collection" ||
+            search == "mediatype:(collection)")
+        {
+            order[1] = "collection_size desc"
+        } else {
+            order[1] = "nav_order desc"
+        }
+    }
+
+    iaout = gettemp()
+    jsout = gettemp()
+
+    # get search results
+
+    field[1] = "creator"
+    field[2] = "description"
+    field[3] = "identifier"
+    field[4] = "mediatype"
+    field[5] = "title"
+    fields = 5
+
+    # remove anyfield, a hobgoblin of consistency
+    searchstr = search
+    gsub(/anyfield:/, "", searchstr)
+
+    url = sprintf("%s/advancedsearch.php?q=%s&output=json&rows=%d&page=%d", \
+        api_endpoint, searchstr, rows, page)
+    for (i = 1; i <= orders; i++) {
+        url = url sprintf("&sort%%5B%d%%5D=%s", i-1, order[i])
+    }
+    for (i = 1; i <= fields; i++) {
+        url = url sprintf("&fl%%5B%d%%5D=%s", i-1, field[i])
+    }
+    api_request(url, "GET", iaout)
+
+    cmd = sprintf("%s <%s >%s 2>&1", cmd_json2tsv, iaout, jsout)
+    system(cmd)
+
+    numfound = 0
+    FS = "\t"
+    while ((getline <jsout) > 0) {
+        if ($1 == ".response.numFound" && $2 == "n") {
+            numfound = $3
+        }
+    }
+    close(jsout)
+
+    if (search ~ /^@/) {
+        numfound++
+    }
+    if (numfound == 0) {
+        print "Your search did not match any items in the Archive."
+        print "Try different keywords or a more general search."
+        print ""
+        printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+        unlink(jsout)
+        unlink(iaout)
+        return
+    } else {
+        pages = int(numfound / rows)
+        if (numfound % rows != 0) {
+            pages++
+        }
+        printf "# %s search results, page %d of %d\n", numfound, \
+            page, pages
+        print ""
+    }
+
+    # format search results as a gopher directory (menu)
+    FS = "\t"
+    creator = ""
+    descr = ""
+    id = ""
+    title = ""
+    type = ""
+    count = 0
+
+    if (search ~ /^@/) {
+        printf "[1|Account %s|%s/account/%s|%s|%s]\n", search, cgipath,
+           search, server, port
+    }
+
+    while ((getline <jsout) > 0) {
+        if ($1 == ".response.docs[].creator" && $2 == "s") {
+            creator = $3
+        } else if ($1 == ".response.docs[].description" && $2 == "s") {
+            descr = $3
+        } else if ($1 == ".response.docs[].identifier" && $2 == "s") {
+            id = $3
+        } else if ($1 == ".response.docs[].mediatype" && $2 == "s") {
+            type = $3
+        } else if ($1 == ".response.docs[].title" && $2 == "s") {
+            title = $3
+            count++
+            if (length(creator) > 0) {
+                label = sprintf("[%s] %s by %s", mediatype[type], \
+                    shorten(title, 40), shorten(creator, 18))
+            } else {
+                label = sprintf("[%s] %s", mediatype[type], shorten(title, 58))
+            }
+            if (type == "collection") {
+                printf "[1|%s|%s/search/%%09collection:(%s)|%s|%s]\n",
+                    label, cgipath, id, server, port
+            } else {
+                printf "[1|%s|%s/details/%s|%s|%s]\n", label, cgipath,
+                   id, server, port
+            }
+            creator = ""
+            descr = ""
+            id = ""
+            type = ""
+        }
+    }
+    close(jsout)
+
+    print ""
+
+    # only show "page back" if the user is past page 1
+    if (page > 1) {
+        printf "[1|[<<] Page %d|%s/search/page%d/rows%d/%%09%s|%s|%s]\n",
+            page - 1, cgipath, page - 1, rows, search, server, port
+    }
+
+    # only show "next page" if the current page is completely full
+    if (count == rows) {
+        printf "[1|[>>] Page %d|%s/search/page%d/rows%d/%%09%s|%s|%s]\n",
+            page + 1, cgipath, page + 1, rows, search, server, port
+    }
+
+    # only show "sort" if there's more than one item to sort
+    if (count > 1) {
+        printf "[1|[^v] Sort|%s/sort/%%09%s|%s|%s]\n", cgipath, search,
+            server, port
+    }
+
+    # only show "search within list" if there's multiple pages of results
+    if (numfound > rows) {
+        printf "[1|[\\/] Filter results|%s/wizard/step1/%s|%s|%s]\n",
+            cgipath, search, server, port
+    }
+
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+
+    unlink(jsout)
+    unlink(iaout)
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    uri_encode_init()
+    util_init()
+    main(search)
+}

ADDED   src/software/index.gph.m4
Index: src/software/index.gph.m4
==================================================================
--- /dev/null
+++ src/software/index.gph.m4
@@ -0,0 +1,27 @@
+include(config.m4)dnl
+
+[1|All Software|__CGIPATH__/search/sortweek desc/%09mediatype:software|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:software|__SERVER__|__PORT__]
+[1|Internet Arcade|__CGIPATH__/search/sortweek desc/%09collection:internetarcade|__SERVER__|__PORT__]
+[1|Console Living Room|__CGIPATH__/search/sortweek desc/%09collection:consolelivingroom|__SERVER__|__PORT__]
+[1|Old School Emulation|__CGIPATH__/search/sortweek desc/%09collection:tosec|__SERVER__|__PORT__]
+[1|MS-DOS Games|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_games|__SERVER__|__PORT__]
+[1|Historical Software|__CGIPATH__/search/sortweek desc/%09collection:historicalsoftware|__SERVER__|__PORT__]
+[1|Classic PC Games|__CGIPATH__/search/sortweek desc/%09collection:classicpcgames|__SERVER__|__PORT__]
+[1|Software Library|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary AND mediatype:collection|__SERVER__|__PORT__]
+[1|Kodi Archive & Support Files|__CGIPATH__/search/sortweek desc/%09collection:kodi_archive|__SERVER__|__PORT__]
+[1|Vintage Software|__CGIPATH__/search/sortweek desc/%09collection:vintagesoftware|__SERVER__|__PORT__]
+[1|APK|__CGIPATH__/search/sortweek desc/%09collection:apkarchive|__SERVER__|__PORT__]
+[1|MS-DOS|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos|__SERVER__|__PORT__]
+[1|CD-ROM Software|__CGIPATH__/search/sortweek desc/%09collection:cd-roms|__SERVER__|__PORT__]
+[1|CD-ROM Software Library|__CGIPATH__/search/sortweek desc/%09collection:cdromsoftware|__SERVER__|__PORT__]
+[1|Software Sites|__CGIPATH__/search/sortweek desc/%09collection:softwaresites|__SERVER__|__PORT__]
+[1|Tucows Software Library|__CGIPATH__/search/sortweek desc/%09collection:tucows|__SERVER__|__PORT__]
+[1|Shareware CD-ROMs|__CGIPATH__/search/sortweek desc/%09collection:cdbbsarchive|__SERVER__|__PORT__]
+[1|Software Capsules Compilation|__CGIPATH__/search/sortweek desc/%09collection:softwarecapsules|__SERVER__|__PORT__]
+[1|CD-ROM Images|__CGIPATH__/search/sortweek desc/%09collection:cdromimages|__SERVER__|__PORT__]
+[1|ZX Spectrum|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_zx_spectrum|__SERVER__|__PORT__]
+[1|DOOM Level CD|__CGIPATH__/search/sortweek desc/%09collection:doom-cds|__SERVER__|__PORT__]
+[1|Floppy Disks of Software|__CGIPATH__/search/sortweek desc/%09collection:floppysoftware|__SERVER__|__PORT__]
+[1|The Good Old Days IBM PC Floppy Collection|__CGIPATH__/search/sortweek desc/%09collection:TGODFloppyCollection|__SERVER__|__PORT__]
+[1|MS-DOS: The Frostbyte Shareware Collection|__CGIPATH__/search/sortweek desc/%09collection:softwarelibrary_msdos_frostbyte|__SERVER__|__PORT__]

ADDED   src/sort/index.dcgi.m4
Index: src/sort/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/sort/index.dcgi.m4
@@ -0,0 +1,61 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# sort/index.dcgi
+#
+# Change search sort order
+
+include(config.awk)
+undivert(cgi.awk)
+
+function main(     i, lbl, opt) {
+    lbl[1]  = "Default [^]"
+    opt[1]  = "nav_order asc"
+    lbl[2]  = "Default [v]"
+    opt[2]  = "nav_order desc"
+    lbl[3]  = "Weekly views [^]"
+    opt[3]  = "week asc"
+    lbl[4]  = "Weekly views [v]"
+    opt[4]  = "week desc"
+    lbl[5]  = "All-time views [^]"
+    opt[5]  = "downloads asc"
+    lbl[6]  = "All-time views [v]"
+    opt[6]  = "downloads desc"
+    lbl[7]  = "Title [^]"
+    opt[7]  = "title asc"
+    lbl[8]  = "Title [v]"
+    opt[8]  = "title desc"
+    lbl[9]  = "Date added [^]"
+    opt[9]  = "addeddate asc"
+    lbl[10] = "Date added [v]"
+    opt[10] = "addeddate desc"
+    lbl[11] = "Date created [^]"
+    opt[11] = "createddate asc"
+    lbl[12] = "Date created [v]"
+    opt[12] = "createddate desc"
+    lbl[13] = "Date published [^]"
+    opt[13] = "publicdate asc"
+    lbl[14] = "Date published [v]"
+    opt[14] = "publicdate desc"
+    lbl[15] = "Creator [^]"
+    opt[15] = "creator asc"
+    lbl[16] = "Creator [v]"
+    opt[16] = "creator desc"
+    lbl[17] = "Random"
+    opt[17] = "random asc"
+
+    print "# Sort by"
+    print ""
+    for (i = 1; i < 18; i++) {
+        printf "[1|%s|%s/search/sort%s%%09%s|%s|%s]\n",
+            lbl[i], cgipath, opt[i], search, server, port
+    }
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

ADDED   src/util.awk
Index: src/util.awk
==================================================================
--- /dev/null
+++ src/util.awk
@@ -0,0 +1,131 @@
+function detect_archive(url,     str) {
+    str = tolower(url)
+    if (str ~ /\.iso$/ ||
+        str ~ /\.zip$/)
+    {
+        retval = 1
+    } else {
+        retval = 0
+    }
+    return retval
+}
+
+function detect_html(url,     str) {
+    str = tolower(url)
+    if (str ~ /\.html?$/ ||
+        str ~ /\.php$/)
+    {
+        retval = 1
+    } else {
+        retval = 0
+    }
+    return retval
+}
+
+function detect_image(url,     str) {
+    str = tolower(url)
+    if (str ~ /\.bmp$/  ||
+        str ~ /\.gif$/  ||
+        str ~ /\.jpg$/  ||
+        str ~ /\.jpeg$/ ||
+        str ~ /\.png$/  ||
+        str ~ /\.webp$/)
+    {
+        retval = 1
+    } else {
+        retval = 0
+    }
+    return retval
+}
+
+function gettemp(     result) {
+    while ((cmd_mktemp | getline) > 0) {
+        retval = $0
+    }
+    result = close(cmd_mktemp)
+    if (result != 0) {
+        print "Error: mktemp failed exit status: " result
+        exit
+    }
+    if (length(retval) == 0) {
+        print "Error: mktemp failed, no tmpfile"
+        exit
+    }
+    return retval
+}
+
+function human_size(bytes) {
+    if (bytes > size_gb) {
+        retval = sprintf("%.1fG", bytes / size_gb)
+    } else if (bytes > size_mb) { 
+        retval = sprintf("%.1fM", bytes / size_mb)
+    } else if (bytes > size_kb) { 
+        retval = sprintf("%.1fK", bytes / size_kb)
+    } else {
+        retval = sprintf("%dB", bytes)
+    }
+    return retval
+}
+
+function print_not_found(url) {
+    print "Item cannot be found"
+    print ""
+    print "Items may be taken down for various reasons,"
+    print "including by decision of the uploader or"
+    print "due to a violation of the Terms of Use."
+    print ""
+    printf "[h|Metadata|URL:%s|%s|%s]\n", url, server, port
+    print ""
+    url = api_ssl_endpoint "/about/terms.php"
+    printf "[0|Terms of Use|%s/text/%%09%s|%s|%s]\n", cgipath,
+        url, server, port
+    print ""
+    printf "[1|PHAROS|%s|%s|%s]\n", cgipath, server, port
+    return
+}
+
+function shorten_left(str, len) {
+    if (length(str) > len) {
+        retval = "..." substr(str, 3 + length(str) - len)
+    } else {
+        retval = str
+    }
+    return retval
+}
+
+function shorten(str, len) {
+    if (length(str) > len) {
+        retval = substr(str, 0, len - 3) "..."
+    } else {
+        retval = str
+    }
+    return retval
+}
+
+function unlink(name) {
+    system(cmd_rm " " name)
+    return
+}
+
+function util_init() {
+    licenseurl[""] = "None"
+    licenseurl["https://creativecommons.org/publicdomain/zero/1.0/"] = \
+        "Public Domain"
+    licenseurl["https://creativecommons.org/licenses/by/4.0/"] = \
+        "Attribution 4.0 International"
+    licenseurl["https://creativecommons.org/licenses/by-sa/4.0/"] = \
+        "Attribution-ShareAlike 4.0 International"
+    licenseurl["https://creativecommons.org/licenses/by-nd/4.0/"] = \
+        "Attribution-NoDerivs 4.0 International"
+    licenseurl["https://creativecommons.org/licenses/by-nc/4.0/"] = \
+        "Attribution-NonCommercial 4.0 International"
+    licenseurl["https://creativecommons.org/licenses/by-nc-sa/4.0/"] = \
+        "Attribution-NonCommercial-ShareAlike 4.0 International"
+    licenseurl["https://creativecommons.org/licenses/by-nc-nd/4.0/"] = \
+        "Attribution-NonCommercial-NoDerivs 4.0 International"
+
+    size_kb = 1024
+    size_mb = 1024 * 1024
+    size_gb = 1024 * 1024 * 1024
+    return
+}

ADDED   src/video/index.gph.m4
Index: src/video/index.gph.m4
==================================================================
--- /dev/null
+++ src/video/index.gph.m4
@@ -0,0 +1,23 @@
+include(config.m4)dnl
+# Video
+
+[1|All Video|__CGIPATH__/search/sortweek desc/%09mediatype:movies|__SERVER__|__PORT__]
+[1|This Just In|__CGIPATH__/search/sortpublicdate desc/%09mediatype:movies|__SERVER__|__PORT__]
+[1|Prelinger Archives|__CGIPATH__/search/sortweek desc/%09collection:prelinger|__SERVER__|__PORT__]
+[1|Democracy Now!|__CGIPATH__/search/sortweek desc/%09collection:democracy_now_vid|__SERVER__|__PORT__]
+[1|Occupy Wall Street|__CGIPATH__/search/sortweek desc/%09collection:occupywallstreet|__SERVER__|__PORT__]
+[1|TV NSA Clip Library|__CGIPATH__/search/sortweek desc/%09collection:nsa|__SERVER__|__PORT__]
+[1|Animation And Cartoons|__CGIPATH__/search/sortweek desc/%09collection:animationandcartoons|__SERVER__|__PORT__]
+[1|Arts & Music|__CGIPATH__/search/sortweek desc/%09collection:artsandmusicvideos|__SERVER__|__PORT__]
+[1|Computers & Technology|__CGIPATH__/search/sortweek desc/%09collection:computersandtechvideos|__SERVER__|__PORT__]
+[1|Cultural & Academic Films|__CGIPATH__/search/sortweek desc/%09collection:culturalandacademicfilms|__SERVER__|__PORT__]
+[1|Ephemeral Films|__CGIPATH__/search/sortweek desc/%09collection:ephemera|__SERVER__|__PORT__]
+[1|Movies|__CGIPATH__/search/sortweek desc/%09collection:moviesandfilms|__SERVER__|__PORT__]
+[1|News & Public Affairs|__CGIPATH__/search/sortweek desc/%09collection:newsandpublicaffairs|__SERVER__|__PORT__]
+[1|Spirituality & Religion|__CGIPATH__/search/sortweek desc/%09collection:spiritualityandreligion|__SERVER__|__PORT__]
+[1|Sports|__CGIPATH__/search/sortweek desc/%09collection:sports|__SERVER__|__PORT__]
+[1|Television|__CGIPATH__/search/sortweek desc/%09collection:television AND mediatype:collection|__SERVER__|__PORT__]
+[1|VHS Vault|__CGIPATH__/search/sortweek desc/%09collection:vhsvault|__SERVER__|__PORT__]
+[1|Video Games|__CGIPATH__/search/sortweek desc/%09collection:gamevideos AND mediatype:collection|__SERVER__|__PORT__]
+[1|Vlogs|__CGIPATH__/search/sortweek desc/%09collection:vlogs|__SERVER__|__PORT__]
+[1|Youth Media|__CGIPATH__/search/sortweek desc/%09collection:youth_media|__SERVER__|__PORT__]

ADDED   src/web.awk
Index: src/web.awk
==================================================================
--- /dev/null
+++ src/web.awk
@@ -0,0 +1,159 @@
+function dump(search, type,     base, cmd, curlcfg, is_html, is_image, \
+    label, limit, link, marker, parts, prefix, proto, relative, root,  \
+    url)
+{
+    url = search
+    gsub(/%3F/, "?", url)
+
+    if (url !~ /^(http|https):\/\/[[:alnum:].-]+(:[0-9]+)*(\/[[:alnum:].,?@~=%%:\/+&_() -]*)*$/) {
+        printf "Error: Unacceptable URL \"%s\"\r\n", url
+        return
+    }
+
+    if (type == TYPE_HEADERS || type == TYPE_RAW) {
+        limit = max_bin_size
+    } else {
+        limit = max_txt_size
+    }
+
+    # Use temporary file for curl configuration.
+    # This keeps user input separate from shell execution.
+
+    curlcfg = gettemp()
+    printf "--connect-timeout 10\n" > curlcfg
+    printf "--max-filesize %dM\n", limit >> curlcfg
+    printf "--max-redirs 0\n" >> curlcfg
+    printf "--proto =http,https\n" >> curlcfg
+    printf "--show-error\n" >> curlcfg
+    printf "--silent\n" >> curlcfg
+    printf "--url %s\n", uri_encode(url) >> curlcfg
+    printf "--user-agent %s\n", agent >> curlcfg
+    if (type == TYPE_HEADERS) {
+        printf "--output /dev/null\n" >> curlcfg
+        printf "--dump-header -\n" >> curlcfg
+    }
+    close(curlcfg)
+
+    if (type == TYPE_HEADERS || type == TYPE_RAW) {
+        cmd = sprintf("%s -K %s 2>&1", cmd_curl, curlcfg)
+        system(cmd)
+        unlink(curlcfg)
+        return
+    }
+
+    # Use strings command to guard webdump from binary input.
+    # Use "strings -a" to avoid security pitfalls.
+
+    cmd = sprintf("%s -K %s 2>&1 | %s -a -n 3 | %s -ilr -w 60", \
+        cmd_curl, curlcfg, cmd_strings, cmd_webdump)
+
+    # Parse base out of original URL.
+    # Use this to convert relative links to full URLs.
+    # webdump has the -b option for this.
+    # Do it manually instead to avoid passing user input through the shell.
+
+    split(url, parts, "?")
+    base = parts[1]
+    if (match(base, /^(http|https):\/\/[[:alnum:].-]+(:[0-9]+)*/)) {
+        root = substr(base, 0, RLENGTH)
+    } else {
+        root = ""
+    }
+    if (match(base, /^(http|https):/)) {
+        proto = substr(base, 0, RLENGTH)
+    } else {
+        proto = ""
+    }
+    sub(/\/$/, "", base)
+
+    # marker determines where the bottom references section begins
+    # line numbers smaller than the marker are content
+    # line numbers larger than the marker are referenced links
+
+    marker = 999999
+
+    if (type == TYPE_LINKS) {
+        is_html = detect_html(url)
+        is_image = detect_image(url)
+        printf "[9|Binary download|%s/raw/%%09%s|%s|%s]\n", cgipath,
+            search, server, port
+        if (is_image) {
+            printf "[I|Image view|%s/raw/%%09%s|%s|%s]\n", cgipath,
+                search, server, port
+        }
+        if (is_html) {
+            label = "Source"
+        } else {
+            label = "Text view"
+        }
+        printf "[0|%s|%s/raw/%%09%s|%s|%s]\n", label, cgipath, search,
+            server, port
+        if (is_html) {
+            label = "HTML view"
+        } else {
+            label = "Strings"
+        }
+        printf "[0|%s|%s/text/%%09%s|%s|%s]\n", label, cgipath, search,
+            server, port
+        printf "[0|Headers|%s/debug/%%09%s|%s|%s]\n", cgipath, search,
+            server, port
+        print ""
+    }
+
+    while ((cmd | getline) > 0) {
+        if (NR < marker) {
+            if ($0 ~ /^References$/) {
+                marker = NR
+            }
+            if (type == TYPE_TEXT) {
+                print $0
+            }
+        } else {
+            if (match($0, /^ [0-9]+\. /)) {
+                prefix = substr($0, 0, RLENGTH)
+                link = substr($0, RLENGTH+1)
+                if (link !~ /^[a-z]+:/) {
+                    # convert relative link to full URL
+                    relative = link
+                    if (relative ~ /^\/\//) {
+                        link = proto relative
+                    } else if (relative ~ /^\//) {
+                        link = root relative
+                    } else {
+                        link = base "/" relative
+                    }
+               }
+               print prefix link
+            } else {
+               print $0
+            }
+        }
+    }
+    close(cmd)
+
+    unlink(curlcfg)
+
+    return
+}
+
+function print_html(html,    cmd, work) {
+    work = gettemp()
+    gsub(/\\n/, "<br>", html)
+    print html >work
+    close(work)
+    cmd = sprintf("%s -a -n 3 <%s | %s -ilr -w 60", cmd_strings, work, \
+        cmd_webdump)
+    while ((cmd | getline) > 0) {
+        print
+    }
+    close(cmd)
+    unlink(work)
+    return
+}
+
+function web_init() {
+    TYPE_HEADERS = 2
+    TYPE_LINKS = 1
+    TYPE_RAW = 9
+    TYPE_TEXT = 0
+}

ADDED   src/wizard/step1/index.dcgi.m4
Index: src/wizard/step1/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/wizard/step1/index.dcgi.m4
@@ -0,0 +1,67 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# wizard/step1/index.dcgi
+#
+# Select field to filter/search by
+
+include(config.awk)
+undivert(cgi.awk)
+
+function main(     i, lbl, opt, searchstr) {
+    searchstr = parts[4]
+
+    lbl[1]  = "Any field contains"
+    opt[1]  = "anyfield"
+    lbl[2]  = "Any field does not contain"
+    opt[2]  = "-anyfield"
+    lbl[3]  = "Title contains"
+    opt[3]  = "title"
+    lbl[4]  = "Title does not contain"
+    opt[4]  = "-title"
+    lbl[5]  = "Creator contains"
+    opt[5]  = "creator"
+    lbl[6]  = "Creator does not contain"
+    opt[6]  = "-creator"
+    lbl[7]  = "Description contains"
+    opt[7]  = "description"
+    lbl[8]  = "Description does not contain"
+    opt[8]  = "-description"
+    lbl[9]  = "Collection is"
+    opt[9]  = "collection"
+    lbl[10] = "Collection does not contain"
+    opt[10] = "-collection"
+    lbl[11] = "Mediatype is"
+    opt[11] = "mediatype"
+    lbl[12] = "Mediatype does not contain"
+    opt[12] = "-mediatype"
+    lbl[13] = "Date or date range is"
+    opt[13] = "date"
+    lbl[14] = "Language contains"
+    opt[14] = "language"
+    lbl[15] = "Always available"
+    opt[15] = "-access-restricted-item"
+
+    print "# Search wizard: Select field"
+    print ""
+    for (i = 1; i < 16; i++) {
+        if (opt[i] ~ /mediatype$/) {
+            printf "[1|%s|%s/wizard/step2/%s/%s|%s|%s]\n", lbl[i],
+                cgipath, opt[i], searchstr, server, port
+        } else if (lbl[i] == "Always available") {
+            printf "[1|%s|%s/wizard/step3/%s/%s%%09true|%s|%s]\n",
+                lbl[i], cgipath, opt[i], searchstr, server, port
+        } else {
+            printf "[7|%s|%s/wizard/step3/%s/%s|%s|%s]\n", lbl[i],
+                cgipath, opt[i], searchstr, server, port
+        }
+    }
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

ADDED   src/wizard/step2/index.dcgi.m4
Index: src/wizard/step2/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/wizard/step2/index.dcgi.m4
@@ -0,0 +1,51 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# wizard/step2/index.dcgi
+#
+# Select mediatype to filter/search by
+
+include(config.awk)
+undivert(cgi.awk)
+
+function main(     field, newsearch, searchstr) {
+    field = parts[4]
+    searchstr = parts[5]
+
+    if (field == "mediatype") {
+        print "# Mediatype is:"
+    } else {
+        print "# Mediatype does not contain:"
+    }
+    print ""
+
+    lbl[1] = "audio"
+    lbl[2] = "collection"
+    lbl[3] = "data"
+    lbl[4] = "etree"
+    lbl[5] = "image"
+    lbl[6] = "movies"
+    lbl[7] = "software"
+    lbl[8] = "texts"
+    lbl[9] = "web"
+    for (i = 1; i < 10; i++) {
+        if (length(searchstr) == 0) {
+            newsearch = sprintf("%s:(%s)", field, lbl[i])
+        } else {
+            newsearch = sprintf("%s AND %s:(%s)", searchstr, field, lbl[i])
+        }
+        printf "[1|%s|%s/search/%%09%s|%s|%s]\n", lbl[i], cgipath,
+            newsearch, server, port
+    }
+    print "# Progress:"
+    print ""
+    print "* Field: Mediatype"
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

ADDED   src/wizard/step3/index.dcgi.m4
Index: src/wizard/step3/index.dcgi.m4
==================================================================
--- /dev/null
+++ src/wizard/step3/index.dcgi.m4
@@ -0,0 +1,44 @@
+include(config.m4)dnl
+#!__CMD_AWK__ -f
+
+# wizard/step3/index.dcgi
+#
+# Apply new search terms
+
+include(config.awk)
+undivert(cgi.awk)
+
+function main(     field, label, newsearch, op, searchstr, value) {
+    field = parts[4]
+    searchstr = parts[5]
+    value = search
+
+    if (field ~ /^-/) {
+        label = toupper(substr(field, 2, 1)) substr(field, 3)
+        op = "does not contain"
+    } else {
+        label = toupper(substr(field, 1, 1)) substr(field, 2)
+        op = "contains"
+    }
+    if (length(searchstr) == 0) {
+        newsearch = sprintf("%s:(%s)", field, value)
+    } else {
+        newsearch = sprintf("%s AND %s:(%s)", searchstr, field, value)
+    }
+    print ""
+    printf "[1|Apply search criteria|%s/search/%%09%s|%s|%s]\n",
+        cgipath, newsearch, server, port
+    print ""
+    print "# Progress:"
+    print ""
+    printf "* Field %s %s %s\n", label, op, value
+    printf "* New search: %s\n", newsearch
+    exit 0
+}
+
+BEGIN {
+    config_init()
+
+    cgi_init()
+    main()
+}

]]></content>
<updated>2024-08-04 22:03:15</updated>
</entry>
<entry>
<id>gopher://tilde.pink/1/~bencollver/fossil/pharos/info/777ee78a5b/</id>
<title>initial empty check-in</title>
<author><name>bencollver</name></author>
<content type="text"><![CDATA[
]]></content>
<updated>2024-08-04 21:43:36</updated>
</entry>
</feed>
