forked from tslocum/gmitohtml
Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Trevor Slocum | a23ad13fd8 | |
Trevor Slocum | f933e9dd1b | |
Trevor Slocum | 6c7b8ec1d3 | |
tslocum | 8e8eef57e2 | |
Aaron Fischer | 2fdec61b41 | |
Aaron Fischer | 0bec3c3eef | |
Aaron Fischer | 586b293bef |
|
@ -1,21 +0,0 @@
|
|||
image: golang:latest
|
||||
|
||||
stages:
|
||||
- validate
|
||||
- build
|
||||
|
||||
fmt:
|
||||
stage: validate
|
||||
script:
|
||||
- gofmt -l -s -e .
|
||||
- exit $(gofmt -l -s -e . | wc -l)
|
||||
|
||||
vet:
|
||||
stage: validate
|
||||
script:
|
||||
- go vet -composites=false ./...
|
||||
|
||||
test:
|
||||
stage: validate
|
||||
script:
|
||||
- go test -race -v ./...
|
|
@ -1,4 +1,8 @@
|
|||
1.0.3:
|
||||
1.0.5:
|
||||
- Add option ConvertImages (thanks to @f)
|
||||
- Fix connection issue affecting some sites
|
||||
|
||||
1.0.4:
|
||||
- Migrate to code.rocketnine.space
|
||||
|
||||
1.0.3:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# gmitohtml
|
||||
[![GoDoc](https://code.rocketnine.space/tslocum/godoc-static/-/raw/master/badge.svg)](https://docs.rocketnine.space/code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml)
|
||||
[![GoDoc](https://code.rocketnine.space/tslocum/godoc-static/raw/branch/master/badge.svg)](https://docs.rocketnine.space/code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml)
|
||||
[![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
|
||||
|
||||
[Gemini](https://gemini.circumlunar.space) to [HTML](https://en.wikipedia.org/wiki/HTML)
|
||||
|
@ -7,9 +7,13 @@ conversion tool and daemon
|
|||
|
||||
## Download
|
||||
|
||||
### PC
|
||||
|
||||
[**Download gmitohtml**](https://gmitohtml.rocketnine.space/download/?sort=name&order=desc)
|
||||
|
||||
gmitohtml is available on Android as [Xenia](https://code.rocketnine.space/tslocum/xenia).
|
||||
### Android
|
||||
|
||||
See [Xenia](https://code.rocketnine.space/tslocum/xenia).
|
||||
|
||||
## Compile
|
||||
|
||||
|
|
77
config.go
77
config.go
|
@ -1,77 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type certConfig struct {
|
||||
Cert string
|
||||
Key string
|
||||
|
||||
cert tls.Certificate
|
||||
}
|
||||
|
||||
type appConfig struct {
|
||||
Bookmarks map[string]string
|
||||
|
||||
Certs map[string]*certConfig
|
||||
}
|
||||
|
||||
var config = &appConfig{
|
||||
Bookmarks: make(map[string]string),
|
||||
|
||||
Certs: make(map[string]*certConfig),
|
||||
}
|
||||
|
||||
func defaultConfigPath() string {
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err == nil && homedir != "" {
|
||||
return path.Join(homedir, ".config", "gmitohtml", "config.yaml")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func readconfig(configPath string) error {
|
||||
if configPath == "" {
|
||||
return errors.New("file unspecified")
|
||||
}
|
||||
|
||||
configData, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var newConfig *appConfig
|
||||
err = yaml.Unmarshal(configData, &newConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config = newConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveConfig(configPath string) error {
|
||||
config.Bookmarks = gmitohtml.GetBookmarks()
|
||||
|
||||
out, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal configuration: %s", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(path.Dir(configPath), 0755) // Ignore error
|
||||
|
||||
err = ioutil.WriteFile(configPath, out, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save configuration to %s: %s", configPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
2
go.mod
2
go.mod
|
@ -2,4 +2,4 @@ module code.rocketnine.space/tslocum/gmitohtml
|
|||
|
||||
go 1.15
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.0-20210105161348-2e78108cf5f8
|
||||
require code.rocketnine.space/tslocum/ez v0.0.0-20210506054357-569018bd037a
|
||||
|
|
6
go.sum
6
go.sum
|
@ -1,4 +1,6 @@
|
|||
code.rocketnine.space/tslocum/ez v0.0.0-20210506054357-569018bd037a h1:Ug5hgK5sM7bdK1gEl/pNLYTtBpFUxCvSCGYkEbUqdmw=
|
||||
code.rocketnine.space/tslocum/ez v0.0.0-20210506054357-569018bd037a/go.mod h1:SQrM+bQ4eZdyAVTxuF2BNnyAnojHP6Kcmm2vMszoFWw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210105161348-2e78108cf5f8 h1:tH9C0MON9YI3/KuD+u5+tQrQQ8px0MrcJ/avzeALw7o=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210105161348-2e78108cf5f8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
|
17
main.go
17
main.go
|
@ -10,6 +10,7 @@ import (
|
|||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"code.rocketnine.space/tslocum/ez"
|
||||
"code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml"
|
||||
)
|
||||
|
||||
|
@ -46,8 +47,10 @@ func main() {
|
|||
// TODO option to include response header in page
|
||||
flag.Parse()
|
||||
|
||||
defaultConfig := defaultConfigPath()
|
||||
if configFile == "" {
|
||||
defaultConfig, err := ez.DefaultConfigPath("gmitohtml")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else if configFile == "" {
|
||||
configFile = defaultConfig
|
||||
}
|
||||
|
||||
|
@ -58,18 +61,18 @@ func main() {
|
|||
}
|
||||
|
||||
if configExists || configFile != defaultConfig {
|
||||
err := readconfig(configFile)
|
||||
err := ez.Deserialize(gmitohtml.Config, configFile)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read configuration file at %s: %v\nSee CONFIGURATION.md for information on configuring gmitohtml", configFile, err)
|
||||
}
|
||||
|
||||
for u, label := range config.Bookmarks {
|
||||
for u, label := range gmitohtml.Config.Bookmarks {
|
||||
gmitohtml.AddBookmark(u, label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for domain, cc := range config.Certs {
|
||||
for domain, cc := range gmitohtml.Config.Certs {
|
||||
certData, err := ioutil.ReadFile(cc.Cert)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load client certificate for domain %s: %s", domain, err)
|
||||
|
@ -88,9 +91,9 @@ func main() {
|
|||
|
||||
if daemon != "" {
|
||||
gmitohtml.SetOnBookmarksChanged(func() {
|
||||
config.Bookmarks = gmitohtml.GetBookmarks()
|
||||
gmitohtml.Config.Bookmarks = gmitohtml.GetBookmarks()
|
||||
|
||||
err := saveConfig(configFile)
|
||||
err := ez.Serialize(gmitohtml.Config, configFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package gmitohtml
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
type CertConfig struct {
|
||||
Cert string
|
||||
Key string
|
||||
|
||||
cert tls.Certificate
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Bookmarks map[string]string
|
||||
|
||||
// Convert image links to images instead of normal links
|
||||
ConvertImages bool
|
||||
|
||||
Certs map[string]*CertConfig
|
||||
}
|
||||
|
||||
var Config = &AppConfig{
|
||||
Bookmarks: make(map[string]string),
|
||||
|
||||
Certs: make(map[string]*CertConfig),
|
||||
}
|
|
@ -19,46 +19,53 @@ var daemonAddress string
|
|||
|
||||
var assetLock sync.Mutex
|
||||
|
||||
var imageExtensions = []string{"png", "jpg", "jpeg", "gif", "svg", "webp"}
|
||||
|
||||
func rewriteURL(u string, loc *url.URL) string {
|
||||
if daemonAddress != "" {
|
||||
scheme := "gemini"
|
||||
if strings.HasPrefix(loc.Path, "/file/") {
|
||||
scheme = "file"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(u, "file://") {
|
||||
if !allowFileAccess {
|
||||
return "http://" + daemonAddress + "/?FileAccessNotAllowed"
|
||||
}
|
||||
return "http://" + daemonAddress + "/file/" + u[7:]
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if strings.HasPrefix(u, "gemini://") {
|
||||
offset = 9
|
||||
}
|
||||
firstSlash := strings.IndexRune(u[offset:], '/')
|
||||
if firstSlash != -1 {
|
||||
u = strings.ToLower(u[:firstSlash+offset]) + u[firstSlash+offset:]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(u, "gemini://") {
|
||||
return "http://" + daemonAddress + "/gemini/" + u[9:]
|
||||
} else if strings.Contains(u, "://") {
|
||||
return u
|
||||
} else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") {
|
||||
if u[0] != '/' {
|
||||
if loc.Path[len(loc.Path)-1] == '/' {
|
||||
u = path.Join("/", loc.Path, u)
|
||||
} else {
|
||||
u = path.Join("/", path.Dir(loc.Path), u)
|
||||
}
|
||||
}
|
||||
return "http://" + daemonAddress + "/" + scheme + "/" + strings.ToLower(loc.Host) + u
|
||||
}
|
||||
return "http://" + daemonAddress + "/" + scheme + "/" + u
|
||||
if daemonAddress == "" {
|
||||
return u
|
||||
}
|
||||
return u
|
||||
|
||||
if loc.Path == "" {
|
||||
loc.Path = "/"
|
||||
}
|
||||
|
||||
scheme := "gemini"
|
||||
if strings.HasPrefix(loc.Path, "/file/") {
|
||||
scheme = "file"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(u, "file://") {
|
||||
if !allowFileAccess {
|
||||
return "http://" + daemonAddress + "/?FileAccessNotAllowed"
|
||||
}
|
||||
return "http://" + daemonAddress + "/file/" + u[7:]
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if strings.HasPrefix(u, "gemini://") {
|
||||
offset = 9
|
||||
}
|
||||
firstSlash := strings.IndexRune(u[offset:], '/')
|
||||
if firstSlash != -1 {
|
||||
u = strings.ToLower(u[:firstSlash+offset]) + u[firstSlash+offset:]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(u, "gemini://") {
|
||||
return "http://" + daemonAddress + "/gemini/" + u[9:]
|
||||
} else if strings.Contains(u, "://") {
|
||||
return u
|
||||
} else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") {
|
||||
if u[0] != '/' {
|
||||
if loc.Path[len(loc.Path)-1] == '/' {
|
||||
u = path.Join("/", loc.Path, u)
|
||||
} else {
|
||||
u = path.Join("/", path.Dir(loc.Path), u)
|
||||
}
|
||||
}
|
||||
return "http://" + daemonAddress + "/" + scheme + "/" + strings.ToLower(loc.Host) + u
|
||||
}
|
||||
return "http://" + daemonAddress + "/" + scheme + "/" + u
|
||||
}
|
||||
|
||||
func newPage() []byte {
|
||||
|
@ -126,12 +133,25 @@ func Convert(page []byte, u string) []byte {
|
|||
linkLabel = line[splitStart:]
|
||||
}
|
||||
|
||||
link := append([]byte(`<a href="`), html.EscapeString(rewriteURL(string(linkURL), parsedURL))...)
|
||||
link = append(link, []byte(`">`)...)
|
||||
link = append(link, html.EscapeString(string(linkLabel))...)
|
||||
link = append(link, []byte(`</a>`)...)
|
||||
result = append(result, link...)
|
||||
result = append(result, []byte("<br>")...)
|
||||
parts := strings.Split(string(linkURL), ".")
|
||||
extension := parts[len(parts)-1]
|
||||
isImage := false
|
||||
for _, ext := range imageExtensions {
|
||||
if extension == ext {
|
||||
isImage = true
|
||||
}
|
||||
}
|
||||
|
||||
uri := html.EscapeString(rewriteURL(string(linkURL), parsedURL))
|
||||
title := html.EscapeString(string(linkLabel))
|
||||
|
||||
// If link ends with gif/png/jpg, add a image instead of a link
|
||||
if isImage && Config.ConvertImages {
|
||||
result = append(result, []byte("<img src=\""+uri+"\" alt=\""+title+"\">")...)
|
||||
} else {
|
||||
result = append(result, []byte("<a href=\""+uri+"\">"+title+"</a><br>")...)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue