diff --git a/pkg/channels/email.go b/pkg/channels/email.go index 380ba04b0..29f2c63ff 100644 --- a/pkg/channels/email.go +++ b/pkg/channels/email.go @@ -19,11 +19,12 @@ import ( "github.com/emersion/go-imap/client" charset "github.com/emersion/go-message/charset" "github.com/emersion/go-message/mail" + "golang.org/x/text/encoding/simplifiedchinese" + "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/utils" - "golang.org/x/text/encoding/simplifiedchinese" ) func init() { @@ -227,9 +228,9 @@ func (c *EmailChannel) Send(ctx context.Context, msg bus.OutboundMessage) error defer client.Close() if err = client.StartTLS(&tls.Config{ServerName: host}); err != nil { // Some servers on 587 do not require STARTTLS; continue anyway - logger.WarnCF("email", "STARTTLS failed, connection may be unencrypted; credentials could be sent in plaintext", map[string]interface{}{ - "error": err.Error(), - }) + logger.WarnCF("email", + "STARTTLS failed, connection may be unencrypted; credentials could be sent in plaintext", + map[string]interface{}{"error": err.Error()}) _ = err } auth := smtp.PlainAuth("", c.config.Username, c.config.Password, host) @@ -291,7 +292,9 @@ func (c *EmailChannel) connect() error { status, err := cl.Select(mailbox, false) if err != nil { if strings.Contains(err.Error(), "Unsafe Login") || strings.Contains(err.Error(), "不安全") { - return fmt.Errorf("failed to select mailbox %s: %w (hint: 163/QQ/126 require app password, not account password)", mailbox, err) + return fmt.Errorf( + "failed to select mailbox %s: %w (hint: 163/QQ/126 require app password, not account password)", + mailbox, err) } return fmt.Errorf("failed to select mailbox %s: %w", mailbox, err) } @@ -832,7 +835,7 @@ func (c *EmailChannel) saveAttachmentToLocal(uid uint32, index int, filename str if dir == "" { return "" } - if err := os.MkdirAll(dir, 0700); err != nil { + if err := os.MkdirAll(dir, 0o700); err != nil { logger.DebugCF("email", "Failed to create attachment dir", map[string]interface{}{"error": err.Error(), "dir": dir}) return "" } diff --git a/pkg/channels/email_test.go b/pkg/channels/email_test.go index 56dac6a38..26f61b231 100644 --- a/pkg/channels/email_test.go +++ b/pkg/channels/email_test.go @@ -13,13 +13,13 @@ import ( "github.com/bytedance/mockey" "github.com/emersion/go-imap" "github.com/emersion/go-imap/client" + "github.com/stretchr/testify/assert" + "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" - "github.com/stretchr/testify/assert" ) func TestEmailChannel_sanitizeHeaderValue(t *testing.T) { - tests := []struct { name string s string @@ -61,6 +61,7 @@ func TestEmailChannel_parseFilenameFromDisposition(t *testing.T) { }) } } + func TestEmailChannel_decodeRFC2047Filename(t *testing.T) { tests := []struct { name string @@ -93,7 +94,8 @@ func TestEmailChannel_extractEmailBodyAndAttachments(t *testing.T) { }) t.Run("plain text body", func(t *testing.T) { - mimeBytes := []byte("From: a@b.com\r\nTo: c@d.com\r\nSubject: Test\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nHello world") + mimeBytes := []byte( + "From: a@b.com\r\nTo: c@d.com\r\nSubject: Test\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nHello world") section := &imap.BodySectionName{} msg := &imap.Message{ Uid: 1, @@ -172,7 +174,6 @@ func TestEmailChannel_extractEmailBodyAndAttachments(t *testing.T) { }, } t.Run("body part max bytes", func(t *testing.T) { - mimeBytes := attachmentwithText section := &imap.BodySectionName{} msg := &imap.Message{ @@ -189,7 +190,6 @@ func TestEmailChannel_extractEmailBodyAndAttachments(t *testing.T) { } func TestEmailChannel_extractTextFromHTML(t *testing.T) { - tests := []struct { name string s string @@ -282,24 +282,32 @@ func TestEmailChannel_checkNewEmails(t *testing.T) { mockey.Mock(mockey.GetMethod(mockClient, "UidSearch")).To(func(imapClient *client.Client, criteria *imap.SearchCriteria) ([]uint32, error) { return []uint32{21}, nil }).Build() - mockey.Mock(mockey.GetMethod(mockClient, "UidFetch")).To(func(imapClient *client.Client, seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error { - mimeBytes := []byte("From: a@b.com\r\nTo: c@d.com\r\nSubject: Test\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nHello world") - section := &imap.BodySectionName{} - msg := &imap.Message{ - Uid: 1, - Envelope: &imap.Envelope{Subject: "Test"}, - Body: map[*imap.BodySectionName]imap.Literal{section: bytes.NewReader(mimeBytes)}, - } - ch <- msg - close(ch) - return nil - }).Build() + mockey.Mock(mockey.GetMethod(mockClient, "UidFetch")).To( + func(imapClient *client.Client, seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error { + mimeBytes := []byte( + "From: a@b.com\r\nTo: c@d.com\r\nSubject: Test\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nHello world") + section := &imap.BodySectionName{} + msg := &imap.Message{ + Uid: 1, + Envelope: &imap.Envelope{Subject: "Test"}, + Body: map[*imap.BodySectionName]imap.Literal{section: bytes.NewReader(mimeBytes)}, + } + ch <- msg + close(ch) + return nil + }).Build() + mockClient.SetState(imap.SelectedState, &imap.MailboxStatus{ + UidNext: 20, + }) mockey.Mock(mockey.GetMethod(mockClient, "State")).To(func(*client.Client) imap.ConnState { return imap.SelectedState }).Build() - mockey.Mock(mockey.GetMethod(mockClient, "UidStore")).To(func(imapClient *client.Client, seqset *imap.SeqSet, item imap.StoreItem, value interface{}, ch chan *imap.Message) error { - return nil - }).Build() + mockey.Mock(mockey.GetMethod(mockClient, "UidStore")).To( + func(imapClient *client.Client, seqset *imap.SeqSet, item imap.StoreItem, value interface{}, + ch chan *imap.Message, + ) error { + return nil + }).Build() // --------------- mock end --------------- ctx := context.Background() c.CheckNewEmails(context.Background()) @@ -308,7 +316,6 @@ func TestEmailChannel_checkNewEmails(t *testing.T) { assert.True(t, ok) assert.True(t, strings.Contains(messge.Content, "Hello world")) }) - } func TestEmailChannel_runIdleLoop(t *testing.T) { @@ -322,7 +329,7 @@ func TestEmailChannel_runIdleLoop(t *testing.T) { // mock connect to return mockClient mockey.PatchConvey("runIdleLoop", t, func() { // --------------- mock start --------------- - var hasCheckEmail = false + hasCheckEmail := false c := &EmailChannel{ BaseChannel: &BaseChannel{ bus: bus.NewMessageBus(), @@ -357,9 +364,7 @@ func TestEmailChannel_runIdleLoop(t *testing.T) { triggerChannel <- struct{}{} time.Sleep(time.Second) assert.True(t, hasCheckEmail) - }) - } func TestEmailChannel_lifecycleCheck(t *testing.T) { diff --git a/pkg/channels/manager.go b/pkg/channels/manager.go index 004042bf6..75bc5f9d8 100644 --- a/pkg/channels/manager.go +++ b/pkg/channels/manager.go @@ -179,9 +179,8 @@ func (m *Manager) initChannels() error { logger.DebugC("channels", "Attempting to initialize Email channel") email, err := NewEmailChannel(m.config.Channels.Email, m.bus) if err != nil { - logger.ErrorCF("channels", "Failed to initialize Email channel", map[string]interface{}{ - "error": err.Error(), - }) + logger.ErrorCF("channels", "Failed to initialize Email channel", + map[string]interface{}{"error": err.Error()}) } else { m.channels["email"] = email logger.InfoC("channels", "Email channel enabled successfully") diff --git a/pkg/config/config.go b/pkg/config/config.go index 50cd70d27..758d6895c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -468,7 +468,6 @@ type ExecConfig struct { CustomDenyPatterns []string `json:"custom_deny_patterns" env:"PICOCLAW_TOOLS_EXEC_CUSTOM_DENY_PATTERNS"` } - type ToolsConfig struct { Web WebToolsConfig `json:"web"` Cron CronToolsConfig `json:"cron"`