Commit: 1b908ee91791c0a20dbe3d45fb47f169773ae171 Author: Vi Grey Date: 2023-05-18 07:31 UTC Summary: Initial Commit .gitignore | 1 + LICENSE | 24 ++++++++++++++++++++++ Makefile | 36 +++++++++++++++++++++++++++++++++ README.txt | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 +++ list.json | 11 ++++++++++ src/blanket-fort-webring.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/flags.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/http.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 331 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6417490 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2023, Vi Grey +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d06850 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +# Copyright (C) 2023, Vi Grey +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +PKG_NAME := blanket-fort-webring +CURRENTDIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +all: + mkdir -p $(CURRENTDIR)build/bin; \ + cd $(CURRENTDIR)src; \ + go build -ldflags="-s -w" -o $(CURRENTDIR)build/bin/$(PKG_NAME); \ + cd $(CURRENTDIR); \ + +clean: + rm -rf -- $(CURRENTDIR)build; \ diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..01e9058 --- /dev/null +++ b/README.txt @@ -0,0 +1,60 @@ +# Blanket Fort Webring + +This is a webring of cool and/or comfy websites (HTTP/S). It's +probably mostly going to have hackers and makers in it, but I'd love +to have visual artists, musicians, and other cool creators join the +webring. + + +## Want to join the webring? + +At the moment, I (Vi Grey) am the person who gets to decide whether or +not someone joins the webring. + +There are a few absolute requirements I have: +* Anyone who joins needs to be able to modify their front page to add + a webring navigator (the point of a webring after all is to make a + ring from site to site) +* The site should be mostly navigatable if Javascript is turned off on + the viewer's browser +* The site should be generally "self made" (We don't buy blanket forts + from a store, we make them!) +* The site should have at least some content that isn't a blog + +Even if you follow the requirements, there's no guarantee that I'll add +your site to the webring, so please be aware of that. I can also +remove a site from the webring for any reason, so please also be aware +of that. + +With that said, if you're still interested in joining the webring, you +can send me an email at vi [at] vigrey.com requesting me to add your +site. Please include something along the lines of "Webring site add +request" in the email subject so I can figure out what your email is +about. + + +## Put the webring navigator on your site + +``` +
+

Blanket Fort Webring

