petit ménage

This commit is contained in:
Laurent Ulrich
2025-07-30 20:33:58 +02:00
parent e9676f8136
commit 67700bcb42
7 changed files with 200 additions and 88 deletions

View File

@@ -1,9 +0,0 @@
package main
type BlogEntry struct {
Id string
Title string
Author string
HTML string
Text string
}

View File

@@ -1,5 +1,11 @@
package main package main
import (
"fmt"
"regexp"
"strconv"
)
type MailBoxConfiguration struct { type MailBoxConfiguration struct {
Server string Server string
Port string Port string
@@ -14,3 +20,56 @@ type BlogConfiguration struct {
MailBox MailBoxConfiguration MailBox MailBoxConfiguration
WWWRoot string WWWRoot string
} }
func (configuration *BlogConfiguration) Prompt() error {
var err error
configuration.Title, err = prompt("Blog Title", nil, "", configuration.Title, false)
if err != nil {
return err
}
configuration.ShortName, err = prompt("Blog Short Name (only letters and numbers, no special signes)", nil, "blog", configuration.ShortName, false)
if err != nil {
return err
}
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(configuration.ShortName) {
return fmt.Errorf("short same invalid")
}
configuration.WWWRoot, err = prompt("Web Root directory", nil, fmt.Sprintf("/var/www/html/%s", configuration.ShortName), configuration.WWWRoot, false)
if err != nil {
return err
}
configuration.MailBox.Server, err = prompt("IMAP Server Hostname or IP", nil, "", configuration.MailBox.Server, false)
if err != nil {
return err
}
configuration.MailBox.Port, err = prompt("IMAP Server Port", nil, "993", configuration.MailBox.Port, false)
if err != nil {
return err
}
_, err = strconv.ParseUint(configuration.MailBox.Port, 10, 16)
if err != nil {
return fmt.Errorf("imap server port invalid")
}
configuration.MailBox.User, err = prompt("IMAP User Login", nil, "", configuration.MailBox.User, false)
if err != nil {
return err
}
configuration.MailBox.Password, err = prompt("IMAP User Password", nil, "", configuration.MailBox.Password, false)
if err != nil {
return err
}
options := [...]string{"SSL/TLS", "StartTLS", "NoTLS"}
configuration.MailBox.SSL, err = prompt("IMAP SSL/TLS", options[:], "SSL/TLS", configuration.MailBox.SSL, false)
if err != nil {
return err
}
configuration.MailBox.InBox, err = prompt("IMAP INBOX", nil, "INBOX", configuration.MailBox.InBox, false)
if err != nil {
return err
}
return nil
}

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.23.2
require ( require (
github.com/emersion/go-imap/v2 v2.0.0-beta.5 github.com/emersion/go-imap/v2 v2.0.0-beta.5
github.com/emersion/go-message v0.18.1 github.com/emersion/go-message v0.18.1
golang.org/x/net v0.6.0
) )
require ( require (

1
go.sum
View File

@@ -12,6 +12,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -3,16 +3,22 @@ package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"html/template"
"io" "io"
"log" "log"
"mime" "mime"
"strconv" "strconv"
"strings" "strings"
/*
_ "golang.org/x/net/html"
_ "golang.org/x/net/html/atom"
*/
"github.com/emersion/go-imap/v2" "github.com/emersion/go-imap/v2"
"github.com/emersion/go-imap/v2/imapclient" "github.com/emersion/go-imap/v2/imapclient"
"github.com/emersion/go-message/charset" "github.com/emersion/go-message/charset"
"github.com/emersion/go-message/mail" "github.com/emersion/go-message/mail"
"golang.org/x/net/html"
) )
type MailBox struct { type MailBox struct {
@@ -82,7 +88,7 @@ func (mb *MailBox) ListFolders() ([]string, error) {
} }
return folders, nil return folders, nil
} }
func (mb *MailBox) GetMessages() ([]BlogEntry, error) { func (mb *MailBox) GetMessages() ([]Post, error) {
inbox, err := mb.Client.Select(mb.InBox, nil).Wait() inbox, err := mb.Client.Select(mb.InBox, nil).Wait()
if err != nil { if err != nil {
@@ -93,9 +99,9 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
if inbox.NumMessages <= 0 { if inbox.NumMessages <= 0 {
return nil, fmt.Errorf("nomessages") return nil, fmt.Errorf("nomessages")
} }
entries := make([]BlogEntry, 0) entries := make([]Post, 0)
for i := uint32(1); i <= inbox.NumMessages; i++ { for i := uint32(1); i <= inbox.NumMessages; i++ {
var entry BlogEntry var post Post
seqSet := imap.SeqSetNum(i) seqSet := imap.SeqSetNum(i)
bodySection := &imap.FetchItemBodySection{} bodySection := &imap.FetchItemBodySection{}
fetchOptions := &imap.FetchOptions{ fetchOptions := &imap.FetchOptions{
@@ -106,14 +112,15 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
if err != nil { if err != nil {
log.Fatal("Error fetching mails:", err) log.Fatal("Error fetching mails:", err)
} }
entry.Title = strings.Trim(messages[0].Envelope.Subject, "\t ") post.Title = strings.Trim(messages[0].Envelope.Subject, "\t ")
if !strings.HasPrefix(strings.ToLower(entry.Title), "blog ") { if !strings.HasPrefix(strings.ToLower(post.Title), "blog ") {
continue continue
} }
entry.Id = messages[0].Envelope.MessageID post.Id = messages[0].Envelope.MessageID
entry.Title = entry.Title[5:] post.Title = post.Title[5:] // remove first chars = blog+whitespace
entry.Author = messages[0].Envelope.From[0].Name post.Author = messages[0].Envelope.From[0].Name
post.Date = messages[0].Envelope.Date.String()
section := messages[0].FindBodySection(bodySection) section := messages[0].FindBodySection(bodySection)
ioReader := bytes.NewReader(section) ioReader := bytes.NewReader(section)
@@ -148,9 +155,33 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
} }
switch mediaType { switch mediaType {
case "text/plain": case "text/plain":
entry.Text = string(body) post.Text = string(body)
case "text/html": case "text/html":
entry.HTML = string(body) post.HTML = ""
nodes, err := html.Parse(strings.NewReader(string(body)))
if err != nil {
post.HTML = ""
break
}
htmlNode, err := findHTMLNode(nodes)
if err != nil {
post.HTML = ""
break
}
log.Println("Trying to generate html for", htmlNode.Data)
var buf bytes.Buffer
err = nil
for child := htmlNode.FirstChild; child != nil; child = child.NextSibling {
log.Println("html for", child.Data)
if err = html.Render(&buf, child); err != nil {
post.HTML = ""
break
}
}
if err == nil {
post.HTML = template.HTML(buf.String())
}
default: default:
log.Println("Content-Type:", part.Header.Get("Content-Type")) log.Println("Content-Type:", part.Header.Get("Content-Type"))
@@ -160,7 +191,7 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
log.Printf("Unkown part type %+v", part) log.Printf("Unkown part type %+v", part)
} }
} }
entries = append(entries, entry) posts = append(posts, post)
} }
return entries, nil return entries, nil
} }
@@ -168,3 +199,22 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
func (mb *MailBox) Close() { func (mb *MailBox) Close() {
mb.Client.Close() mb.Client.Close()
} }
func findHTMLNode(node *html.Node) (*html.Node, error) {
if node.Type == html.ElementNode {
log.Println(node.Data)
}
if node.Type == html.ElementNode && node.Data == "body" {
log.Println("Found", node.Data)
return node, nil
}
for e := node.FirstChild; e != nil; e = node.NextSibling {
log.Println("Search in", e.Data)
n, err := findHTMLNode(e)
if err == nil {
return n, nil
}
}
return nil, fmt.Errorf("notfound")
}

