File: ./check/check.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package check 26 27 import ( 28 "bufio" 29 "fmt" 30 "io" 31 "math" 32 "net" 33 "os" 34 "strconv" 35 "time" 36 ) 37 38 const info = ` 39 check [options...] [timeout...] [domains...] 40 41 Check if the domains given can be connected to via TCP. An optional leading 42 number (of seconds) lets you override the default timeout of 5 seconds. 43 ` 44 45 func Main() { 46 args := os.Args[1:] 47 timeout := 5 * time.Second 48 49 if len(args) > 0 { 50 switch args[0] { 51 case `-h`, `--h`, `-help`, `--help`: 52 os.Stdout.WriteString(info[1:]) 53 return 54 } 55 } 56 57 if len(args) > 0 { 58 f, err := strconv.ParseFloat(args[0], 64) 59 if err == nil && f > 0 && !math.IsNaN(f) && !math.IsInf(f, 0) { 60 timeout = time.Duration(f * float64(time.Second)) 61 args = args[1:] 62 } 63 } 64 65 if len(args) > 0 && args[0] == `--` { 66 args = args[1:] 67 } 68 69 if len(args) == 0 { 70 os.Stderr.WriteString(info[1:]) 71 os.Exit(1) 72 return 73 } 74 75 ok, err := run(os.Stdout, args, timeout) 76 77 if err != nil && err != io.EOF { 78 os.Stderr.WriteString(err.Error()) 79 os.Stderr.WriteString("\n") 80 os.Exit(1) 81 return 82 } 83 84 if !ok { 85 os.Exit(1) 86 return 87 } 88 } 89 90 func run(w io.Writer, domains []string, timeout time.Duration) (ok bool, err error) { 91 bw := bufio.NewWriterSize(w, 32*1024) 92 defer bw.Flush() 93 94 allOK := true 95 96 for _, s := range domains { 97 ok, err := check(bw, s, timeout) 98 if err != nil { 99 bw.Flush() 100 } 101 if !ok { 102 allOK = false 103 } 104 } 105 106 return allOK, nil 107 } 108 109 func check(w *bufio.Writer, domain string, timeout time.Duration) (ok bool, err error) { 110 conn, err := net.DialTimeout(`tcp`, domain, timeout) 111 if conn != nil { 112 defer conn.Close() 113 } 114 115 if err == nil { 116 w.WriteString(domain) 117 w.WriteString("\tOK\n") 118 if w.Flush() != nil { 119 return true, io.EOF 120 } 121 return true, nil 122 } 123 124 fmt.Fprintf(w, "%s\tFAILED\t%s\n", domain, err.Error()) 125 if w.Flush() != nil { 126 return false, io.EOF 127 } 128 return false, nil 129 } File: ./dict/commands.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package dict 26 27 import ( 28 "bufio" 29 "fmt" 30 "io" 31 "net" 32 "regexp" 33 "strings" 34 "time" 35 ) 36 37 // https://www.rfc-editor.org/rfc/rfc2229 38 39 var ( 40 okLines = regexp.MustCompile(`^[12][0-9]{2} `) 41 errLines = regexp.MustCompile(`^5[0-9]{2} `) 42 ) 43 44 const maxBufSize = 8 * 1024 * 1024 * 1024 45 46 func lookup(w io.Writer, dict string, words []string) (ok bool, err error) { 47 ok = true 48 dict = strings.ToLower(dict) 49 if strings.Contains(dict, `-`) && !strings.HasPrefix(dict, `fd-`) { 50 dict = `fd-` + dict 51 } 52 53 for _, s := range words { 54 cmd := fmt.Sprintf(`define %s %s`, dict, s) 55 // color the command in blue 56 // fmt.Fprintf(w, "\x1b[38;2;0;95;215m%s\x1b[0m\n", cmd) 57 58 // highlight the word being looked-up 59 fmt.Fprintf(w, "\x1b[7m%-80s\x1b[0m\n", s) 60 61 good, err := runCommand(w, cmd) 62 if !good { 63 ok = false 64 } 65 if err != nil { 66 return ok, err 67 } 68 } 69 return ok, nil 70 } 71 72 func runCommand(w io.Writer, cmd string) (ok bool, err error) { 73 conn, err := net.Dial(`tcp`, `dict.org:2628`) 74 if err != nil { 75 return false, err 76 } 77 defer conn.Close() 78 79 conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) 80 fmt.Fprintf(conn, "%s\r\n", cmd) 81 82 sc := bufio.NewScanner(conn) 83 sc.Buffer(nil, maxBufSize) 84 85 ok = true 86 87 for sc.Scan() { 88 line := sc.Text() 89 if okLines.MatchString(line) { 90 // ignore success lines 91 continue 92 } 93 if errLines.MatchString(line) { 94 ok = false 95 // color errors in red 96 fmt.Fprintf(w, "\x1b[38;2;204;0;0m%s\x1b[0m\n", line) 97 continue 98 } 99 100 if line == `.` { 101 // ignore last line 102 break 103 } 104 105 fmt.Fprintln(w, line) 106 } 107 108 return ok, sc.Err() 109 } File: ./dict/info.txt 1 dict [options...] [dictionary...] [word...] 2 3 Look up words from the free online dictionary (dict://dict.org). The help 4 option also shows you which dictionaries are available. 5 6 If no specific dictionary is given (using 1 or 2 leading dashes), `WordNet` 7 (wn) is used as the default. File: ./dict/main.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package dict 26 27 import ( 28 "bufio" 29 "fmt" 30 "io" 31 "os" 32 "strings" 33 34 _ "embed" 35 ) 36 37 //go:embed info.txt 38 var info string 39 40 func Main() { 41 dict := `wn` 42 args := os.Args[1:] 43 44 for len(args) > 0 { 45 if args[0] == `--` { 46 args = args[1:] 47 break 48 } 49 50 switch args[0] { 51 case `-h`, `--h`, `-help`, `--help`: 52 showHelp() 53 return 54 } 55 56 if strings.HasPrefix(args[0], `-`) { 57 s := args[0] 58 for len(s) > 0 && s[0] == '-' { 59 s = s[1:] 60 } 61 dict = s 62 continue 63 } 64 65 break 66 } 67 68 if len(args) == 0 { 69 showHelp() 70 os.Exit(1) 71 return 72 } 73 74 ok, err := run(os.Stdout, dict, args) 75 if err != nil { 76 fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 77 os.Exit(1) 78 return 79 } 80 if !ok { 81 os.Exit(1) 82 return 83 } 84 } 85 86 func run(w io.Writer, dict string, args []string) (ok bool, err error) { 87 bw := bufio.NewWriterSize(w, 16*1024) 88 defer bw.Flush() 89 ok, err = lookup(bw, dict, args) 90 return ok, err 91 } 92 93 func showHelp() { 94 fmt.Fprint(os.Stderr, info) 95 fmt.Fprintln(os.Stderr) 96 fmt.Fprintln(os.Stderr, `Dictionaries available from dict.org`) 97 fmt.Fprintln(os.Stderr) 98 99 ok, err := runCommand(os.Stderr, `show db`) 100 if err != nil { 101 fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 102 os.Exit(1) 103 return 104 } 105 if !ok { 106 os.Exit(1) 107 return 108 } 109 } File: ./fetch/fetch.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package fetch 26 27 import ( 28 "bufio" 29 "io" 30 "net/http" 31 "os" 32 "strings" 33 ) 34 35 const info = ` 36 fetch [options...] [URIs...] 37 38 Load/get data from the HTTP/HTTPS URIs given, auto-completing the protocol to 39 HTTPS, when missing. 40 ` 41 42 func Main() { 43 args := os.Args[1:] 44 45 if len(args) > 0 { 46 switch args[0] { 47 case `-h`, `--h`, `-help`, `--help`: 48 os.Stdout.WriteString(info[1:]) 49 return 50 } 51 } 52 53 if len(args) > 0 && args[0] == `--` { 54 args = args[1:] 55 } 56 57 if len(args) == 0 { 58 os.Stderr.WriteString(info[1:]) 59 os.Exit(1) 60 return 61 } 62 63 if err := run(os.Stdout, args); err != nil && err != io.EOF { 64 os.Stderr.WriteString(err.Error()) 65 os.Stderr.WriteString("\n") 66 os.Exit(1) 67 return 68 } 69 } 70 71 func run(w io.Writer, args []string) error { 72 bw := bufio.NewWriterSize(w, 32*1024) 73 defer bw.Flush() 74 75 for _, s := range args { 76 if err := fetch(bw, s); err != nil { 77 return err 78 } 79 } 80 return nil 81 } 82 83 func fetch(w *bufio.Writer, uri string) error { 84 if !hasAnyPrefix(uri, `https://`, `http://`) { 85 uri = `https://` + uri 86 } 87 88 resp, err := http.Get(uri) 89 if err != nil { 90 return err 91 } 92 defer resp.Body.Close() 93 94 _, err = io.Copy(w, resp.Body) 95 return err 96 } 97 98 func hasAnyPrefix(s string, prefixes ...string) bool { 99 for _, p := range prefixes { 100 if strings.HasPrefix(s, p) { 101 return true 102 } 103 } 104 return false 105 } File: ./get/get.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package get 26 27 import ( 28 "bufio" 29 "io" 30 "net/http" 31 "os" 32 ) 33 34 const info = ` 35 get [options...] [URIs...] 36 37 Load/get data from the HTTP/HTTPS URIs given. 38 ` 39 40 func Main() { 41 args := os.Args[1:] 42 43 if len(args) > 0 { 44 switch args[0] { 45 case `-h`, `--h`, `-help`, `--help`: 46 os.Stdout.WriteString(info[1:]) 47 return 48 } 49 } 50 51 if len(args) > 0 && args[0] == `--` { 52 args = args[1:] 53 } 54 55 if len(args) == 0 { 56 os.Stderr.WriteString(info[1:]) 57 os.Exit(1) 58 return 59 } 60 61 if err := run(os.Stdout, args); err != nil && err != io.EOF { 62 os.Stderr.WriteString(err.Error()) 63 os.Stderr.WriteString("\n") 64 os.Exit(1) 65 return 66 } 67 } 68 69 func run(w io.Writer, args []string) error { 70 bw := bufio.NewWriterSize(w, 32*1024) 71 defer bw.Flush() 72 73 for _, s := range args { 74 if err := get(bw, s); err != nil { 75 return err 76 } 77 } 78 return nil 79 } 80 81 func get(w *bufio.Writer, uri string) error { 82 resp, err := http.Get(uri) 83 if err != nil { 84 return err 85 } 86 defer resp.Body.Close() 87 88 _, err = io.Copy(w, resp.Body) 89 return err 90 } File: ./info.txt 1 netbox [options...] [tool] [arguments...] 2 3 This is a collection of many specialized networking-related app-like tools, 4 similar to "busybox". 5 6 You can either run it with the tool name as its first argument, or run a link 7 to it whose name is one of those same tools, avoiding the tool-name argument 8 in that case. 9 10 Tool "help" shows you all tools available, as well as all their aliases, and 11 tool "tools" merely lists all main tool-names. File: ./main.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 To compile a smaller-sized command-line app, you can use the `go` command as 27 follows: 28 29 go build -ldflags "-s -w" -trimpath 30 31 If you have `tinygo`, you can do even better by instead using: 32 33 tinygo build -no-debug -opt=2 netbox 34 */ 35 36 package main 37 38 import ( 39 "fmt" 40 "io" 41 "os" 42 "path" 43 "sort" 44 "strings" 45 "unicode/utf8" 46 47 "./check" 48 "./dict" 49 "./fetch" 50 "./get" 51 "./podfeed" 52 "./wports" 53 54 _ "embed" 55 ) 56 57 //go:embed info.txt 58 var info string 59 60 // mains has some entries starting as nil to avoid circular-dependency errors 61 var mains = map[string]func(){ 62 `check`: check.Main, 63 `dict`: dict.Main, 64 `fetch`: fetch.Main, 65 `get`: get.Main, 66 `podfeed`: podfeed.Main, 67 `wports`: wports.Main, 68 } 69 70 var aliases = map[string]string{} 71 72 var blurbs = map[string]string{ 73 `check`: `check if the domains given can be connected to via TCP`, 74 `dict`: `dictionary-define the words given, via dict://dict.org`, 75 `get`: `try a get request over HTTP/HTTPS; no protocol autocomplete`, 76 `fetch`: `fetch over HTTP/HTTPS, with protocol auto-completed to HTTPS`, 77 `help`: `show the help message for "netbox"`, 78 `podfeed`: `link to podcast episodes in a self-contained web-page`, 79 `tools`: `list all tools available`, 80 `wports`: `find which localhost TCP ports are currently in use`, 81 } 82 83 func main() { 84 // try to use the app's `name`, in case it's being called from a file-link 85 // named after one of the tools 86 if tool, ok := lookupTool(path.Base(os.Args[0])); ok { 87 tool() 88 return 89 } 90 91 // try normal tool-lookup using the first command-line argument 92 if len(os.Args) >= 2 { 93 name := os.Args[1] 94 95 if tool, ok := lookupTool(name); ok { 96 os.Args = os.Args[1:] 97 tool() 98 return 99 } 100 101 switch name { 102 case `-h`, `--h`, `-help`, `--help`, `help`: 103 showHelp(os.Stdout) 104 return 105 106 case `-l`, `--l`, `-list`, `--list`: 107 tools() 108 return 109 110 case `-links`, `--links`: 111 showLinks(os.Stdout) 112 return 113 114 case `-t`, `--t`, `-tools`, `--tools`, `tools`: 115 tools() 116 return 117 } 118 119 const fs = "netbox: tool/alias named %q not found\n" 120 fmt.Fprintf(os.Stderr, fs, name) 121 os.Exit(1) 122 return 123 } 124 125 showHelp(os.Stderr) 126 fmt.Fprintln(os.Stderr, ``) 127 fmt.Fprintln(os.Stderr, `netbox: no tool name given`) 128 os.Exit(1) 129 return 130 } 131 132 // dealias tries to lookup a string to the aliases given, returning the name 133 // given if the lookup fails 134 func dealias(aliases map[string]string, name string) string { 135 if s, ok := aliases[name]; ok { 136 return s 137 } 138 return name 139 } 140 141 func help() { 142 showHelp(os.Stdout) 143 } 144 145 func lookupTool(name string) (tool func(), ok bool) { 146 if name == `help` { 147 return help, true 148 } 149 if name == `tools` { 150 return tools, true 151 } 152 153 name = strings.ReplaceAll(name, `-`, ``) 154 155 if tool, ok := mains[dealias(aliases, name)]; ok { 156 return tool, ok 157 } 158 return tool, ok 159 } 160 161 // showHelp has a parameter to write either to stdout or stderr 162 func showHelp(w io.Writer) { 163 fmt.Fprintln(w, info) 164 fmt.Fprintln(w, ``) 165 fmt.Fprintln(w, `Tools Available`) 166 167 maxlen := 0 168 names := make([]string, 0, max(len(mains), len(aliases))) 169 for k := range mains { 170 names = append(names, k) 171 maxlen = max(maxlen, utf8.RuneCountInString(k)) 172 } 173 174 sort.Strings(names) 175 176 for _, s := range names { 177 fmt.Fprintf(w, " - %-*s %s\n", maxlen, s, blurbs[s]) 178 } 179 180 fmt.Fprintln(w, ``) 181 fmt.Fprintln(w, `Aliases Available`) 182 183 maxlen = 0 184 names = names[:0] 185 for k := range aliases { 186 names = append(names, k) 187 maxlen = max(maxlen, utf8.RuneCountInString(k)) 188 } 189 190 sort.Strings(names) 191 192 for _, k := range names { 193 fmt.Fprintf(w, " - %-*s -> %s\n", maxlen, k, aliases[k]) 194 } 195 } 196 197 // showLinks has a parameter to write either to stdout or stderr 198 func showLinks(w io.Writer) { 199 names := make([]string, 0, len(mains)) 200 for k := range mains { 201 names = append(names, k) 202 } 203 204 sort.Strings(names) 205 206 for _, s := range names { 207 fmt.Fprintf(w, "ln -s \"$(which netbox)\" ./%s\n", s) 208 } 209 } 210 211 func tools() { 212 names := make([]string, 0, len(mains)) 213 for k := range mains { 214 names = append(names, k) 215 } 216 217 sort.Strings(names) 218 219 for _, s := range names { 220 fmt.Fprintln(os.Stdout, s) 221 } 222 } File: ./main_test.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package main 26 27 import "testing" 28 29 func TestAliases(t *testing.T) { 30 for alias, name := range aliases { 31 if _, ok := mains[name]; ok { 32 continue 33 } 34 35 t.Errorf("alias %q leads nowhere", alias) 36 } 37 } 38 39 func TestBlurbs(t *testing.T) { 40 for name := range mains { 41 if blurbs[name] != `` { 42 continue 43 } 44 t.Errorf("no description/blurb for tool %q", name) 45 } 46 } File: ./mit-license.txt 1 The MIT License (MIT) 2 3 Copyright (c) 2026 pacman64 4 5 Permission is hereby granted, free of charge, to any person obtaining a copy of 6 this software and associated documentation files (the "Software"), to deal 7 in the Software without restriction, including without limitation the rights to 8 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 of the Software, and to permit persons to whom the Software is furnished to do 10 so, subject to the following conditions: 11 12 The above copyright notice and this permission notice shall be included in all 13 copies or substantial portions of the Software. 14 15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 SOFTWARE. File: ./podfeed/atom.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package podfeed 26 27 import ( 28 "bytes" 29 "encoding/xml" 30 ) 31 32 // atomFeed is an atom-format XML/RSS document: all its useful info is in the 33 // Channels field, which is an array with usually only 1 item 34 type atomFeed struct { 35 Atom string `xml:"atom"` 36 CC string `xml:"cc"` 37 Channels []atomChannel `xml:"channel"` 38 Content string `xml:"content"` 39 Media string `xml:"media"` 40 Version int `xml:"version"` 41 } 42 43 // atomChannel has all the channel tags in an atom-format document: its most 44 // useful info is in its Items array field 45 type atomChannel struct { 46 Author string `xml:"author"` 47 Description string `xml:"description"` 48 Docs string `xml:"docs"` 49 Explicit string `xml:"explicit"` 50 Image atomImage `xml:"image"` 51 Items []atomItem `xml:"item"` 52 Language string `xml:"language"` 53 Link string `xml:"link"` 54 PublicationDate string `xml:"pubDate"` 55 Summary string `xml:"summary"` 56 Title string `xml:"title"` 57 Subtitle string `xml:"subtitle"` 58 59 // Copyright string `xml:"copyright"` 60 // Generator string `xml:"generator"` 61 // Categories []string 62 // Image []string 63 // Owner []string 64 // ManagingEditor string 65 // LastBuildDate string 66 // Type string 67 } 68 69 // atomImage is a channel's thumbnail image/logo 70 type atomImage struct { 71 Title string `xml:"title"` 72 URL string `xml:"url"` 73 } 74 75 // atomItem is a link to a podcast episode or to an article 76 type atomItem struct { 77 Author string `xml:"author"` 78 Description string `xml:"description"` 79 Duration string `xml:"duration"` // media-duration as hh:mm:ss 80 81 Enclosures []atomEnclosure `xml:"enclosure"` 82 83 Episode int `xml:"episode"` 84 Explicit string `xml:"explicit"` 85 PublicationDate string `xml:"pubDate"` 86 Summary string `xml:"summary"` 87 Title string `xml:"title"` 88 89 // Keywords []string // not sure these array items are strings 90 } 91 92 // atomEnclosure is an item's link, along with some useful metadata 93 type atomEnclosure struct { 94 Length int `xml:"length"` // seems to be the media filesize 95 Type string `xml:"type"` // MIME type for the media file 96 URL string `xml:"url"` // the URL for the media file 97 98 // special iTunes attributes 99 AttrLength int `xml:"length,attr"` 100 AttrType string `xml:"type,attr"` 101 AttrURL string `xml:"url,attr"` 102 AttrDuration string `xml:"duration,attr"` 103 } 104 105 // parseAtom decodes podcast/feed info from the bytes given 106 func parseAtom(b []byte) (atomFeed, error) { 107 var wrap atomFeed 108 if !bytes.Contains(b, []byte(`itunes:`)) { 109 err := xml.Unmarshal(b, &wrap) 110 return wrap, err 111 } 112 113 b = bytes.ReplaceAll(b, []byte(`<itunes:`), []byte{'<'}) 114 b = bytes.ReplaceAll(b, []byte(`</itunes:`), []byte{'<', '/'}) 115 err := xml.Unmarshal(b, &wrap) 116 return wrap, err 117 } File: ./podfeed/config.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package podfeed 26 27 import ( 28 "bufio" 29 "flag" 30 "fmt" 31 "io" 32 "os" 33 "strings" 34 ) 35 36 const ( 37 titleUsage = `title for the HTML result` 38 itemLimitUsage = `max items shown per feed, starting from latest; negative to disable` 39 thumbnailsUsage = `show channel/podcast thumbnails` 40 inlineUsage = `inline/embed thumbnails as base64 data` 41 ) 42 43 // config has all the cmd-line options: each has its own default value, but 44 // can be explicitly set via one of the cmd-line flags 45 type config struct { 46 Feeds []string 47 Title string 48 ItemLimit int 49 Thumbnails bool 50 Inline bool 51 } 52 53 func parseFlags(usage string) config { 54 cfg := config{ 55 Title: `Latest Podcast Episodes`, 56 ItemLimit: -1, 57 Thumbnails: true, 58 Inline: true, 59 } 60 61 flag.Usage = func() { 62 fmt.Fprintf(flag.CommandLine.Output(), "%s\n\nOptions\n\n", usage) 63 flag.PrintDefaults() 64 } 65 flag.StringVar(&cfg.Title, `title`, cfg.Title, titleUsage) 66 flag.IntVar(&cfg.ItemLimit, `max`, cfg.ItemLimit, itemLimitUsage) 67 flag.BoolVar(&cfg.Thumbnails, `thumbs`, cfg.Thumbnails, thumbnailsUsage) 68 flag.BoolVar(&cfg.Inline, `inline`, cfg.Inline, inlineUsage) 69 flag.Parse() 70 71 for _, a := range flag.Args() { 72 if strings.HasPrefix(a, `https://`) || strings.HasPrefix(a, `http://`) { 73 // it's a URI feed 74 cfg.Feeds = append(cfg.Feeds, a) 75 continue 76 } 77 78 // it's a text file with feed URIs, one per line 79 lines, err := slurpFileLines(a) 80 if err != nil { 81 fmt.Fprintln(os.Stderr, err.Error()) 82 continue 83 } 84 cfg.Feeds = append(cfg.Feeds, lines...) 85 } 86 87 // if not given any filenames/URIs, read URIs from stdin 88 if flag.NArg() == 0 { 89 lines, err := slurpLines(os.Stdin) 90 if err == nil { 91 cfg.Feeds = append(cfg.Feeds, lines...) 92 } else { 93 fmt.Fprintln(os.Stderr, err.Error()) 94 } 95 } 96 97 return cfg 98 } 99 100 func slurpFileLines(fname string) ([]string, error) { 101 f, err := os.Open(fname) 102 if err != nil { 103 return nil, err 104 } 105 defer f.Close() 106 return slurpLines(f) 107 } 108 109 func slurpLines(r io.Reader) ([]string, error) { 110 var lines []string 111 const maxbufsize = 8 * 1024 * 1024 * 1024 112 sc := bufio.NewScanner(r) 113 sc.Buffer(nil, maxbufsize) 114 115 for sc.Scan() { 116 err := sc.Err() 117 if err != nil { 118 return lines, err 119 } 120 121 s := strings.TrimSpace(sc.Text()) 122 // ignore empty lines and comment lines 123 if s == `` || strings.HasPrefix(s, `#`) { 124 continue 125 } 126 127 lines = append(lines, s) 128 } 129 130 return lines, nil 131 } File: ./podfeed/feeds.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package podfeed 26 27 import ( 28 "errors" 29 "fmt" 30 "io" 31 "strconv" 32 "strings" 33 "time" 34 ) 35 36 // parseFeed takes raw RSS-string bytes and makes a feed object out of them 37 func parseFeed(b []byte) (feed, error) { 38 atom, err := parseAtom(b) 39 if err != io.EOF && err != nil { 40 return feed{}, err 41 } 42 43 if len(atom.Channels) == 0 { 44 return feed{}, errors.New(`feed has no channels`) 45 } 46 if len(atom.Channels) > 1 { 47 const msg = `multiple channels in a single feed aren't supported` 48 return feed{}, errors.New(msg) 49 } 50 51 var feed feed 52 ch := atom.Channels[0] 53 feed.Title = ch.Title 54 feed.Link = strings.Replace(ch.Link, `http://`, `https://`, 1) 55 feed.ImageLink = ch.Image.URL 56 feed.Description = clean(ch.Description) 57 58 for _, v := range ch.Items { 59 if len(v.Enclosures) == 0 { 60 continue 61 } 62 feed.Items = append(feed.Items, adaptItem(v)) 63 } 64 return feed, nil 65 } 66 67 // feed is a template-friendly representation of a parsed podcast feed 68 type feed struct { 69 Title string 70 Link string 71 ImageLink string 72 Description string 73 74 Items []item 75 } 76 77 // item is a template-friendly representation of a podcast episode 78 type item struct { 79 Title string 80 Link string 81 Tooltip string 82 Description string 83 } 84 85 // adaptItem makes a podcast episodes's info more template-friendly 86 func adaptItem(v atomItem) item { 87 tooltip := `` 88 duration := v.Duration 89 // if duration is in seconds, turn it into the hh:mm:ss format 90 if !strings.Contains(duration, `:`) { 91 n, err := strconv.Atoi(duration) 92 if err == nil && n > 0 { 93 duration = (time.Duration(n) * time.Second).String() 94 } 95 } 96 97 if duration != `` && v.PublicationDate != `` { 98 const fs = `published: %s | duration: %s` 99 tooltip = fmt.Sprintf(fs, v.PublicationDate, duration) 100 } 101 if duration == `` && v.PublicationDate != `` { 102 tooltip = fmt.Sprintf(`published: %s`, v.PublicationDate) 103 } 104 if duration != `` && v.PublicationDate == `` { 105 tooltip = fmt.Sprintf(`duration: %s`, v.PublicationDate) 106 } 107 108 enc := v.Enclosures[0] 109 return item{ 110 Title: v.Title, 111 Link: notEmptyOr(enc.URL, enc.AttrURL), 112 Tooltip: tooltip, 113 Description: clean(v.Description), 114 } 115 } File: ./podfeed/fetch.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package podfeed 26 27 import ( 28 "io" 29 "net/http" 30 "runtime" 31 "strings" 32 "sync" 33 ) 34 35 // Result is the payload/error combo resulting from trying to fetch a feed. 36 type Result struct { 37 Index int 38 URI string 39 40 Feed feed 41 Problem error 42 } 43 44 // fetch tries to fetch all podcast feeds concurrently, to save time 45 func fetch(cfg config) []Result { 46 var wg sync.WaitGroup 47 wg.Add(len(cfg.Feeds)) 48 49 // start rate-limiter up to the # of CPUs 50 tickets := make(chan int, runtime.NumCPU()) 51 go func() { 52 for i := range cfg.Feeds { 53 tickets <- i 54 } 55 56 // wait until fetcher loop below has finished dispatching all tasks 57 wg.Wait() 58 close(tickets) // quit the fetcher loop 59 }() 60 61 // setup parameters and final results array 62 res := make([]Result, len(cfg.Feeds)) 63 for i, uri := range cfg.Feeds { 64 res[i] = Result{Index: i, URI: uri, Feed: feed{}, Problem: nil} 65 } 66 67 // concurrently fetch feeds 68 for i := range tickets { 69 go fetchItem(&res[i], &wg, cfg) 70 } 71 return res 72 } 73 74 // fetchItem is concurrently called/dispatched to try to fetch and decode a 75 // single podcast feed: any error along the way is remembered as part of the 76 // result, so the user can later be told about it 77 func fetchItem(r *Result, wg *sync.WaitGroup, cfg config) { 78 defer wg.Done() 79 80 // read RSS feed 81 b, err := slurp(r.URI) 82 if err != nil { 83 r.Problem = err 84 return 85 } 86 87 // extract most important RSS info 88 f, err := parseFeed(b) 89 if err != nil { 90 r.Problem = err 91 return 92 } 93 // r.Feed = newFeed(f) 94 r.Feed = f 95 96 if !cfg.Thumbnails { 97 // to hide thumbnails, use a no-data URI 98 r.Feed.ImageLink = `data,` 99 return 100 } 101 102 if !cfg.Inline { 103 // if asked to, keep images as externally-linked resources 104 return 105 } 106 107 // read image thumbnail 108 b, err = slurp(f.ImageLink) 109 if err != nil { 110 r.Problem = err 111 return 112 } 113 114 mime := `image/jpeg` 115 if strings.Contains(f.ImageLink, `.png`) { 116 mime = `image/png` 117 } 118 119 // data-URI-encode thumbnail, so it's part of the resulting webpage 120 s, err := makeDataURI(b, mime) 121 if err != nil { 122 r.Problem = err 123 return 124 } 125 r.Feed.ImageLink = s 126 } 127 128 func slurp(uri string) ([]byte, error) { 129 resp, err := http.Get(uri) 130 if err != nil { 131 return nil, err 132 } 133 defer resp.Body.Close() 134 return io.ReadAll(resp.Body) 135 } File: ./podfeed/info.txt 1 podfeed [URIs/filenames...] 2 3 Keep track of what's on multiple podcasts/RSS feeds with auto-popup links and 4 collapsible descriptions. 5 6 After fetching all RSS feeds, this program emits script-free HTML code for a 7 standalone webpage with links to all feed items, each having expandable 8 descriptions. 9 10 The cmd-line arguments can be a mix of direct URIs to podcast/RSS feeds and 11 filenames: in any files given, each line is taken as a URI to check, unless 12 the line is empty or starts with #, which marks it as a comment line. File: ./podfeed/main.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package podfeed 26 27 import ( 28 "bufio" 29 "fmt" 30 "html/template" 31 "os" 32 33 _ "embed" 34 ) 35 36 //go:embed template.html 37 var src string 38 39 //go:embed info.txt 40 var usage string 41 42 // enable inlining/embedding thumbnails into page 43 var funcs = template.FuncMap{ 44 `url`: func(s string) template.URL { 45 return template.URL(s) 46 }, 47 } 48 49 var pageTemplate = template.Must(template.New(`main`).Funcs(funcs).Parse(src)) 50 51 // result is the payload given to the page template 52 type result struct { 53 Title string 54 Feeds []feed 55 } 56 57 func Main() { 58 cfg := parseFlags(usage) 59 60 // fetch feeds concurrently 61 res := fetch(cfg) 62 63 // show which podcasts/feeds caused problems, and keep only the ones 64 // which were loaded successfully 65 page := result{Title: cfg.Title} 66 for _, v := range res { 67 if v.Problem != nil { 68 fmt.Fprintln(os.Stderr, v.Problem.Error()) 69 continue 70 } 71 72 // limit feed's item-length, unless length-limiting was disabled via 73 // a negative value 74 if cfg.ItemLimit >= 0 && len(v.Feed.Items) > cfg.ItemLimit { 75 v.Feed.Items = v.Feed.Items[:cfg.ItemLimit] 76 } 77 page.Feeds = append(page.Feeds, v.Feed) 78 } 79 80 // render HTML result to standard output 81 w := bufio.NewWriter(os.Stdout) 82 defer w.Flush() 83 pageTemplate.Execute(w, page) 84 } File: ./podfeed/strings.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package podfeed 26 27 import ( 28 "encoding/base64" 29 "fmt" 30 "math" 31 "regexp" 32 "strings" 33 ) 34 35 // note: the tag matcher can't rid anchor tags of inner tags in its content 36 const tagRE = `</?[a-z][a-z1-6]*( +[a-z]+ *= *"[a-z A-Z0-9-]*")*( /)?>` 37 38 // regex to match opening/closing HTML tags, used in function `clean`; the 39 // first letter explicitly excludes a, to avoid matching/replacing anchor tags 40 var tagMatcher = regexp.MustCompile(tagRE) 41 42 // regex to match ampersand escapes, used in function `clean` 43 var ampersandMatcher = regexp.MustCompile(`&[a-zA-Z]+;`) 44 45 var ampersandEscapes = map[string]string{ 46 ` `: ` `, 47 `&`: `&`, 48 `<`: `<`, 49 `>`: `>`, 50 } 51 52 // clean improves the content of descriptions, by removing typical markup 53 // junk often found in RSS feeds 54 func clean(s string) string { 55 s = tagMatcher.ReplaceAllStringFunc(s, func(s string) string { 56 if strings.HasPrefix(s, `<a `) { 57 return s 58 } 59 60 switch s { 61 case `</a>`: 62 return `</a>` 63 case `<br/>`, `<br />`: 64 return "\n" 65 default: 66 return `` 67 } 68 }) 69 70 s = ampersandMatcher.ReplaceAllStringFunc(s, func(s string) string { 71 sub, ok := ampersandEscapes[s] 72 if ok { 73 return sub 74 } 75 return s 76 }) 77 78 return s 79 } 80 81 // makeDataURI encodes the bytes given into a MIME-typed base64-encoded URI 82 func makeDataURI(b []byte, mime string) (string, error) { 83 var buf strings.Builder 84 base64len := int(math.Ceil(4 * float64(len(b)) / 3)) 85 buf.Grow(len(`data:`) + len(mime) + len(`;base64,`) + base64len) 86 fmt.Fprintf(&buf, `data:%s;base64,`, mime) 87 88 enc := base64.NewEncoder(base64.StdEncoding, &buf) 89 defer enc.Close() 90 91 _, err := enc.Write(b) 92 if err != nil { 93 return ``, err 94 } 95 return buf.String(), nil 96 } 97 98 // notEmptyOr simplifies control flow around this app 99 func notEmptyOr(s, fallback string) string { 100 if len(s) > 0 { 101 return s 102 } 103 return fallback 104 } File: ./podfeed/template.html 1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <link rel="icon" href="data:,"> 8 <title>{{ .Title }}</title> 9 <style> 10 body { 11 font-size: 0.9rem; 12 margin: 0 0 2rem 0; 13 font-family: system-ui, -apple-system, sans-serif; 14 } 15 16 main { 17 margin: auto; 18 display: flex; 19 width: fit-content; 20 } 21 22 h1 { 23 top: 0; 24 position: sticky; 25 font-size: 0.9rem; 26 text-align: center; 27 background-color: white; 28 } 29 30 img { 31 margin: auto; 32 margin-bottom: 1rem; 33 display: block; 34 max-width: 15ch; 35 } 36 37 section { 38 width: 48ch; 39 padding: 0.3rem; 40 margin: 0 0.1rem; 41 } 42 43 section:nth-child(2n+1) { 44 background-color: #eee; 45 } 46 47 a { 48 color: steelblue; 49 text-decoration: none; 50 } 51 52 details p { 53 line-height: 1.3rem; 54 } 55 </style> 56 </head> 57 58 <body> 59 <main> 60 {{- range .Feeds }} 61 <article> 62 <h1> 63 <a target="_blank" rel="noreferrer" href="{{ .Link }}">{{ .Title }}</a> 64 </h1> 65 <img src="{{ .ImageLink | url }}"> 66 {{- range .Items }} 67 <section> 68 <details> 69 <summary title="{{ .Tooltip }}"> 70 <a target="_blank" rel="noreferrer" href="{{ .Link }}">{{ .Title }}</a> 71 </summary> 72 <p>{{ .Description }}</p> 73 </details> 74 </section> 75 {{- end }} 76 </article> 77 {{- end }} 78 </main> 79 </body> 80 81 </html> File: ./wports/wports.go 1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2026 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 package wports 26 27 import ( 28 "net" 29 "os" 30 "runtime" 31 "strconv" 32 "sync" 33 "time" 34 ) 35 36 const info = ` 37 wports [options...] 38 39 Which PORTS finds all localhost TCP ports currently in use. 40 41 The only option available is to show this help message, using any of 42 "-h", "--h", "-help", or "--help", without the quotes. 43 ` 44 45 const ( 46 // timeout gives plenty of waiting-time to check localhost ports 47 timeout = 500 * time.Millisecond 48 49 // lastPort = int(^uint16(0)) 50 51 lastPort = 1<<16 - 1 52 ) 53 54 // result values report which ports are currently being used 55 type result struct { 56 // When is the date/time the port was checked 57 When time.Time 58 59 // Port is the network-port number checked 60 Port int 61 } 62 63 func Main() { 64 args := os.Args[1:] 65 66 if len(args) > 0 { 67 switch args[0] { 68 case `-h`, `--h`, `-help`, `--help`: 69 os.Stdout.WriteString(info[1:]) 70 return 71 72 case `--`: 73 args = args[1:] 74 } 75 } 76 77 _, err := os.Stdout.WriteString("when\tport\n") 78 if err != nil { 79 return 80 } 81 82 results := make(chan result) 83 go run(results) 84 85 // buf is a buffer big enough for any output line 86 var buf [32]byte 87 88 for r := range results { 89 line := buf[:0] 90 line = r.When.AppendFormat(line, `2006-01-02 15:04:05`) 91 line = append(line, '\t') 92 line = strconv.AppendInt(line, int64(r.Port), 10) 93 line = append(line, '\n') 94 95 _, err := os.Stdout.Write(line) 96 if err != nil { 97 return 98 } 99 } 100 } 101 102 // run asynchronously dispatches tasks to check all ports 103 func run(results chan<- result) { 104 defer close(results) // allow the main app to end 105 106 var tasks sync.WaitGroup 107 // the number of tasks is always known in advance 108 tasks.Add(lastPort) 109 110 // permissions is buffered to limit concurrency to the core-count 111 permissions := make(chan struct{}, runtime.NumCPU()) 112 defer close(permissions) 113 114 // avoid checking port 0, as it's the `anything-available` port 115 for port := 1; port <= lastPort; port++ { 116 // wait until some concurrency-room is available, before proceeding 117 permissions <- struct{}{} 118 go check(port, &tasks, permissions, results) 119 } 120 121 // wait for all checks to finish, before closing the `results` channel, 122 // which in turn would quit the whole app right away 123 tasks.Wait() 124 } 125 126 // check figures out if the TCP port given is being used 127 func check(port int, tasks *sync.WaitGroup, turn <-chan struct{}, res chan<- result) { 128 when := time.Now() 129 defer tasks.Done() 130 131 var buf [24]byte 132 addr := strconv.AppendInt(append(buf[:0], ':'), int64(port), 10) 133 conn, err := net.DialTimeout(`tcp`, string(addr), timeout) 134 if err != nil { 135 <-turn 136 return 137 } 138 conn.Close() 139 140 <-turn 141 // only report ports being used 142 res <- result{When: when, Port: port} 143 }