A webmail client. Forked from https://git.sr.ht/~migadu/alps
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

115 lines
2.4 KiB

  1. package koushinbase
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "strings"
  7. "time"
  8. "github.com/emersion/go-message/mail"
  9. "github.com/emersion/go-smtp"
  10. )
  11. func quote(r io.Reader) (string, error) {
  12. scanner := bufio.NewScanner(r)
  13. var builder strings.Builder
  14. for scanner.Scan() {
  15. builder.WriteString("> ")
  16. builder.Write(scanner.Bytes())
  17. builder.WriteString("\n")
  18. }
  19. if err := scanner.Err(); err != nil {
  20. return "", fmt.Errorf("quote: failed to read original message: %s", err)
  21. }
  22. return builder.String(), nil
  23. }
  24. type OutgoingMessage struct {
  25. From string
  26. To []string
  27. Subject string
  28. InReplyTo string
  29. Text string
  30. }
  31. func (msg *OutgoingMessage) ToString() string {
  32. return strings.Join(msg.To, ", ")
  33. }
  34. func (msg *OutgoingMessage) WriteTo(w io.Writer) error {
  35. from := []*mail.Address{{"", msg.From}}
  36. to := make([]*mail.Address, len(msg.To))
  37. for i, addr := range msg.To {
  38. to[i] = &mail.Address{"", addr}
  39. }
  40. var h mail.Header
  41. h.SetDate(time.Now())
  42. h.SetAddressList("From", from)
  43. h.SetAddressList("To", to)
  44. if msg.Subject != "" {
  45. h.SetText("Subject", msg.Subject)
  46. }
  47. if msg.InReplyTo != "" {
  48. h.Set("In-Reply-To", msg.InReplyTo)
  49. }
  50. mw, err := mail.CreateWriter(w, h)
  51. if err != nil {
  52. return fmt.Errorf("failed to create mail writer: %v", err)
  53. }
  54. var th mail.InlineHeader
  55. th.Set("Content-Type", "text/plain; charset=utf-8")
  56. tw, err := mw.CreateSingleInline(th)
  57. if err != nil {
  58. return fmt.Errorf("failed to create text part: %v", err)
  59. }
  60. defer tw.Close()
  61. if _, err := io.WriteString(tw, msg.Text); err != nil {
  62. return fmt.Errorf("failed to write text part: %v", err)
  63. }
  64. if err := tw.Close(); err != nil {
  65. return fmt.Errorf("failed to close text part: %v", err)
  66. }
  67. if err := mw.Close(); err != nil {
  68. return fmt.Errorf("failed to close mail writer: %v", err)
  69. }
  70. return nil
  71. }
  72. func sendMessage(c *smtp.Client, msg *OutgoingMessage) error {
  73. if err := c.Mail(msg.From, nil); err != nil {
  74. return fmt.Errorf("MAIL FROM failed: %v", err)
  75. }
  76. for _, to := range msg.To {
  77. if err := c.Rcpt(to); err != nil {
  78. return fmt.Errorf("RCPT TO failed: %v", err)
  79. }
  80. }
  81. w, err := c.Data()
  82. if err != nil {
  83. return fmt.Errorf("DATA failed: %v", err)
  84. }
  85. defer w.Close()
  86. if err := msg.WriteTo(w); err != nil {
  87. return fmt.Errorf("failed to write outgoing message: %v", err)
  88. }
  89. if err := w.Close(); err != nil {
  90. return fmt.Errorf("failed to close SMTP data writer: %v", err)
  91. }
  92. return nil
  93. }