From 67700bcb429d7df693b7c72311e10ff5ac018d65 Mon Sep 17 00:00:00 2001 From: Laurent Ulrich Date: Wed, 30 Jul 2025 20:33:58 +0200 Subject: [PATCH] =?UTF-8?q?petit=20m=C3=A9nage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blogentry.go | 9 --- configuration.go | 59 +++++++++++++++++ go.mod | 1 + go.sum | 1 + imap_handler.go => imap.go | 72 +++++++++++++++++---- mailblog.go | 129 ++++++++++++++++++------------------- post.go | 17 +++++ 7 files changed, 200 insertions(+), 88 deletions(-) delete mode 100644 blogentry.go rename imap_handler.go => imap.go (68%) create mode 100644 post.go diff --git a/blogentry.go b/blogentry.go deleted file mode 100644 index 748b581..0000000 --- a/blogentry.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -type BlogEntry struct { - Id string - Title string - Author string - HTML string - Text string -} diff --git a/configuration.go b/configuration.go index 2dfd0a9..5b34633 100644 --- a/configuration.go +++ b/configuration.go @@ -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 +} diff --git a/go.mod b/go.mod index d902668..9343c29 100644 --- a/go.mod +++ b/go.mod @@ -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 ( diff --git a/go.sum b/go.sum index 0df6c7a..4f61336 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/imap_handler.go b/imap.go similarity index 68% rename from imap_handler.go rename to imap.go index 74c01e6..f768ada 100644 --- a/imap_handler.go +++ b/imap.go @@ -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") +} diff --git a/mailblog.go b/mailblog.go index ccb9e3d..4f724c1 100644 --- a/mailblog.go +++ b/mailblog.go @@ -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,30 +61,70 @@ 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) - log.Println(m.Author) - log.Println("Text version") - log.Println(m.Text) - log.Println("HTML version") - log.Println(m.HTML) - log.Println(m.Id) - log.Println() - } + /* + for _, m := range msgs { + log.Println(m.Id) + log.Println(m.Title) + log.Println(m.Author) + log.Println("Text version") + log.Println(m.Text) + log.Println("HTML version") + log.Println(m.HTML) + 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) } diff --git a/post.go b/post.go new file mode 100644 index 0000000..10c7d7e --- /dev/null +++ b/post.go @@ -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 +}