View File

@@ -1,18 +1,20 @@
package main package main
import ( import (
_ "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template"
"log" "log"
"net/http" "net/http"
"os" "os"
"regexp"
"strconv"
) )
func slash(w http.ResponseWriter, r *http.Request) { //go:embed html/index.tmpl
var indexTemplate string
} //go:embed html/entry.tmpl
var entryTemplate string
func main() { func main() {
@@ -38,56 +40,7 @@ func main() {
if len(args) > 0 { if len(args) > 0 {
switch args[0] { switch args[0] {
case "configure": case "configure":
configuration.Prompt()
configuration.Title, err = prompt("Blog Title", nil, "", configuration.Title, false)
if err != nil {
log.Fatal(err)
}
configuration.ShortName, err = prompt("Blog Short Name (only letters and numbers, no special signes)", nil, "blog", configuration.ShortName, false)
if err != nil {
log.Fatal(err)
}
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(configuration.ShortName) {
log.Fatal("Short Name invalid")
}
configuration.WWWRoot, err = prompt("Web Root directory", nil, fmt.Sprintf("/var/www/html/%s", configuration.ShortName), configuration.WWWRoot, false)
if err != nil {
log.Fatal(err)
}
configuration.MailBox.Server, err = prompt("IMAP Server Hostname or IP", nil, "", configuration.MailBox.Server, false)
if err != nil {
log.Fatal(err)
}
configuration.MailBox.Port, err = prompt("IMAP Server Port", nil, "993", configuration.MailBox.Port, false)
if err != nil {
log.Fatal(err)
}
_, err = strconv.ParseUint(configuration.MailBox.Port, 10, 16)
if err != nil {
log.Fatal("IMAP Server Port invalid")
}
configuration.MailBox.User, err = prompt("IMAP User Login", nil, "", configuration.MailBox.User, false)
if err != nil {
log.Fatal(err)
}
configuration.MailBox.Password, err = prompt("IMAP User Password", nil, "", configuration.MailBox.Password, false)
if err != nil {
log.Fatal(err)
}
options := [...]string{"SSL/TLS", "StartTLS", "NoTLS"}
configuration.MailBox.SSL, err = prompt("IMAP SSL/TLS", options[:], "SSL/TLS", configuration.MailBox.SSL, false)
if err != nil {
log.Fatal(err)
}
configuration.MailBox.InBox, err = prompt("IMAP INBOX", nil, "INBOX", configuration.MailBox.InBox, false)
if err != nil {
log.Fatal(err)
}
file, err = os.Create(fmt.Sprintf("%s/.config/mailblog.json", home)) file, err = os.Create(fmt.Sprintf("%s/.config/mailblog.json", home))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@@ -108,30 +61,70 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer mb.Close()
folders, err := mb.ListFolders() folders, err := mb.ListFolders()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Println(folders) log.Println(folders)
msgs, err := mb.GetMessages() messages, err := mb.GetMessages()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
for _, m := range msgs { /*
log.Println(m.Id) for _, m := range msgs {
log.Println(m.Title) log.Println(m.Id)
log.Println(m.Author) log.Println(m.Title)
log.Println("Text version") log.Println(m.Author)
log.Println(m.Text) log.Println("Text version")
log.Println("HTML version") log.Println(m.Text)
log.Println(m.HTML) log.Println("HTML version")
log.Println(m.Id) log.Println(m.HTML)
log.Println() log.Println(m.Id)
} log.Println()
}
*/
http.HandleFunc("GET /{$}",
func(w http.ResponseWriter, r *http.Request) {
tpl, err := template.New("index").Parse(indexTemplate)
if err != nil {
log.Fatal(err)
}
err = tpl.ExecuteTemplate(w, "index", messages)
if err != nil {
log.Fatal(err)
}
mb.Close() })
http.HandleFunc("GET /post/{id}",
func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
log.Println("showing message:", id)
for _, m := range messages {
log.Println("Examining:", m.Id)
if m.Id == id {
tpl, err := template.New("entry").Parse(entryTemplate)
if err != nil {
log.Fatal(err)
}
err = tpl.ExecuteTemplate(w, "entry", m)
if err != nil {
log.Fatal(err)
}
return
}
}
w.WriteHeader(404)
})
http.HandleFunc("GET /favicon.ico",
func(w http.ResponseWriter, r *http.Request) {
log.Println("favicon.ico")
w.WriteHeader(200)
})
http.HandleFunc("/", slash)
http.ListenAndServe("0.0.0.0:8080", nil) http.ListenAndServe("0.0.0.0:8080", nil)
} }

17
post.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import "html/template"
type Post struct {
Id string
Title string
Author string
Date string
HTML template.HTML
Text string
}
type Blog struct {
Title string
Posts []Post
}