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
import (
"fmt"
"regexp"
"strconv"
)
type MailBoxConfiguration struct {
Server string
Port string
@@ -14,3 +20,56 @@ type BlogConfiguration struct {
MailBox MailBoxConfiguration
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 (
github.com/emersion/go-imap/v2 v2.0.0-beta.5
github.com/emersion/go-message v0.18.1
golang.org/x/net v0.6.0
)
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-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.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -3,16 +3,22 @@ package main
import (
"bytes"
"fmt"
"html/template"
"io"
"log"
"mime"
"strconv"
"strings"
/*
_ "golang.org/x/net/html"
_ "golang.org/x/net/html/atom"
*/
"github.com/emersion/go-imap/v2"
"github.com/emersion/go-imap/v2/imapclient"
"github.com/emersion/go-message/charset"
"github.com/emersion/go-message/mail"
"golang.org/x/net/html"
)
type MailBox struct {
@@ -82,7 +88,7 @@ func (mb *MailBox) ListFolders() ([]string, error) {
}
return folders, nil
}
func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
func (mb *MailBox) GetMessages() ([]Post, error) {
inbox, err := mb.Client.Select(mb.InBox, nil).Wait()
if err != nil {
@@ -93,9 +99,9 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
if inbox.NumMessages <= 0 {
return nil, fmt.Errorf("nomessages")
}
entries := make([]BlogEntry, 0)
entries := make([]Post, 0)
for i := uint32(1); i <= inbox.NumMessages; i++ {
var entry BlogEntry
var post Post
seqSet := imap.SeqSetNum(i)
bodySection := &imap.FetchItemBodySection{}
fetchOptions := &imap.FetchOptions{
@@ -106,14 +112,15 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
if err != nil {
log.Fatal("Error fetching mails:", err)
}
entry.Title = strings.Trim(messages[0].Envelope.Subject, "\t ")
if !strings.HasPrefix(strings.ToLower(entry.Title), "blog ") {
post.Title = strings.Trim(messages[0].Envelope.Subject, "\t ")
if !strings.HasPrefix(strings.ToLower(post.Title), "blog ") {
continue
}
entry.Id = messages[0].Envelope.MessageID
entry.Title = entry.Title[5:]
entry.Author = messages[0].Envelope.From[0].Name
post.Id = messages[0].Envelope.MessageID
post.Title = post.Title[5:] // remove first chars = blog+whitespace
post.Author = messages[0].Envelope.From[0].Name
post.Date = messages[0].Envelope.Date.String()
section := messages[0].FindBodySection(bodySection)
ioReader := bytes.NewReader(section)
@@ -148,9 +155,33 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
}
switch mediaType {
case "text/plain":
entry.Text = string(body)
post.Text = string(body)
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:
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)
}
}
entries = append(entries, entry)
posts = append(posts, post)
}
return entries, nil
}
@@ -168,3 +199,22 @@ func (mb *MailBox) GetMessages() ([]BlogEntry, error) {
func (mb *MailBox) 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
import (
_ "embed"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"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() {
@@ -38,56 +40,7 @@ func main() {
if len(args) > 0 {
switch args[0] {
case "configure":
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)
}
configuration.Prompt()
file, err = os.Create(fmt.Sprintf("%s/.config/mailblog.json", home))
if err != nil {
log.Fatal(err)
@@ -108,16 +61,19 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer mb.Close()
folders, err := mb.ListFolders()
if err != nil {
log.Fatal(err)
}
log.Println(folders)
msgs, err := mb.GetMessages()
messages, err := mb.GetMessages()
if err != nil {
log.Fatal(err)
}
/*
for _, m := range msgs {
log.Println(m.Id)
log.Println(m.Title)
@@ -129,9 +85,46 @@ func main() {
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)
}

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
}