Added the cli echo viewer and rudimentary README
This commit is contained in:
@@ -0,0 +1,134 @@
|
|||||||
|
tii: a Tcl-based suite for working with ii/IDEC protocol
|
||||||
|
========================================================
|
||||||
|
This suite implements the client side of ii and (partially) IDEC protocols of
|
||||||
|
distributed, cross-platform, text-based communication (a FIDOnet successor, so
|
||||||
|
to speak). Protocol documentation can be found here for both ii and IDEC:
|
||||||
|
https://github.com/IDEC-net/new-docs/blob/master/protocol-en.md
|
||||||
|
(there will be an effort to write a more concise version of that doc)
|
||||||
|
|
||||||
|
The tii suite requires at least Tcl 8.6 to run. Running it inside starpacks is
|
||||||
|
possible but not recommended.
|
||||||
|
|
||||||
|
The tii repo consists of the following parts:
|
||||||
|
|
||||||
|
* tiifetch.tcl: the core ii/IDEC message fetching library and CLI utility
|
||||||
|
* tiipost.tcl: the core ii/IDEC message posting library and CLI utility
|
||||||
|
* tiiview.tcl: the CLI viewer of the fetched ii/IDEC messages and conferences
|
||||||
|
* tiidb: the (overridable) directory that contains all messages and echo lists
|
||||||
|
* stations.txt: the list of stations to be auto-fetched by tiifetch when none
|
||||||
|
of its command-line parameters is passed
|
||||||
|
* auth.txt: the list of station/authstring mappings to be used by tiipost when
|
||||||
|
posting messages to a particular station
|
||||||
|
* tiix.tcl: the GUI ii/IDEC viewer that also leverages tiifetch and tiipost to
|
||||||
|
provide fetching and posting functionality
|
||||||
|
|
||||||
|
Readiness status
|
||||||
|
----------------
|
||||||
|
* tiifetch.tcl: ready/tested
|
||||||
|
* tiipost.tcl: planned
|
||||||
|
* tiiview.tcl: ready/tested
|
||||||
|
* tiidb (format): ready/tested
|
||||||
|
* stations.txt (format): ready/tested
|
||||||
|
* auth.txt (format): ready to be implemented
|
||||||
|
* tiix.tcl: planned
|
||||||
|
* Overall status: work in progress
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
This section is a work in progress and will be updated as more components are
|
||||||
|
developed.
|
||||||
|
|
||||||
|
### Fetching the messages (tiifetch.tcl): ###
|
||||||
|
|
||||||
|
tclsh tiifetch.tcl [station_url] [echos] [dbdir]
|
||||||
|
|
||||||
|
This command will fetch all messages into the dbdir ("tiidb" in the script dir
|
||||||
|
by default) from the station_url (can be empty, see below) based on the echo
|
||||||
|
conference names (can be delimited with slash /, comma (,) or semicolon (;))
|
||||||
|
and create the corresponding file structure if it's missing.
|
||||||
|
|
||||||
|
Fetching is supported for the following station URL schemes and protocols:
|
||||||
|
|
||||||
|
* HTTP (http://)
|
||||||
|
* HTTPS (https://)
|
||||||
|
* Gopher (gopher://)
|
||||||
|
* Gopher over TLS (gophers://)
|
||||||
|
* Finger (finger://)
|
||||||
|
* Nex (nex://)
|
||||||
|
* Spartan (spartan://)
|
||||||
|
* Gemini (gemini://)
|
||||||
|
|
||||||
|
If the station_url parameter is empty or no parameters are passed at all,
|
||||||
|
tiifetch.tcl will look for a file called stations.txt that lists (each on a
|
||||||
|
new line) all the station URLs to sync from. Messages from all listed stations
|
||||||
|
will be merged into the same echo conference database.
|
||||||
|
|
||||||
|
### Viewing the messages from CLI (tiiview.tcl): ###
|
||||||
|
|
||||||
|
tclsh tiiview.tcl [echo_name] [filter_string] [line_width] [dbdir]
|
||||||
|
|
||||||
|
If the echo_name parameter is passed, this command will write all formatted
|
||||||
|
messages from the coresponding echo conference to the standard output.
|
||||||
|
You should pipe this message stream to your $PAGER terminal application, like
|
||||||
|
less or most. The messages will be formatted according to the filter_string
|
||||||
|
format (see below). If the filter string is empty or omitted, the messages
|
||||||
|
will appear in the exact order their IDs appear in the echo conference file.
|
||||||
|
|
||||||
|
If the echo_name parameter is empty or no parameters are passed at all, this
|
||||||
|
command will output the list of echo conferences registered in the dbdir.
|
||||||
|
The conference list will be alphabetically ordered and the filter_string will
|
||||||
|
have no effect at all if you pass it.
|
||||||
|
|
||||||
|
If the line_width parameter is omitted, the text reflows to 80 chars per line.
|
||||||
|
|
||||||
|
If the dbdir is ommitted, it defaults to "tiidb" in the script directory.
|
||||||
|
|
||||||
|
This component is fully offline and can only work with a compatible message
|
||||||
|
database that tiifetch.tcl can generate (see "Message database format").
|
||||||
|
|
||||||
|
The filter string can take one of the following basic forms:
|
||||||
|
|
||||||
|
* h[number]: only take [number] messages from the head (start) of the list
|
||||||
|
* t[number]: only take [number] messages from the tail (end) of the list
|
||||||
|
* rh[number]: same as h but output messages from newest to oldest in the list
|
||||||
|
* rt[number]: same as t but output messages from newest to oldest in the list
|
||||||
|
|
||||||
|
If [number] is 0 then it means no message limit.
|
||||||
|
The reverse operation is always done after limiting the results.
|
||||||
|
|
||||||
|
e.g. rt50 will output 50 newest messages in the conference, starting from the
|
||||||
|
most recent one. The default basic value is h0, so no filter applied will mean
|
||||||
|
outputting all messages from the oldest to the newest.
|
||||||
|
|
||||||
|
You can extend the basic form by appending a search regular expression to it,
|
||||||
|
like this: [form]/[regex]. Note that the regex always applies AFTER the basic
|
||||||
|
filtering has been done. Also note that the search is done within all fields
|
||||||
|
of the message. E.g. h100/retro will find the retro-themed messages among the
|
||||||
|
first 100 of them.
|
||||||
|
|
||||||
|
Message database format
|
||||||
|
-----------------------
|
||||||
|
The tiidb format is based upon the official ii/IDEC developer recommendations
|
||||||
|
and is fully plaintext-based, portable and very simple:
|
||||||
|
|
||||||
|
* Message contents are stored decoded in the "msg/" subdirectory. The file
|
||||||
|
names are their plain 20-character hash IDs.
|
||||||
|
* Echo contents are stored in the "echo/" subdirectory. The file names are the
|
||||||
|
conference names verbatim, containing newline-separated message IDs that
|
||||||
|
belong to those conferences, in order they were published there.
|
||||||
|
Every echo file ends with a blank line.
|
||||||
|
|
||||||
|
FAQ
|
||||||
|
---
|
||||||
|
|
||||||
|
- Does tii implement any IDEC extensions?
|
||||||
|
|
||||||
|
Only one: fetching list.txt from the station to get the entire list of echo
|
||||||
|
conferences served by this station. This is something that the original ii
|
||||||
|
spec did not support.
|
||||||
|
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
Created by Luxferre in 2024, released into public domain with no warranties.
|
||||||
|
|
||||||
+32
-6
@@ -1,13 +1,12 @@
|
|||||||
#!/usr/bin/env tclsh
|
#!/usr/bin/env tclsh
|
||||||
# tiifetch: fetch all data from an ii/idec station into the local text db
|
# tiifetch: fetch all data from an ii/idec station into the local text db
|
||||||
# (see https://github.com/idec-net/new-docs/blob/master/protocol-en.md)
|
# (see https://github.com/idec-net/new-docs/blob/master/protocol-en.md)
|
||||||
# Usage: tiifetch station_http_url [echos] [db_dir]
|
# Usage: tiifetch.tcl [station_url] [echos] [db_dir]
|
||||||
# The echo list should be delimited with slash (/), comma (,) or semicolon (;)
|
# The echo list should be delimited with slash (/), comma (,) or semicolon (;)
|
||||||
# if no echos are specified (or "" is passed), then list.txt will be fetched
|
# if no echos are specified (or "" is passed), then list.txt will be fetched
|
||||||
# and then all missing echo content from it will be downloaded
|
# and then all missing echo content from it will be downloaded
|
||||||
# If db_dir isn't specified, it's fetched and merged into the
|
# If db_dir isn't specified, it's fetched and merged into the
|
||||||
# tiidb directory in the program root
|
# tiidb directory in the program root with echoconfs and messages respectively
|
||||||
# with echoconfs and messages respectively
|
|
||||||
# This component only fetches the messages, doesn't parse or display them
|
# This component only fetches the messages, doesn't parse or display them
|
||||||
# Supported protocols: HTTP, HTTPS, Gemini, Spartan, Gopher/Finger/Nex
|
# Supported protocols: HTTP, HTTPS, Gemini, Spartan, Gopher/Finger/Nex
|
||||||
# Depends on Tcllib for URI parsing
|
# Depends on Tcllib for URI parsing
|
||||||
@@ -268,6 +267,24 @@ proc fetchiidb {url echos dbdir dolog} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proc massfetch {echos dbdir dolog} {
|
||||||
|
global appdir
|
||||||
|
if {$dolog eq 1} {puts "No ii/idec station URL specified, using stations.txt"}
|
||||||
|
set stfile [file join $appdir "stations.txt"]
|
||||||
|
if {[file exists $stfile]} {
|
||||||
|
set stlist [split [readfile $stfile] "\n"]
|
||||||
|
foreach station $stlist {
|
||||||
|
set station [string trim $station]
|
||||||
|
if {$station ne ""} {
|
||||||
|
if {$dolog eq 1} {puts "Fetching from $station"}
|
||||||
|
fetchiidb $station $echos $dbdir $dolog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if {$dolog eq 1} {puts "No stations.txt found, bailing out!"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# end of procs, start the entrypoint
|
# end of procs, start the entrypoint
|
||||||
if {![info exists argv0] || [file tail [info script]] ne [file tail $argv0]} {return}
|
if {![info exists argv0] || [file tail [info script]] ne [file tail $argv0]} {return}
|
||||||
|
|
||||||
@@ -278,13 +295,22 @@ if [string match *app-tiifetch $appdir] {
|
|||||||
set appdir [file normalize [file join $appdir ".." ".." ".." ]]
|
set appdir [file normalize [file join $appdir ".." ".." ".." ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set localdbdir [file join $appdir "tiidb"]
|
||||||
if {$argc > 0} {
|
if {$argc > 0} {
|
||||||
set localdbdir [file join $appdir "tiidb"]
|
|
||||||
if {$argc > 2} {
|
if {$argc > 2} {
|
||||||
set localdbdir [lindex $argv 2]
|
set localdbdir [lindex $argv 2]
|
||||||
}
|
}
|
||||||
puts "Fetching messages, please wait..."
|
puts "Fetching messages, please wait..."
|
||||||
fetchiidb [lindex $argv 0] [lindex $argv 1] $localdbdir 1
|
set sturl [string trim [lindex $argv 0]]
|
||||||
|
if {$sturl eq ""} {
|
||||||
|
massfetch [lindex $argv 1] $localdbdir 1
|
||||||
|
} else {
|
||||||
|
fetchiidb $sturl [lindex $argv 1] $localdbdir 1
|
||||||
|
}
|
||||||
puts "Messages fetched"
|
puts "Messages fetched"
|
||||||
} else {puts "No ii/idec station URL specified!"}
|
} else {
|
||||||
|
puts "Fetching messages, please wait..."
|
||||||
|
massfetch "" $localdbdir 1
|
||||||
|
puts "Messages fetched"
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Executable
+147
@@ -0,0 +1,147 @@
|
|||||||
|
#!/usr/bin/env tclsh
|
||||||
|
# tiiview: view ii/idec messages from the local text db
|
||||||
|
# Usage: tiiview.tcl [echo_name] [filter_string] [termwidth] [dbdir]
|
||||||
|
# Created by Luxferre in 2024, released into public domain
|
||||||
|
|
||||||
|
# file read helper
|
||||||
|
proc readfile {fname} {
|
||||||
|
set fp [open $fname r]
|
||||||
|
fconfigure $fp -encoding utf-8
|
||||||
|
set data [read $fp]
|
||||||
|
close $fp
|
||||||
|
return $data
|
||||||
|
}
|
||||||
|
|
||||||
|
# basic text reflow helper
|
||||||
|
# list in, string out
|
||||||
|
proc tiiflow {lines width} {
|
||||||
|
set outtext {}
|
||||||
|
foreach inl $lines {
|
||||||
|
set l [string length $inl]
|
||||||
|
if {$l <= $width} {
|
||||||
|
append outtext "$inl\n"
|
||||||
|
} else {
|
||||||
|
set wordset ""
|
||||||
|
set words [split $inl " "]
|
||||||
|
foreach w $words {
|
||||||
|
set candidate "$wordset $w"
|
||||||
|
if {[string length $candidate] <= $width} {
|
||||||
|
set wordset $candidate
|
||||||
|
} else {
|
||||||
|
append outtext "$wordset\n"
|
||||||
|
set wordset $w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append outtext "$wordset\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $outtext
|
||||||
|
}
|
||||||
|
|
||||||
|
# parse and pretty-print the found message
|
||||||
|
proc formatmessage {msgdata msgid globalwidth} {
|
||||||
|
set globalline [string repeat = $globalwidth]
|
||||||
|
set hdrline [string repeat - $globalwidth]
|
||||||
|
set msglines [lmap s [split $msgdata "\n"] {string trimright $s}]
|
||||||
|
# parsing according to the spec, first 7 lines are:
|
||||||
|
# tags, echoarea, timestamp, msgfrom, msgfrom_addr, msgto, subj
|
||||||
|
# and then an empty line and the message body follows
|
||||||
|
set tags [split [lindex $msglines 0] "/"]
|
||||||
|
if {[dict exists $tags repto]} {
|
||||||
|
set replyto [dict get $tags repto]
|
||||||
|
} else {set replyto ""}
|
||||||
|
set echoarea [lindex $msglines 1]
|
||||||
|
set timestamp [lindex $msglines 2]
|
||||||
|
set msgfrom [lindex $msglines 3]
|
||||||
|
set msgfromaddr [lindex $msglines 4]
|
||||||
|
set msgto [lindex $msglines 5]
|
||||||
|
set subj [lindex $msglines 6]
|
||||||
|
set msgbody [tiiflow [lrange $msglines 8 end] $globalwidth]
|
||||||
|
set tz ""
|
||||||
|
set renderedts [clock format $timestamp -format {%Y-%m-%d %H:%M:%S} -timezone $tz]
|
||||||
|
catch { # because pipe can be broken anytime
|
||||||
|
puts "\[$renderedts\] ii://$msgid"
|
||||||
|
puts "$echoarea - $msgfrom ($msgfromaddr) to $msgto"
|
||||||
|
if {$replyto ne ""} {
|
||||||
|
puts "Replied to: ii://$replyto"
|
||||||
|
}
|
||||||
|
puts "Subj: $subj"
|
||||||
|
puts $hdrline
|
||||||
|
puts "$msgbody$globalline\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# entry point
|
||||||
|
set scriptpath [file normalize [info script]]
|
||||||
|
set appdir [file dirname $scriptpath]
|
||||||
|
# check if we're running from a starpack
|
||||||
|
if [string match *app-tiiview $appdir] {
|
||||||
|
set appdir [file normalize [file join $appdir ".." ".." ".." ]]
|
||||||
|
}
|
||||||
|
set localdbdir [file join $appdir "tiidb"]
|
||||||
|
set echoname ""
|
||||||
|
set filterstr ""
|
||||||
|
set twidth 80
|
||||||
|
if {$argc > 0} {
|
||||||
|
if {$argc > 1} {
|
||||||
|
set filterstr [string trim [lindex $argv 1]]
|
||||||
|
}
|
||||||
|
if {$argc > 2} {
|
||||||
|
set twidth [expr {int([lindex $argv 2])}]
|
||||||
|
}
|
||||||
|
if {$argc > 3} {
|
||||||
|
set localdbdir [lindex $argv 3]
|
||||||
|
}
|
||||||
|
set echoname [string trim [lindex $argv 0]]
|
||||||
|
}
|
||||||
|
if {$twidth < 20} {set twidth 80}
|
||||||
|
if {$filterstr eq ""} {set filterstr "h0"}
|
||||||
|
set msgdir [file join $localdbdir "msg"]
|
||||||
|
set echodir [file join $localdbdir "echo"]
|
||||||
|
if {$echoname eq ""} { # list the echodir
|
||||||
|
set echos [glob -tails -directory $echodir -nocomplain -types f "*.*"]
|
||||||
|
puts [join [lsort $echos] "\n"]
|
||||||
|
} else { # fetch the actual contents
|
||||||
|
set echofile [file join $echodir $echoname]
|
||||||
|
if {[file exists $echofile]} {
|
||||||
|
set msglist [split [readfile $echofile] "\n"]
|
||||||
|
set filters [split $filterstr "/"]
|
||||||
|
set basicmod [string trim [lindex $filters 0]]
|
||||||
|
set filterregex {}
|
||||||
|
if {[llength $filters] > 1} {
|
||||||
|
set filterregex [string trim [lindex $filters 1]]
|
||||||
|
}
|
||||||
|
set doreverse 0
|
||||||
|
if {[string first r $basicmod] > -1} {set doreverse 1}
|
||||||
|
set dotail 0
|
||||||
|
if {[string first t $basicmod] > -1} {set dotail 1}
|
||||||
|
set numitems 0
|
||||||
|
if {[regexp {\d+} $basicmod foundnum]} {set numitems $foundnum}
|
||||||
|
# perform the element filtering
|
||||||
|
if {$numitems > 0} {
|
||||||
|
incr numitems -1
|
||||||
|
if {$dotail eq 1} {
|
||||||
|
set msglist [lrange $msglist end-$numitems end]
|
||||||
|
} else {
|
||||||
|
set msglist [lrange $msglist 0 $numitems]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if {$doreverse eq 1} {
|
||||||
|
set msglist [lreverse $msglist]
|
||||||
|
}
|
||||||
|
foreach msgid $msglist { # iterate over the list after filtering
|
||||||
|
set msgid [string trim $msgid]
|
||||||
|
if {$msgid ne ""} {
|
||||||
|
set msgfile [file join $msgdir $msgid]
|
||||||
|
if {[file exists $msgfile]} {
|
||||||
|
set msgdata [readfile $msgfile]
|
||||||
|
set pass 1
|
||||||
|
if {$filterregex ne {}} {
|
||||||
|
set pass [regexp -line -nocase -- $filterregex $msgdata]
|
||||||
|
}
|
||||||
|
if {$pass eq 1} {formatmessage $msgdata $msgid $twidth}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {puts "This echo conference doesn't exist in the local DB!"}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user