gemini-demo-3/gemini-demo.go

132 lines
3.1 KiB
Go

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)
}
}
}