From fed0fb025a66ac2bc91123a22637d757fe610ac9 Mon Sep 17 00:00:00 2001 From: Stanley Lieber Date: Fri, 29 Jan 2021 21:46:41 -0500 Subject: [PATCH 01/10] apps/blagh/rss20.tpl: print correct for each (thanks, phil9) --- apps/blagh/rss20.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/blagh/rss20.tpl b/apps/blagh/rss20.tpl index 0cba818..ab2426f 100644 --- a/apps/blagh/rss20.tpl +++ b/apps/blagh/rss20.tpl @@ -25,10 +25,10 @@ fn statpost { # rfc2822 last time channel content changed. lbd=`{ndate -m `{date `{mtime `{ls $blagh_root$blagh_dirs/[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]/[0-9] | tail -1} | awk '{print $1}'}}} echo ''$"lbd'' - # rfc2822 publication date for content in the channel. - pubdate=`{ndate -m} for(f in `{get_post_list $blagh_root$blagh_dirs}){ statpost $f + # rfc2822 publication date for this post. + pubdate=`{ndate -m `{date `{mtime $f | awk '{print $1}'}}} %} <![CDATA[%($title%)]]> From 4e454ff73b93ca50fcc0905e6a73213587e54fa3 Mon Sep 17 00:00:00 2001 From: Stanley Lieber Date: Thu, 9 Sep 2021 12:59:15 -0400 Subject: [PATCH 02/10] sites/werc.cat-v.org/index.md: freenode -> oftc (thanks, hiro) --- sites/werc.cat-v.org/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/werc.cat-v.org/index.md b/sites/werc.cat-v.org/index.md index 0fa90f5..3d2ba76 100644 --- a/sites/werc.cat-v.org/index.md +++ b/sites/werc.cat-v.org/index.md @@ -65,7 +65,7 @@ the werc mailing list. To join, send a message with a body consisting only of th To track commit messages, you can join the werc-commits mailing list. To join, send a message with a body consisting only of the word _subscribe_ to werc-commits-owner@cat-v.org. -On irc, join [#cat-v](irc://irc.freenode.org/cat-v) on irc.freenode.org +On irc, join [#cat-v](irc://irc.oftc.org/cat-v) on irc.oftc.org License From 3a90e2da7856e6ce3b4b4bc0bcacfaaaa66b6753 Mon Sep 17 00:00:00 2001 From: Stanley Lieber Date: Sun, 21 Nov 2021 18:49:58 -0500 Subject: [PATCH 03/10] bin/contrib/rc-httpd{rc-httpd, handlers/error}: do some minimal sanitization on $SERVER_NAME before handing it off to select-handler. this prevents malformed Host: headers from retrieving arbitrary files from the file system. (thanks, Lightning) --- bin/contrib/rc-httpd/rc-httpd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/contrib/rc-httpd/rc-httpd b/bin/contrib/rc-httpd/rc-httpd index 8e4fad9..864a8d0 100755 --- a/bin/contrib/rc-httpd/rc-httpd +++ b/bin/contrib/rc-httpd/rc-httpd @@ -86,6 +86,11 @@ if(~ $#SERVER_NAME 2){ SERVER_PORT=$SERVER_NAME(2) SERVER_NAME=$SERVER_NAME(1) } +switch($SERVER_NAME){ + case */* .. + error 400 + exit +} if(~ $REQUEST_METHOD (PUT POST)){ if(! ~ $"CONTENT_LENGTH '') trim_input | exec $rc_httpd_dir/select-handler From 1a0337f6847239a139be6ff097804425599439cd Mon Sep 17 00:00:00 2001 From: Stanley Lieber Date: Mon, 22 Nov 2021 00:45:43 -0500 Subject: [PATCH 04/10] bin/contrib/rc-httpd/handlers/error: add missing error 400 --- bin/contrib/rc-httpd/handlers/error | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/contrib/rc-httpd/handlers/error b/bin/contrib/rc-httpd/handlers/error index 282d870..fa594a9 100755 --- a/bin/contrib/rc-httpd/handlers/error +++ b/bin/contrib/rc-httpd/handlers/error @@ -3,7 +3,7 @@ fn do_error{ echo 'HTTP/1.1 '^$1^$cr emit_extra_headers - echo 'Content-type: text/html'^$cr + echo 'Content-type: text/html; charset=utf-8'^$cr echo $cr echo ' @@ -19,6 +19,11 @@ fn do_error{ ' } +fn 400{ + do_error '400 Bad Request' \ + 'The request was invalid.' +} + fn 401{ do_error '401 Unauthorized' \ 'The requested path '^$"location^' requires authorization.' From bc939071cf26b9ea744829df290b26951a14f33d Mon Sep 17 00:00:00 2001 From: Stanley Lieber Date: Fri, 20 Sep 2024 20:17:26 -0400 Subject: [PATCH 05/10] apps/wman/search.tpl: we already filter user input. avoid xss by printing filtered user input instead of unfiltered user input on error. --- apps/wman/search.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wman/search.tpl b/apps/wman/search.tpl index a6c59e4..ed57743 100755 --- a/apps/wman/search.tpl +++ b/apps/wman/search.tpl @@ -6,7 +6,7 @@ % if(! ~ $"post_arg_wman_search '') { % if(~ $"wman_search_results '') { - No matches found for '%($post_arg_wman_search%)'. + No matches found for '%($s%)'. % } % if not {
    From 51e19b2266bb31b76cacdbfeafa58b28a3ef8b70 Mon Sep 17 00:00:00 2001 From: sl Date: Sat, 14 Jun 2025 23:46:57 +0000 Subject: [PATCH 06/10] bin/{corehandlers.rc, werc.rc, werclib.rc}: handle .gmi and .gem --- bin/werc.rc | 2 +- bin/werclib.rc | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/werc.rc b/bin/werc.rc index 0d006a3..101834e 100755 --- a/bin/werc.rc +++ b/bin/werc.rc @@ -12,7 +12,7 @@ difs=$ifs # Used to restore default ifs when needed # Expected input: ls -F style, $sitedir/path/to/files/ # dirfilter='s/\*$//; s,/+\./+,/,g; s,^\./,,; /\/[._][^\/]/d; /'$forbidden_uri_chars'/d; /\/sitemap\.xml$/d; /\/index\.(md|html|txt|tpl)$/d; /\/(robots|sitemap)\.txt$/d; /_werc\/?$/d; ' -dirclean=' s/\.(md|html|txt)$//; ' +dirclean=' s/\.(gmi|gem|md|html|txt)$//; ' # Careful, the proper p9p path might not be set until initrc.local is sourced path=(. /bin ./bin) diff --git a/bin/werclib.rc b/bin/werclib.rc index bcebf91..0e095fc 100755 --- a/bin/werclib.rc +++ b/bin/werclib.rc @@ -97,6 +97,10 @@ fn get_md_title { sed -n -e '1N; /^.*\n===*$/N; /.*\n===*\n *$/!b' -e 's/\n==*\n//p' < $1 } +fn get_gmi_title { + sed -e 's/^# //;q' < $1 +} + fn get_html_title { t=`{sed -n '32q; s/^.*<[Tt][Ii][Tt][Ll][Ee]> *([^<]+) *(<\/[Tt][Ii][Tt][Ll][Ee]>.*)?$/\1/p' < $1} @@ -110,6 +114,10 @@ fn get_html_title { fn get_file_title { if (~ $1 *.md) get_md_title $1 + if not if(~ $1 *.gmi) + get_gmi_title $1 + if not if(~ $1 *.gem) + get_gmi_title $1 if not if(~ $1 *.html) get_html_title $1 if not if(~ $1 */) { From 71619f80dc63e859203ae76af002cfea65016200 Mon Sep 17 00:00:00 2001 From: sl Date: Sat, 14 Jun 2025 23:48:50 +0000 Subject: [PATCH 07/10] add apps/mdir --- apps/mdir/TODO | 10 +++ apps/mdir/app.rc | 178 +++++++++++++++++++++++++++++++++++++++++++ apps/mdir/readme.txt | 11 +++ 3 files changed, 199 insertions(+) create mode 100644 apps/mdir/TODO create mode 100755 apps/mdir/app.rc create mode 100644 apps/mdir/readme.txt diff --git a/apps/mdir/TODO b/apps/mdir/TODO new file mode 100644 index 0000000..d1b1f3b --- /dev/null +++ b/apps/mdir/TODO @@ -0,0 +1,10 @@ +* unfuck quoted-printable crap +* more robust plaintext extraction from mime +* more efficient month sort in the year view + * I think sort(1) might have a bug with -M and pos1 + * or stupid awk can just produce an ordered list +* a lot of speedup + * this might mean an external index + * or it might just mean aggressive in-app cache +* do we want threading in the month views +* decode non-ascii header values (From: especially) diff --git a/apps/mdir/app.rc b/apps/mdir/app.rc new file mode 100755 index 0000000..b9f05c9 --- /dev/null +++ b/apps/mdir/app.rc @@ -0,0 +1,178 @@ +fn conf_enable_mdir { + mdir=`{pwd} + listbase=$conf_wd + listname=`{basename `{ basename -d $listbase}} + conf_enable_app mdir + dirfilter=$dirfilter' /mbox\/?$/d;' +} + + +fn mdir_init { + showmonth=`{echo $req_path | sed 's/.*[0-9][0-9][0-9][0-9]\/([A-Z][a-z][^\/]+).*/\1/'} + showyear=`{echo $req_path | sed 's/.*\/([0-9][0-9][0-9][0-9])\/.*/\1/'} + handler_body_main='mdir_index' + + if (~ $req_path $listbase[0-9][0-9][0-9][0-9]/[A-Z][a-z]*/[0-9]*) { + handler_body_main='message_display' + } + if not if (~ $req_path $listbase[0-9][0-9][0-9][0-9]/[A-Z][a-z]*) { + handler_body_main='month_display' + } + if not if(~ $req_path $listbase[0-9][0-9][0-9][0-9]*) { + handler_body_main='year_display' + } + + +} + +fn message_display { + message=`{basename $req_path} + echo '

    '$listname' - '$showyear' - ' + echo ''$showmonth' -' + echo 'this message ' + approx=`{echo $"message | sed 's/(...).*/\1/' } + neighbors=`{ls -p $mdir/mbox/$approx^*} + prevmsg=`{echo $neighbors | tr '\012' ' ' | sed -n 's/.* ([^ ]+) '$message'.*/\1/p'} + nextmsg=`{echo $neighbors | tr '\012' ' ' | sed -n 's/.*'$message' ([^ ]+).*/\1/p'} + echo '

    ' + if (~ $#prevmsg 1 || ~ $#nextmsg 1) { echo '[ ' } + if (~ $#prevmsg 1) { echo 'previous' } + if (~ $#prevmsg 1 && ~ $#nextmsg 1) { echo ' , ' } + if (~ $#nextmsg 1) { echo 'next' } + if (~ $#prevmsg 1 || ~ $#nextmsg 1) { echo ' ]' } + echo '

    ' + awk ' + /^Subject:/ && (headersdone == 0) { subject=$0 } + /^From:/ && (headersdone == 0) { from=$0; gsub(//, "\\>", from); } + /^Date:/ && (headersdone == 0) { date=$0 } + /^$/ && (headersdone==0) { print "

    " subject "

    "; print from; print date; headersdone=1 }
    +  (headersdone == 1) { exit }
    +  ' $mdir/mbox/$message | sed 's/([^ @.]+)\@[^ @]+\.[^@ $][a-zA-Z]+/\1@[REDACTED]/g;' 
    +
    +	cat $mdir/mbox/$message | {
    +		boundary=undef;
    +		while (line=`{read}) {
    +			if (~ $line Content-Type*multipart*) { # flag mimeshit boundary
    +										multipart=1
    +				boundary=`{echo $line | sed 's/^.*boundary=//; s/[  ].*$//'}
    +										if (test -z $boundary) { boundary=undef; } # fucking header wrap
    +			}
    +						if (~ $multipart 1) {
    +										if (~ $boundary undef) { # desperately try to identify a boundary
    +					boundary=`{echo $line | sed 's/^.*boundary=//; s/[        ].*$//'}
    +			 }
    +			}
    +
    +			if (~ $"line '') { # note end of header section
    +				endheaders = 1;
    +			}
    +
    +			if (~ $boundary undef ) { # no boundary, headers over:  print message
    +				if (~ $endheaders 1) {
    +					echo $line
    +				}
    +			}
    +			if not { # boundaries: print only text/plain
    +				if (~ $line --$boundary) { # boundary found, toggle state
    +					if (~ $inboundary 1) { 
    +						inboundary=0
    +					}
    +					if not {
    +						inboundary=1
    +					}
    +				}
    +				if (~ $inboundary 1) { # we're in a text/plain part, print it
    +					if (~ $textpart 1) {
    +						echo $line
    +					}
    +				}
    +				if (~ $line Content-Type*text*plain*) { # note advent of text/plain part
    +					if (~ $inboundary 1) {
    +						echo found text part
    +						textpart=1
    +					}
    +				}
    +			}
    +		}
    +	}
    +
    +  echo '
    ' +} + +fn mdir_index { + echo '

    ' $listname '

    ' + echo '
      ' + ls -p $mdir/mbox | awk ' { year = int($0 / 31556926); array[year+1970]+=1; } + END { for (i in array) { print "
    • " i " (" array[i] " messages)
    • " | "sort"} }' + echo '
    ' +} + +fn year_display { +echo '

    '$listname' - '$showyear'

    ' +echo '
      ' +ls -p $mdir/mbox | awk 'BEGIN { jan = (ENVIRON["showyear"] - 1970) * 31556926 + feb = jan + 2629743; mar = feb + 2629743; apr = mar + 2629743; may = apr + 2629743; + jun = may + 2629743; jul = jun + 2629743; aug = jul + 2629743; sep = aug + 2629743; + oct = sep + 2629743; nov = oct + 2629743; dec = nov + 2629743; end = dec + 2629743 } + ($0 > jan) && ($0 < feb) { month["January"] += 1 } + ($0 > feb) && ($0 < mar) { month["February"] += 1 } + ($0 > mar) && ($0 < apr) { month["March"] += 1 } + ($0 > apr) && ($0 < may) { month["April"] += 1 } + ($0 > may) && ($0 < jun) { month["May"] += 1 } + ($0 > jun) && ($0 < jul) { month["June"] += 1 } + ($0 > jul) && ($0 < aug) { month["July"] += 1 } + ($0 > aug) && ($0 < sep) { month["August"] += 1 } + ($0 > sep) && ($0 < oct) { month["September"] += 1 } + ($0 > oct) && ($0 < nov) { month["October"] += 1 } + ($0 > nov) && ($0 < dec) { month["November"] += 1 } + ($0 > dec) && ($0 < end) { month["December"] += 1 } + END { for (i in month) { + print i "
    • " i " (" month[i] " messages)
    • " + } } + '| sort -M | sed 's/^[A-Za-z]+
    • /
    • /' + +echo '
    ' +} + +fn month_display { +echo '

    '$listname' - '$showyear' - '$showmonth'

    ' +echo '
      ' +ls -p $mdir/mbox | awk 'BEGIN { monthstamp = (ENVIRON["showyear"] - 1970) * 31556926 + month = ENVIRON["showmonth"] + year = ENVIRON["showyear"] + listbase = ENVIRON["listbase"] + msg = ENVIRON["msg"] + if (month == "February") { monthstamp += 2629743 } + if (month == "March") { monthstamp += (2629743 * 2) } + if (month == "April") { monthstamp += (2629743 * 3) } + if (month == "May") { monthstamp += (2629743 * 4) } + if (month == "June") { monthstamp += (2629743 * 5) } + if (month == "July") { monthstamp += (2629743 * 6) } + if (month == "August") { monthstamp += (2629743 * 7) } + if (month == "September"){ monthstamp += (2629743 * 8) } + if (month == "October") { monthstamp += (2629743 * 9) } + if (month == "November") { monthstamp += (2629743 * 10) } + if (month == "December") { monthstamp += (2629743 * 11) } + monthend = monthstamp + 2629743 + } + ($0 > monthstamp) && ($0 < monthend) { print $0 } + ' | sort -u | while (msg=`{read}) { + awk 'BEGIN { month = ENVIRON["showmonth"]; year = ENVIRON["showyear"]; + listbase = ENVIRON["listbase"]; msg = ENVIRON["msg"] } + /^Subject:[ ]/ { subject = $0; sub(/^Subject: /, "", subject) } + /^From:[ ]/ { gsub(/^From:[ ]+/, "", $0); + gsub(/ <.*$/, "", $0); + gsub(/[<>]/, "", $0); + gsub(/^.*[(]/, "", $0); + gsub(/[)].*$/, "", $0); + gsub(/["]/, "", $0); + gsub(/@[^ ]+/, "", $0); + from=$0; } + /^$/ { nextfile; } + END { if (subject == "") { subject = "[no subject]" } + print "
    • "; + print subject " - " from "
    • " }' $mdir/mbox/$msg + } + +echo '
    ' +} diff --git a/apps/mdir/readme.txt b/apps/mdir/readme.txt new file mode 100644 index 0000000..25ab886 --- /dev/null +++ b/apps/mdir/readme.txt @@ -0,0 +1,11 @@ +INSTALL: + +put this folder in e.g. /werc/apps/mdir + +put your mdir in e.g. /werc/sites/example.com/mail/ +(such that all the messages land in /werc/sites/example.com/mail/mbox/) + +mkdir /werc/sites/example.com/mail/_werc +echo "conf_enable_mdir" > /werc/sites/example.com/mail/_werc/config + +good luck. From 6ba7fe759383efe561e0ef8767f302d6e3c12503 Mon Sep 17 00:00:00 2001 From: sl Date: Sun, 15 Jun 2025 00:24:20 +0000 Subject: [PATCH 08/10] remove .hgignore --- .hgignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .hgignore diff --git a/.hgignore b/.hgignore deleted file mode 100644 index f9cadd9..0000000 --- a/.hgignore +++ /dev/null @@ -1,3 +0,0 @@ -syntax: glob -etc/initrc.local -sites/?*/ From 63085c71ab79cb4defc3ba64b5285e7db86f0d07 Mon Sep 17 00:00:00 2001 From: sl Date: Sun, 15 Jun 2025 00:40:00 +0000 Subject: [PATCH 09/10] sites/werc.cat-v.org/development/index.md: hg -> git --- sites/werc.cat-v.org/development/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sites/werc.cat-v.org/development/index.md b/sites/werc.cat-v.org/development/index.md index 2e71b3d..f9ae943 100644 --- a/sites/werc.cat-v.org/development/index.md +++ b/sites/werc.cat-v.org/development/index.md @@ -1,7 +1,7 @@ Werc Development ================ -The latest dev code is available in the werc mercurial repo: +The latest dev code is available in the werc git repo: `git://shithub.us/sl/werc` Version Numbering and Branching Rules @@ -11,7 +11,7 @@ There is no such thing! Originally a convention similar to that of the Linux ker For radical or experimental changes the `werc-dev` branch might be used, but at the moment it is outdated. -Bug reports, feature requests, bug fixes and other patches are all very welcome, just send them to the [werc mailing list](http://lists.9front.org). +Bug reports, feature requests, bug fixes and other patches are all very welcome, just send them to the [werc mailing list](http://lists.cat-v.org). See Also From e3e07ddd95c3015262397831a4fed6f9be708e41 Mon Sep 17 00:00:00 2001 From: sl Date: Fri, 22 Aug 2025 01:50:41 +0000 Subject: [PATCH 10/10] initial import --- sites/plan9.stanleylieber.com/mother/README | 65 +++ .../plan9.stanleylieber.com/mother/facemother | 19 + .../mother/img/ello.co.png | Bin 0 -> 7942 bytes sites/plan9.stanleylieber.com/mother/index.md | 65 +++ sites/plan9.stanleylieber.com/mother/mkfile | 9 + sites/plan9.stanleylieber.com/mother/mother | 504 ++++++++++++++++++ 6 files changed, 662 insertions(+) create mode 100644 sites/plan9.stanleylieber.com/mother/README create mode 100755 sites/plan9.stanleylieber.com/mother/facemother create mode 100644 sites/plan9.stanleylieber.com/mother/img/ello.co.png create mode 100644 sites/plan9.stanleylieber.com/mother/index.md create mode 100755 sites/plan9.stanleylieber.com/mother/mkfile create mode 100755 sites/plan9.stanleylieber.com/mother/mother diff --git a/sites/plan9.stanleylieber.com/mother/README b/sites/plan9.stanleylieber.com/mother/README new file mode 100644 index 0000000..2dc70ad --- /dev/null +++ b/sites/plan9.stanleylieber.com/mother/README @@ -0,0 +1,65 @@ +mother +====== + +Mother is an [rc(1)](http://man.9front.org/1/rc) script that provides +an experience similar to +[nedmail(1)](http://man.9front.org/1/nedmail). + +Download it [here](http://only9fans.com/sl/mother/HEAD/info.html). + +help +---- + + usage: mother [ -d ] [ -f mbox ] [ -p msg ] [ -r ] + + Commands are of the form [] [args] + := | , + := + a reply to sender and recipients + b print the next ten headers + d mark for deletion + e ... enter message (args passed to upas/marshal) + g/regexp/cmd grep headlines for regexp and run cmd on matches + h print message headline (,h for all) + help print this help message + m ... forward mail to address(es) + mb ... change to specified mailbox + p print the processed message + P print the raw message + q quit + r reply to message + s ... store message in specified mailbox + u remove deletion mark + y synchronize with mail box + " print message in quoted form, suitable for reply + |cmd pipe the processed message to a command + ||cmd pipe the raw message to a command + !cmd run a command + +extensions +---------- + +A script may be used in conjunction with +[faces(1)](http://man.9front.org/1/faces) and the +[plumber(4)](http://man.9front.org/4/plumber) to open individual +messages (or the entire mailbox) by clicking mb1 on a face in the +[faces(1)](http://man.9front.org/1/faces) window. Each click produces +a new window containing the selected message. + +The script: +[facemother](https://plan9.stanleylieber.com/rc/facemother) <- read +and modify as needed before using + +The plumber rule: + + # faces -> new mail window for message + type is text + data matches '[a-zA-Z¡-￿0-9_\-./]+' + data matches '/mail/fs/[a-zA-Z¡-￿0-9/]+/[0-9]+' + plumb to showmail + plumb start window -r 646 117 1366 676 facemother $0 + +screenshots +----------- + +![ello.co](img/ello.co.png) diff --git a/sites/plan9.stanleylieber.com/mother/facemother b/sites/plan9.stanleylieber.com/mother/facemother new file mode 100755 index 0000000..facd29f --- /dev/null +++ b/sites/plan9.stanleylieber.com/mother/facemother @@ -0,0 +1,19 @@ +#!/bin/rc +# 2018-02-15T21:12:06-0500 +# helper program for launching http://plan9.stanleylieber.com/mother from faces(1). +rfork en + +# plumber rules for faces(1) +#type is text +#data matches '[a-zA-Z¡-￿0-9_\-./]+' +#data matches '/mail/fs/[a-zA-Z¡-￿0-9/]+/[0-9]+' +#plumb to showmail +#plumb start window -r 583 206 1303 1200 facemother $0 + +# open and print only the indicated message +msg=`{sed -n 19p $1/info} +upas/fs -f /mail/box/sl/mbox/$msg +mother -p 1 + +# use existing mailbox, print the indicated message +#mother -p `{basename $1} diff --git a/sites/plan9.stanleylieber.com/mother/img/ello.co.png b/sites/plan9.stanleylieber.com/mother/img/ello.co.png new file mode 100644 index 0000000000000000000000000000000000000000..93ce22775d4d349370c4c44cd72085e8f1341026 GIT binary patch literal 7942 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] [args] + := | , + := + a reply to sender and recipients + b print the next ten headers + d mark for deletion + e ... enter message (args passed to upas/marshal) + g/regexp/cmd grep headlines for regexp and run cmd on matches + h print message headline (,h for all) + help print this help message + m ... forward mail to address(es) + mb ... change to specified mailbox + p print the processed message + P print the raw message + q quit + r reply to message + s ... store message in specified mailbox + u remove deletion mark + y synchronize with mail box + " print message in quoted form, suitable for reply + |cmd pipe the processed message to a command + ||cmd pipe the raw message to a command + !cmd run a command + +extensions +---------- + +A script may be used in conjunction with +[faces(1)](http://man.9front.org/1/faces) and the +[plumber(4)](http://man.9front.org/4/plumber) to open individual +messages (or the entire mailbox) by clicking mb1 on a face in the +[faces(1)](http://man.9front.org/1/faces) window. Each click produces +a new window containing the selected message. + +The script: +[facemother](https://plan9.stanleylieber.com/rc/facemother) <- read +and modify as needed before using + +The plumber rule: + + # faces -> new mail window for message + type is text + data matches '[a-zA-Z¡-￿0-9_\-./]+' + data matches '/mail/fs/[a-zA-Z¡-￿0-9/]+/[0-9]+' + plumb to showmail + plumb start window -r 646 117 1366 676 facemother $0 + +screenshots +----------- + +![ello.co](img/ello.co.png) diff --git a/sites/plan9.stanleylieber.com/mother/mkfile b/sites/plan9.stanleylieber.com/mother/mkfile new file mode 100755 index 0000000..e72fed0 --- /dev/null +++ b/sites/plan9.stanleylieber.com/mother/mkfile @@ -0,0 +1,9 @@ +web:V: + cp README index.md + if(test -f mother.tgz) + rm mother.tgz + cd .. + tar zcvf /tmp/mother.tgz mother + cd mother + cp /tmp/mother.tgz ../src/ + rm /tmp/mother.tgz diff --git a/sites/plan9.stanleylieber.com/mother/mother b/sites/plan9.stanleylieber.com/mother/mother new file mode 100755 index 0000000..b700e0c --- /dev/null +++ b/sites/plan9.stanleylieber.com/mother/mother @@ -0,0 +1,504 @@ +#!/bin/rc +# 2022-11-16T18:11:20-05:00 +# Mother wants to talk to you. +# Similar to nedmail. Use with 9front or nupas/fs (creates mdir format files on save). +# BONUS: Helper program for use with faces(1): http://plan9.stanleylieber.com/mother/facemother +rfork en +ramfs -p +argv0=$0 +if(~ $#editor 0) + editor=hold +if(~ $#pager 0) + pager=cat +mb=mbox +msg=() +sort=-r +fn d{ + if(test $1 -le $#rposts && test -d $rposts($1)){ + flag +D $1 && + dposts=($dposts $1) || + echo !delete $1 failed + } + if not + echo !address +} +fn deldposts{ + + + ndel=() + for(i in $dposts){ + if(test $i -le $#rposts && test -d $rposts($i)){ + echo delete $mb $rposts($i) >/mail/fs/ctl && + echo !deleted $i && + ndel=($ndel $i) || + echo !delete $i failed + } + } + echo !$#ndel messages deleted + + +} +fn e{ + >/tmp/e && + eval $editor /tmp/e && + yn send && + if(~ $yn y) + /bin/upas/marshal $* $rposts($2)^/flags && + puth $2 || + echo !address + } +} +fn fmtd{ + date=`{read} + switch($date(2)){ + case Jan; mo=1 + case Feb; mo=2 + case Mar; mo=3 + case Apr; mo=4 + case May; mo=5 + case Jun; mo=6 + case Jul; mo=7 + case Aug; mo=8 + case Sep; mo=9 + case Oct; mo=10 + case Nov; mo=11 + case Dec; mo=12 + } + switch($date(3)){ + case [0-9] + da=0^$date(3) + case * + da=$date(3) + } + switch($date(6)){ + case `{date | awk '{print $6;}'} + ti=`{echo $date(4) | awk '{print substr($0,0,5);}'} + case * + ti=$date(6) + } + echo $mo/$da $ti +} +fn geth{ + for(i in $*){ + flags=`{sed -n 18p $rposts($i)^/info | sed 's/-//g'} + mime=`{ + if(~ `{sed -n 7p $rposts($i)^/info} multipart*) + echo H + } + size=`{sed -n 17p $rposts($i)^/info} + date=`{sed -n 5p $rposts($i)^/info | fmtd} + from=`{sed -n 1p $rposts($i)^/info} + subject=`{sed -n 6p $rposts($i)^/info | awk '{print substr($0,0,50);}'} + # Unicode 00a0 divides the message number from the headline. + # Command input ignores everything after the unicode 00a0. + # These lines may be selected and sent to the prompt + # in order to print the indicated message. + echo ' '$"i' '$"mime' '$"flags' '$"size' '$"date' '$"from' '$"subject + } +} +fn getposts{ ls | grep -e '^[0-9]+$' | sort -n $sort } +fn getr{ + switch($*){ + case ,; echo $posts + case ,*; seq 1 `{echo $* | sed 's/,//g'} + case *,; seq `{echo $* | sed 's/,//g'} $posts($#posts) + case *,*; seq `{echo $* | sed 's/,/ /g'} + case *; echo $* + } +} +fn h{ sed -n $1^p /tmp/h } +fn m{ + if(test $1 -le $#rposts && test -f $rposts($1)^/info){ + subject=`{sed -n 6p $rposts($1)^/info} + if(! ~ $subject FWD:* Fwd:* fwd:*) + subject=(Fwd: $subject) + e -s $"subject -A $rposts($1)^/raw $*(2-) && + flag +a $1 + } + if not + echo !address +} +fn mb{ + mb=$1 + if(test -d /mail/box/$user/$mb || test -d $mb){ + if(! ~ $mb mbox){ + if(~ $mb /mail/fs/*) + mb=`{basename $mb} + if not if(~ $mb /*) + echo open $mb `{basename $mb} >/mail/fs/ctl + if not + echo open /mail/box/$user/$mb $mb >/mail/fs/ctl + mb=`{basename $mb} + } + cd /mail/fs/$mb + dposts=() + y + post=$posts(1) + prompt=$post + } + if not + echo !^$mb does not exist +} +fn printhelp{ +echo 'Commands are of the form [] [args] + := | ',' + := +a reply to sender and recipients +b print the next ten headers +d mark for deletion +e ... enter message (args passed to upas/marshal) +g/regexp/cmd grep headlines for regexp and run cmd on matches +h print message headline (,h for all) +help print this help message +m ... forward mail to address(es) +mb ... change to specified mailbox +p print the processed message +P print the raw message +q quit +r reply to message +s ... store message in specified mailbox +u remove deletion mark +y synchronize with mail box +" print message in quoted form, suitable for reply +|cmd pipe the processed message to a command +||cmd pipe the raw message to a command +!cmd run a command' +} +fn pp{ +if(test $1 -le $#rposts && test -f $rposts($1)^/header){ +{ # Avoid stutter by dumping everything into a file first. + cat $rposts($1)^/header + echo + if(test -d $rposts($1)^/1){ + parts=`{ls -p $rposts($1) | grep -e '^[0-9]+'} + body=1/body + if(test -f $rposts($1)^/1/1/body) + body=1/1/body + } + if not{ + parts=() + body=body + } + type=`{file -m $rposts($1)^/$body} + if(~ $type text/plain) + cat $rposts($1)^/$body + if not if(~ $type text/html){ + hcmd=(htmlfmt -l60 -cutf8 -a $rposts($1)^/$body) + echo !/bin/^$"hcmd + eval $hcmd + echo + echo !--- $rposts($1) $type `{du $rposts($1)^/$body | awk '{print $1}'} [file:///mail/fs/$mb/^$rposts($1)^/$body] # plumb to browser + } + if not{ + disp=`{sed -n 8p $rposts($1)^/info} + file=`{sed -n 9p $rposts($1)^/info} + fakefile $rposts($1) + } + echo + if(! ~ $#parts 0){ + if(! ~ $#parts 1) + parts=$parts(2-) + for(j in $parts){ + type=`{file -m $rposts($1)^/$j/body} + disp=`{sed -n 8p $rposts($1)^/$j/info} + file=`{sed -n 9p $rposts($1)^/$j/info} + fakefile $rposts($1)^/$j + } + parts=() + } +} >/tmp/p + eval $pager /tmp/p + go=1 + r=$1 + post=$1 + prompt=$1 + flag +s $1 +} +if not + echo !address +} +fn P{ + if(test $1 -le $#rposts && test -f $rposts($1)^/rawunix){ + eval $pager $rposts($1)^/rawunix + go=1 + r=$1 + post=$1 + prompt=$1 + flag +s $1 + } + if not + echo !address +} +fn puth{ + flags=`{sed -n 18p $rposts($1)^/info | sed 's/-//g'} + mime=`{ + if(~ `{sed -n 7p $rposts($1)^/info} multipart*) + echo H + } + size=`{sed -n 17p $rposts($1)^/info} + date=`{sed -n 5p $rposts($1)^/info | fmtd} + from=`{sed -n 1p $rposts($1)^/info} + subject=`{sed -n 6p $rposts($1)^/info | awk '{print substr($0,0,50);}'} + { + echo $1 + echo c + # REMEMBER: Unicode 00a0 divides the message number from the headline. + echo ' '^$1^' '$"mime' '$"flags' '$"size' '$"date' '$"from' '$"subject + echo . + echo w + echo q + } | sam -d /tmp/h >/dev/null >[2=1] +} +fn r{ + if(test $1 -le $#rposts && test -f $rposts($1)^/info){ + subject=`{sed -n 6p $rposts($1)^/info} + if(! ~ $subject RE:* Re:* re:*) + subject=(Re: $subject) + e -R $rposts($1) -s $"subject $*(2-) `{sed -n 4p $rposts($1)^/info} && + flag +a $1 + } + if not + echo !address +} +fn s{ + if(test $1 -le $#rposts && test -f $rposts($1)^/raw){ + if(! test -d /mail/box/$user/$2) + echo create $2 >/mail/fs/ctl + /bin/upas/mbappend $2 $rposts($1)^/raw && + flag +S $1 && + echo !saved in $2 + } + if not + echo !address +} +fn u{ + if(test $1 -le $#rposts && test -d $rposts($1)) + flag -D $1 || echo !undelete $1 failed + if not + echo !address + dposts=`{grep -v $1 <{for(j in $dposts){ echo $j }}} +} +fn y{ + go=() + r=$post + if(! ~ $#dposts 0){ + deldposts + dposts=() + } + if(! ~ $q 1){ + rposts=`{getposts} + posts=`{seq 1 $#rposts} + post=$posts(1) + prompt=$post + geth $posts >/tmp/h + if(~ $#msg 0) + echo $#posts messages + } +} +fn yn{ + echo + echo -n $* ' (y, n) ' + yn=`{read} + switch($yn){ + case y n + ; + case * + yn + } +} +fn '"' { pager=cat pp $1 | sed 1d | sed 's/^/> /g' | sed 's/^> >/>>/g' } +fn usage{ + echo usage: $argv0 [ -b ] [ -d ] [ -f mbox ] [ -p msg ] [ -r ] >[1=2] + exit usage +} +while(~ $1 -*){ + switch($1){ + case -b; biff=-b + case -d; debug=1 + case -f; mb=$2; shift + case -p; msg=$2; shift + case -r; sort=(); shift + case *; usage + } + shift +} +if(! ~ $#* 0) + usage +if(! test -f /mail/fs/ctl) + /bin/upas/fs $biff #>[2]/dev/null +if(! test -d /mail/box/$user/$mb && ! test -d $mb){ + echo !^$mb does not exist + exit $mb^' does not exist' +} +mb $mb +if(! ~ $#msg 0){ + for(i in `{seq 1 $#rposts}) + if(~ $msg $rposts($i)) + msg=$posts($i) + pp $msg +} +while(){ + echo -n $"prompt': ' + # Command input ignores everything after unicode 00a0. + rcmd=`{read | sed 's/[ ].*$//g' | sed 's/^([0-9]+)?(,)?([0-9]+)?/& /g'} + switch($rcmd){ + case ,* [0-9]* + r=`{getr $rcmd(1)} + cmd=$rcmd(2-) + if(~ $#cmd 0) + cmd=p + case * + r=$post + cmd=$rcmd + } + switch($cmd){ + case a a' '* + for(i in $r) + r $i $cmd(2-) `{sed -n 2,3p $rposts($i)^/info | sort -n | uniq} + post=$r($#r) + prompt=$post + case b + r=`{seq $r(1) `{echo $r(1)^+10|bc}} + if(test $r($#r) -gt $posts($#posts)) + r=`{seq $r(1) $posts($#posts)} + if(! ~ $#r 0 && test $r(1) -le $posts($#posts)){ + sed -n $r(1)^,$r($#r)^p /tmp/h + post=$r($#r) + prompt=$post + } + if not + echo !address + case d + for(i in $r) + d $i + post=$r($#r) + prompt=$post + case e' '* + e $cmd(2-) + case g/* + regexp=`{echo $cmd | awk -F '/' '{print $2;}'} # BUG: / is stripped from regexp and cmd. + cmd=`{echo $cmd | awk -F '/' '{$1=""; $2=""; print;}'} + r=`{