mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
GODT-1650: Test of end-to-end send with attachments (internal)
This commit is contained in:
4
go.mod
4
go.mod
@ -5,7 +5,7 @@ go 1.18
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.1.1
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-rfc5322 v0.11.0
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.10
|
||||
@ -38,7 +38,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/urfave/cli/v2 v2.16.3
|
||||
gitlab.protontech.ch/go/liteapi v0.32.1
|
||||
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b
|
||||
golang.org/x/exp v0.0.0-20220921164117-439092de6870
|
||||
golang.org/x/net v0.1.0
|
||||
golang.org/x/sys v0.1.0
|
||||
|
||||
10
go.sum
10
go.sum
@ -31,6 +31,10 @@ github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkF
|
||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0 h1:SsacIP40QP64FNZrBlm5XDLHZMIx0i36mUYmTSWI2Y4=
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI=
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221006085838-c527b37bb418 h1:YvWdTvj2s+ZqEy0HwCIRFnGcWRhF0OY0EuIiknGChwM=
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221006085838-c527b37bb418/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI=
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d h1:EgrtTr3sx5cHkkd8/fviO5YOIcT9iOTibqJek2uhwM0=
|
||||
github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
@ -465,6 +469,12 @@ github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0
|
||||
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
|
||||
gitlab.protontech.ch/go/liteapi v0.32.1 h1:EiaLP+LkVVDqFxU6Uux4w5HHfPj6dSnOILJPzIny5l4=
|
||||
gitlab.protontech.ch/go/liteapi v0.32.1/go.mod h1:SVxEeF4uYYYpSlfeAj2ZqluVEP95pbZ8LyoieSxU0pM=
|
||||
gitlab.protontech.ch/go/liteapi v0.33.0 h1:lkerCG7Y3Tn+ICPwVkyZxPu5jJjiTTfaYiuqUwXRF0E=
|
||||
gitlab.protontech.ch/go/liteapi v0.33.0/go.mod h1:+70trwxSrBP1fU1m2q5wzhYkr9/NUrFGuBr6cKl3ixk=
|
||||
gitlab.protontech.ch/go/liteapi v0.33.1 h1:Ks8YdojRwYTLTUmGLG9MzFvxNeiwYJYQEaTHUQjOvsA=
|
||||
gitlab.protontech.ch/go/liteapi v0.33.1/go.mod h1:9nsslyEJn7Utbielp4c+hc7qT6hqIJ52aGFR/tX+tYk=
|
||||
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b h1:Obu2CCCYdVi3NJmoYf/iJco1mat6EzJezInKoUTo+Dc=
|
||||
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b/go.mod h1:9nsslyEJn7Utbielp4c+hc7qT6hqIJ52aGFR/tX+tYk=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
|
||||
@ -22,7 +22,7 @@ import (
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.protontech.ch/go/liteapi/server"
|
||||
"gitlab.protontech.ch/go/liteapi/server/account"
|
||||
"gitlab.protontech.ch/go/liteapi/server/backend"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -39,7 +39,7 @@ var (
|
||||
func init() {
|
||||
user.DefaultEventPeriod = 100 * time.Millisecond
|
||||
user.DefaultEventJitter = 0
|
||||
account.GenerateKey = tests.FastGenerateKey
|
||||
backend.GenerateKey = tests.FastGenerateKey
|
||||
certs.GenerateCert = tests.FastGenerateCert
|
||||
}
|
||||
|
||||
|
||||
@ -16,13 +16,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.protontech.ch/go/liteapi"
|
||||
"gitlab.protontech.ch/go/liteapi/server"
|
||||
"gitlab.protontech.ch/go/liteapi/server/account"
|
||||
"gitlab.protontech.ch/go/liteapi/server/backend"
|
||||
)
|
||||
|
||||
func init() {
|
||||
user.DefaultEventPeriod = 100 * time.Millisecond
|
||||
user.DefaultEventJitter = 0
|
||||
account.GenerateKey = tests.FastGenerateKey
|
||||
backend.GenerateKey = tests.FastGenerateKey
|
||||
certs.GenerateCert = tests.FastGenerateCert
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gluon/rfc822"
|
||||
"gitlab.protontech.ch/go/liteapi"
|
||||
"gitlab.protontech.ch/go/liteapi/server"
|
||||
)
|
||||
@ -23,8 +20,9 @@ type API interface {
|
||||
GetLabels(userID string) ([]liteapi.Label, error)
|
||||
CreateLabel(userID, name string, labelType liteapi.LabelType) (string, error)
|
||||
|
||||
GetMessages(userID string) ([]liteapi.Message, error)
|
||||
CreateMessage(userID, addrID string, labelIDs []string, subject string, sender *mail.Address, toList, ccList, bccList []*mail.Address, decBody string, mimeType rfc822.MIMEType, read, starred bool) (string, error)
|
||||
CreateMessage(userID, addrID string, literal []byte, flags liteapi.MessageFlag, unread, starred bool) (string, error)
|
||||
LabelMessage(userID, messageID, labelID string) error
|
||||
UnlabelMessage(userID, messageID, labelID string) error
|
||||
|
||||
Close()
|
||||
}
|
||||
|
||||
@ -28,12 +28,12 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/user"
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.protontech.ch/go/liteapi/server/account"
|
||||
"gitlab.protontech.ch/go/liteapi/server/backend"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Use the fast key generation for tests.
|
||||
account.GenerateKey = FastGenerateKey
|
||||
backend.GenerateKey = FastGenerateKey
|
||||
|
||||
// Use the fast cert generation for tests.
|
||||
certs.GenerateCert = FastGenerateCert
|
||||
|
||||
142
tests/features/smtp/send/one_account_to_another.feature
Normal file
142
tests/features/smtp/send/one_account_to_another.feature
Normal file
@ -0,0 +1,142 @@
|
||||
Feature: SMTP sending two messages
|
||||
Background:
|
||||
Given there exists an account with username "user@pm.me" and password "password"
|
||||
And there exists an account with username "other@pm.me" and password "other"
|
||||
And bridge starts
|
||||
And the user logs in with username "user@pm.me" and password "password"
|
||||
And the user logs in with username "other@pm.me" and password "other"
|
||||
|
||||
Scenario: Send from one account to the other
|
||||
When user "user@pm.me" connects and authenticates SMTP client "1"
|
||||
And SMTP client "1" sends the following message from "user@pm.me" to "other@pm.me":
|
||||
"""
|
||||
From: Bridge Test <user@pm.me>
|
||||
To: Internal Bridge <other@pm.me>
|
||||
Subject: One account to the other
|
||||
|
||||
hello
|
||||
|
||||
"""
|
||||
Then it succeeds
|
||||
And the body in the "POST" request to "/mail/v4/messages" is:
|
||||
"""
|
||||
{
|
||||
"Message": {
|
||||
"Subject": "One account to the other",
|
||||
"Sender": {
|
||||
"Name": "Bridge Test",
|
||||
"Address": "user@pm.me"
|
||||
},
|
||||
"ToList": [
|
||||
{
|
||||
"Name": "Internal Bridge",
|
||||
"Address": "other@pm.me"
|
||||
}
|
||||
],
|
||||
"CCList": [],
|
||||
"BCCList": [],
|
||||
"MIMEType": "text/plain"
|
||||
}
|
||||
}
|
||||
"""
|
||||
And the body in the "POST" request to "/mail/v4/messages/.*" is:
|
||||
"""
|
||||
{
|
||||
"Packages":[
|
||||
{
|
||||
"Addresses":{
|
||||
"other@pm.me":{
|
||||
"Type":1
|
||||
}
|
||||
},
|
||||
"Type":1,
|
||||
"MIMEType":"text/plain"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
When user "other@pm.me" connects and authenticates IMAP client "1"
|
||||
Then IMAP client "1" eventually sees the following messages in "Inbox":
|
||||
| from | to | subject | body |
|
||||
| user@pm.me | other@pm.me | One account to the other | hello |
|
||||
|
||||
Scenario: Send from one account to the other with attachments
|
||||
When user "user@pm.me" connects and authenticates SMTP client "1"
|
||||
And SMTP client "1" sends the following message from "user@pm.me" to "other@pm.me":
|
||||
"""
|
||||
From: Bridge Test <user@pm.me>
|
||||
To: Internal Bridge <other@pm.me>
|
||||
Subject: Plain with attachment internal
|
||||
Content-Type: multipart/related; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
|
||||
|
||||
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
|
||||
Content-Disposition: inline
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
This is the body
|
||||
|
||||
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
|
||||
Content-Disposition: attachment; filename=outline-light-instagram-48.png
|
||||
Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com>
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Type: image/png
|
||||
|
||||
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD/////////////////
|
||||
//////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd
|
||||
OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg
|
||||
WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv
|
||||
1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I
|
||||
BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6
|
||||
9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0
|
||||
YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP
|
||||
HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy
|
||||
KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg==
|
||||
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
|
||||
|
||||
"""
|
||||
Then it succeeds
|
||||
And the body in the "POST" request to "/mail/v4/messages" is:
|
||||
"""
|
||||
{
|
||||
"Message": {
|
||||
"Subject": "Plain with attachment internal",
|
||||
"Sender": {
|
||||
"Name": "Bridge Test"
|
||||
},
|
||||
"ToList": [
|
||||
{
|
||||
"Address": "other@pm.me",
|
||||
"Name": "Internal Bridge"
|
||||
}
|
||||
],
|
||||
"CCList": [],
|
||||
"BCCList": [],
|
||||
"MIMEType": "text/plain"
|
||||
}
|
||||
}
|
||||
"""
|
||||
And the body in the "POST" request to "/mail/v4/messages/.*" is:
|
||||
"""
|
||||
{
|
||||
"Packages":[
|
||||
{
|
||||
"Addresses":{
|
||||
"other@pm.me":{
|
||||
"Type":1
|
||||
}
|
||||
},
|
||||
"Type":1,
|
||||
"MIMEType":"text/plain"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
When user "user@pm.me" connects and authenticates IMAP client "1"
|
||||
Then IMAP client "1" eventually sees the following messages in "Sent":
|
||||
| from | to | subject | body | attachments | unread |
|
||||
| user@pm.me | other@pm.me | Plain with attachment internal | This is the body | outline-light-instagram-48.png | false |
|
||||
When user "other@pm.me" connects and authenticates IMAP client "2"
|
||||
Then IMAP client "2" eventually sees the following messages in "Inbox":
|
||||
| from | to | subject | body | attachments | unread |
|
||||
| user@pm.me | other@pm.me | Plain with attachment internal | This is the body | outline-light-instagram-48.png | true |
|
||||
@ -444,7 +444,7 @@ func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error)
|
||||
go func() {
|
||||
if err := client.Fetch(
|
||||
&imap.SeqSet{Set: []imap.Seq{{Start: 1, Stop: status.Messages}}},
|
||||
[]imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid},
|
||||
[]imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid, "BODY.PEEK[]"},
|
||||
resCh,
|
||||
); err != nil {
|
||||
panic(err)
|
||||
|
||||
@ -4,8 +4,12 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gluon/rfc822"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/message"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/cucumber/messages-go/v16"
|
||||
"github.com/emersion/go-imap"
|
||||
"golang.org/x/exp/slices"
|
||||
@ -13,6 +17,8 @@ import (
|
||||
|
||||
type Message struct {
|
||||
Subject string `bdd:"subject"`
|
||||
Body string `bdd:"body"`
|
||||
Attachments string `bdd:"attachments"`
|
||||
|
||||
From string `bdd:"from"`
|
||||
To string `bdd:"to"`
|
||||
@ -22,10 +28,60 @@ type Message struct {
|
||||
Unread bool `bdd:"unread"`
|
||||
}
|
||||
|
||||
func (msg Message) Build() []byte {
|
||||
var b []byte
|
||||
|
||||
if msg.From != "" {
|
||||
b = append(b, "From: "+msg.From+"\r\n"...)
|
||||
}
|
||||
|
||||
if msg.To != "" {
|
||||
b = append(b, "To: "+msg.To+"\r\n"...)
|
||||
}
|
||||
|
||||
if msg.CC != "" {
|
||||
b = append(b, "Cc: "+msg.CC+"\r\n"...)
|
||||
}
|
||||
|
||||
if msg.BCC != "" {
|
||||
b = append(b, "Bcc: "+msg.BCC+"\r\n"...)
|
||||
}
|
||||
|
||||
if msg.Subject != "" {
|
||||
b = append(b, "Subject: "+msg.Subject+"\r\n"...)
|
||||
}
|
||||
|
||||
if msg.Body != "" {
|
||||
b = append(b, "\r\n"+msg.Body+"\r\n"...)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func newMessageFromIMAP(msg *imap.Message) Message {
|
||||
section, err := imap.ParseBodySectionName("BODY[]")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
m, err := message.Parse(msg.GetBody(section))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var body string
|
||||
|
||||
if m.MIMEType == rfc822.TextPlain {
|
||||
body = strings.TrimSpace(string(m.PlainBody))
|
||||
} else {
|
||||
body = strings.TrimSpace(string(m.RichBody))
|
||||
}
|
||||
|
||||
message := Message{
|
||||
Subject: msg.Envelope.Subject,
|
||||
Unread: slices.Contains(msg.Flags, imap.SeenFlag),
|
||||
Body: body,
|
||||
Attachments: strings.Join(xslices.Map(m.Attachments, func(att message.Attachment) string { return att.Name }), ", "),
|
||||
Unread: !slices.Contains(msg.Flags, imap.SeenFlag),
|
||||
}
|
||||
|
||||
if len(msg.Envelope.From) > 0 {
|
||||
@ -48,6 +104,14 @@ func newMessageFromIMAP(msg *imap.Message) Message {
|
||||
}
|
||||
|
||||
func matchMessages(have, want []Message) error {
|
||||
slices.SortFunc(have, func(a, b Message) bool {
|
||||
return a.Subject < b.Subject
|
||||
})
|
||||
|
||||
slices.SortFunc(want, func(a, b Message) bool {
|
||||
return a.Subject < b.Subject
|
||||
})
|
||||
|
||||
if !IsSub(ToAny(have), ToAny(want)) {
|
||||
return fmt.Errorf("missing messages: %v", want)
|
||||
}
|
||||
|
||||
@ -4,10 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/gluon/rfc822"
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/google/uuid"
|
||||
"gitlab.protontech.ch/go/liteapi"
|
||||
@ -120,20 +118,12 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address,
|
||||
}
|
||||
|
||||
for _, wantMessage := range wantMessages {
|
||||
if _, err := s.t.api.CreateMessage(
|
||||
userID,
|
||||
addrID,
|
||||
[]string{mboxID},
|
||||
wantMessage.Subject,
|
||||
&mail.Address{Address: wantMessage.From},
|
||||
[]*mail.Address{{Address: wantMessage.To}},
|
||||
[]*mail.Address{{Address: wantMessage.CC}},
|
||||
[]*mail.Address{{Address: wantMessage.BCC}},
|
||||
"some body goes here",
|
||||
rfc822.TextPlain,
|
||||
wantMessage.Unread,
|
||||
false,
|
||||
); err != nil {
|
||||
messageID, err := s.t.api.CreateMessage(userID, addrID, wantMessage.Build(), liteapi.MessageFlagReceived, wantMessage.Unread, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.t.api.LabelMessage(userID, messageID, mboxID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -147,20 +137,17 @@ func (s *scenario) theAddressOfAccountHasMessagesInMailbox(address, username str
|
||||
mboxID := s.t.getMBoxID(userID, mailbox)
|
||||
|
||||
for idx := 0; idx < count; idx++ {
|
||||
if _, err := s.t.api.CreateMessage(
|
||||
userID,
|
||||
addrID,
|
||||
[]string{mboxID},
|
||||
fmt.Sprintf("subject %d", idx),
|
||||
&mail.Address{Address: fmt.Sprintf("sender %d", idx)},
|
||||
[]*mail.Address{{Address: fmt.Sprintf("recipient %d", idx)}},
|
||||
[]*mail.Address{},
|
||||
[]*mail.Address{},
|
||||
fmt.Sprintf("body %d", idx),
|
||||
rfc822.TextPlain,
|
||||
idx%2 == 0,
|
||||
false,
|
||||
); err != nil {
|
||||
messageID, err := s.t.api.CreateMessage(userID, addrID, Message{
|
||||
Subject: fmt.Sprintf("subject %d", idx),
|
||||
To: fmt.Sprintf("to %d", idx),
|
||||
From: fmt.Sprintf("from %d", idx),
|
||||
Body: fmt.Sprintf("body %d", idx),
|
||||
}.Build(), liteapi.MessageFlagReceived, idx%2 == 0, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.t.api.LabelMessage(userID, messageID, mboxID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user