summaryrefslogtreecommitdiff
path: root/bin/gemini
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2022-02-03 14:44:31 +0100
committerMarc Vertes <mvertes@free.fr>2022-02-03 14:44:31 +0100
commit7d5b2b5cde9f50b9c02f066a96799d9ae4430651 (patch)
treed96bead362540491d466ede0a6316418e8505fb3 /bin/gemini
parenta3a3a61742950d45f1573a45a928e9b04c115634 (diff)
parent70375b4962152133c0423115427d7eaa006822d6 (diff)
Merge branch 'master' of github.com:mvertes/dotfiles
Diffstat (limited to 'bin/gemini')
-rwxr-xr-xbin/gemini132
1 files changed, 132 insertions, 0 deletions
diff --git a/bin/gemini b/bin/gemini
new file mode 100755
index 0000000..1bc3d57
--- /dev/null
+++ b/bin/gemini
@@ -0,0 +1,132 @@
+#!/usr/bin/env yaegi
+package main
+
+import (
+ "bufio"
+ "crypto/tls"
+ "fmt"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+)
+
+func main() {
+ stdinReader := bufio.NewReader(os.Stdin)
+ var u string // URL
+ links := make([]string, 0, 100)
+ history := make([]string, 0, 100)
+ for {
+ fmt.Print("> ")
+ cmd, _ := stdinReader.ReadString('\n')
+ cmd = strings.TrimSpace(cmd)
+ // Command dispatch
+ switch strings.ToLower(cmd) {
+ case "": // Nothing
+ continue
+ case "q": // Quit
+ fmt.Println("Bye!")
+ os.Exit(0)
+ case "b": // Back
+ if len(history) < 2 {
+ fmt.Println("No history yet!")
+ continue
+ }
+ u = history[len(history)-2]
+ history = history[0 : len(history)-2]
+ default:
+ index, err := strconv.Atoi(cmd)
+ if err != nil {
+ // Treat this as a URL
+ u = cmd
+ if !strings.HasPrefix(u, "gemini://") {
+ u = "gemini://" + u
+ }
+ } else {
+ // Treat this as a menu lookup
+ u = links[index-1]
+ }
+ }
+ // Parse URL
+ parsed, err := url.Parse(u)
+ if err != nil {
+ fmt.Println("Error parsing URL!")
+ continue
+ }
+ // Connect to server
+ conn, err := tls.Dial("tcp", parsed.Host+":1965", &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ fmt.Println("Failed to connect: " + err.Error())
+ continue
+ }
+ defer conn.Close()
+ // Send request
+ conn.Write([]byte(u + "\r\n"))
+ // Receive and parse response header
+ reader := bufio.NewReader(conn)
+ responseHeader, err := reader.ReadString('\n')
+ parts := strings.Fields(responseHeader)
+ status, err := strconv.Atoi(parts[0][0:1])
+ meta := parts[1]
+ // Switch on status code
+ switch status {
+ case 1, 3, 6:
+ // No input, redirects or client certs
+ fmt.Println("Unsupported feature!")
+ case 2:
+ // Successful transaction
+ // text/* content only
+ if !strings.HasPrefix(meta, "text/") {
+ fmt.Println("Unsupported type " + meta)
+ continue
+ }
+ // Read everything
+ bodyBytes, err := ioutil.ReadAll(reader)
+ if err != nil {
+ fmt.Println("Error reading body")
+ continue
+ }
+ body := string(bodyBytes)
+ if meta == "text/gemini" {
+ // Handle Gemini map
+ links = make([]string, 0, 100)
+ preformatted := false
+ for _, line := range strings.Split(body, "\n") {
+ if strings.HasPrefix(line, "```") {
+ preformatted = !preformatted
+ } else if preformatted {
+ fmt.Println(line)
+ } else if strings.HasPrefix(line, "=>") {
+ line = line[2:]
+ bits := strings.Fields(line)
+ parsedLink, err := url.Parse(bits[0])
+ if err != nil {
+ continue
+ }
+ link := parsed.ResolveReference(parsedLink).String()
+ var label string
+ if len(bits) == 1 {
+ label = link
+ } else {
+ label = strings.Join(bits[1:], " ")
+ }
+ links = append(links, link)
+ fmt.Printf("[%d] %s\n", len(links), label)
+ } else {
+ // This should really be wrapped, but there's
+ // no easy support for this in Go's standard
+ // library
+ fmt.Println(line)
+ }
+ }
+ } else {
+ // Just print any other kind of text
+ fmt.Print(body)
+ }
+ history = append(history, u)
+ case 4, 5:
+ fmt.Println("ERROR: " + meta)
+ }
+ }
+}