+ << Prev - + Random - + Full List - + Next >> +
+ What is this? +
+``` + +Replace {your-id} with your id in the webring. For instance, my id is +"vigrey", so my "next" link would be +"https://vigrey.com/webring/id/vigrey/next". You can find your id in +the "list.json" file in the root directory of this repository. + + +## Future Work + +Over time, I'd like to find ways to decentralize the webring so folks +don't have to rely on me and my webserver to host everything, but that's +a thing for future me to figure out. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a1ff6ca --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module blanket-fort-webring + +go 1.20 diff --git a/list.json b/list.json new file mode 100644 index 0000000..9011a36 --- /dev/null +++ b/list.json @@ -0,0 +1,11 @@ +[ + { + "id": "vigrey", + "url": "https://vigrey.com", + "rss": "https://vigrey.com/feed" + }, + { + "id": "mle-online", + "url": "https://mle-online.com/projects" + } +] diff --git a/src/blanket-fort-webring.go b/src/blanket-fort-webring.go new file mode 100644 index 0000000..34b2662 --- /dev/null +++ b/src/blanket-fort-webring.go @@ -0,0 +1,60 @@ +package main + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "math/big" + "os" +) + +const ( + VERSION = "0.0.1" +) + +var ( + webringEntries []webringEntry + webringLen int +) + +type webringEntry struct { + ID string `json:"id"` + URL string `json:"url"` +} + +func getWebringLink(id, action string) string { + for x := range webringEntries { + if webringEntries[x].ID == id { + switch action { + case "prev": + return webringEntries[(x+webringLen-1)%webringLen].URL + case "next": + return webringEntries[(x+1)%webringLen].URL + case "random": + if nBig, err := rand.Int(rand.Reader, big.NewInt(int64(webringLen)-1)); err != nil { + return "" + } else { + return webringEntries[(x+int(nBig.Int64())+1)%webringLen].URL + } + } + } + } + return "" +} + +func main() { + if err := handleFlags(os.Args[1:]); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + if webringList, err := os.ReadFile(listPath); err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("Unable to read --list path %s", listPath)) + os.Exit(1) + } else { + json.Unmarshal(webringList, &webringEntries) + } + webringLen = len(webringEntries) + if webringLen > 0 { + startHTTPServer() + } +} diff --git a/src/flags.go b/src/flags.go new file mode 100644 index 0000000..a2185a9 --- /dev/null +++ b/src/flags.go @@ -0,0 +1,87 @@ +package main + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" +) + +var ( + portNumber int + listPath string + help bool + version bool +) + +func handleFlags(args []string) (err error) { + for x := 0; x < len(args); x++ { + arg := args[x] + switch strings.ToLower(arg) { + case "--list", "-l": + if x == len(args) { + err = errors.New(fmt.Sprintf("blanket-fort-webring: flag `%s` missing value\nUse `blanket-fort-webring --help` for details", arg)) + return + } + x++ + if listPath == "" { + listPath = args[x] + } + case "--port", "-p": + if x == len(args) { + err = errors.New(fmt.Sprintf("blanket-fort-webring: flag `%s` missing value\nUse `blanket-fort-webring --help` for details", arg)) + return + } + x++ + if portNumber == 0 { + if portNumber, _ = strconv.Atoi(args[x]); portNumber == 0 { + portNumber = 0 + err = errors.New(fmt.Sprintf("blanket-fort-webring: invalid value for flag `%s`. Must be 1-65535\nUse `blanket-fort-webring --help` for details", arg)) + return + } + } + case "--help", "-h": + help = true + break + case "--version": + version = true + default: + err = errors.New(fmt.Sprintf("blanket-fort-webring: invalid argument `%s`\nUse `blanket-fort-webring --help` for details", arg)) + return + } + } + if help { + displayHelp() + os.Exit(0) + } + if version { + displayVersion() + os.Exit(0) + } + if listPath == "" { + err = errors.New("blanket-fort-webring: missing flag `--list`\nUse `blanket-fort-webring --help` for details") + return + } + if portNumber == 0 { + err = errors.New("blanket-fort-webring: missing flag `--port`\nUse `blanket-fort-webring --help` for details") + return + } + return +} + +func displayHelp() { + fmt.Printf("Usage: blanket-fort-webring [ OPTIONS ]...\n\nOptions:\n" + + " -h, --help Print Help (this message) and exit\n" + + " -l, --list Path of webring JSON list\n" + + " -p, --port Localhost port to run webring HTTP server on\n" + + " --version Print version information and exit\n\n" + + "Examples:\n" + + " blanket-fort-webring --port 9999 --list ~/list.json\n" + + " (This example starts blanket-fort-webring on 127.0.0.1:9999 and uses\n" + + " the webring json list at ~/list.json)\n") +} + +func displayVersion() { + fmt.Printf("blanket-fort-webring %s\n", VERSION) +} diff --git a/src/http.go b/src/http.go new file mode 100644 index 0000000..73e170b --- /dev/null +++ b/src/http.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "os" + "regexp" + "strconv" + "time" +) + +const ( + HTTP_DEFAULT_PORT = 80 +) + +var ( + webringLinkRe = regexp.MustCompile(`^/webring/id/(\S*)/(prev|next|random)$`) +) + +func catchAll(w http.ResponseWriter, r *http.Request) { + u := r.URL.Path + if len(u) > 0 { + if u[len(u)-1] == '/' { + u = u[:len(u)-1] + } + } + if webringReMatches := webringLinkRe.FindStringSubmatch(u); len(webringReMatches) > 2 { + webringLinkNew := getWebringLink(webringReMatches[1], webringReMatches[2]) + http.Redirect(w, r, webringLinkNew, http.StatusSeeOther) + } else { + w.WriteHeader(http.StatusNotFound) + } +} + +func startHTTPServer() { + mux := http.NewServeMux() + mux.HandleFunc("/", catchAll) + srv := &http.Server{ + ReadTimeout: 5 * time.Second, + Handler: mux, + } + if networkListener, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(portNumber)); err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("Unable to start HTTP server at 127.0.0.1:%d", portNumber)) + os.Exit(1) + } else { + srv.Serve(networkListener) + } +}