-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path1secmail.go
223 lines (196 loc) · 6.31 KB
/
1secmail.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package onesecmail
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
)
type mailboxAction int
const (
getMessages mailboxAction = iota
readMessage
download
genRandomMailbox
getDomainList
)
func (m mailboxAction) String() string {
return [...]string{
"getMessages", "readMessage", "download", "genRandomMailbox", "getDomainList",
}[m]
}
// Mail represents a mail in a 1secmail inbox.
type Mail struct {
ID int `json:"id"`
From string `json:"from"`
Subject string `json:"subject"`
Date string `json:"date"`
Attachments []Attachment `json:"attachments,omitempty"`
Body *string `json:"body,omitempty"`
TextBody *string `json:"textBody,omitempty"`
HTMLBody *string `json:"htmlBody,omitempty"`
}
// Attachment represents an attachment in a 1secmail mail.
type Attachment struct {
Filename string `json:"filename"`
ContentType string `json:"contentType"`
Size int `json:"size"`
}
// HTTPClient is an interface that makes an HTTP request.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// API manages communication with the 1secmail's APIs that do not belong to a specific mailbox.
type API struct {
client HTTPClient
}
// NewAPI returns a new API. If nil httpClient is provided, a new http.Client will be created.
func NewAPI(httpClient HTTPClient) API {
if httpClient == nil {
httpClient = http.DefaultClient
}
return API{client: httpClient}
}
func (a API) RandomAddresses(count int) ([]string, error) {
req := a.constructRequest("GET", genRandomMailbox, map[string]string{
"count": strconv.Itoa(count),
})
resp, err := a.client.Do(req)
if err != nil || (resp != nil && resp.StatusCode != 200) {
return nil, fmt.Errorf("generate random mailbox failed: %w", err)
}
defer resp.Body.Close()
var list []string
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
return nil, fmt.Errorf("decode JSON failed: %w", err)
}
return list, nil
}
func (a API) Domains() ([]string, error) {
req := a.constructRequest("GET", getDomainList, nil)
resp, err := a.client.Do(req)
if err != nil || (resp != nil && resp.StatusCode != 200) {
return nil, fmt.Errorf("get domain list failed: %w", err)
}
defer resp.Body.Close()
var list []string
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
return nil, fmt.Errorf("decode JSON failed: %w", err)
}
return list, nil
}
// UpdateDomains updates the list of domains that 1secmail supports.
// This is useful if the list of domains have changed since this library was last updated.
func (a API) UpdateDomains() error {
domains := make(map[string]struct{})
liveDomains, err := a.Domains()
if err != nil {
return err
}
for _, domain := range liveDomains {
domains[domain] = struct{}{}
}
domainsMu.Lock()
defer domainsMu.Unlock()
Domains = domains
return nil
}
// Mailbox manages communication with the 1secmail's APIs that belong to a specific mailbox.
type Mailbox struct {
Login string
Domain string
API
}
// Address returns the email address of a Mailbox.
func (m Mailbox) Address() string {
return fmt.Sprintf("%s@%s", m.Login, m.Domain)
}
// NewMailbox returns a new Mailbox. Use login and domain for the email
// handler that you intend to use. Login is the email username.
// If nil httpClient is provided, a new http.Client will be created.
func NewMailbox(login, domain string, httpClient HTTPClient) (Mailbox, error) {
if _, ok := Domains[domain]; !ok {
return Mailbox{}, fmt.Errorf("invalid domain: %s", domain)
}
return Mailbox{
API: NewAPI(httpClient),
Domain: domain,
Login: login,
}, nil
}
// NewMailboxWithAddress returns a new Mailbox. It accepts an email address
// that refers to a 1secmail mailbox. This is easier to use than NewMailbox
// if you already have an email address. If nil httpClient is provided, a
// new http.Client will be created.
func NewMailboxWithAddress(address string, httpClient HTTPClient) (Mailbox, error) {
login, domain, ok := strings.Cut(address, "@")
if !ok || login == "" || domain == "" {
return Mailbox{}, fmt.Errorf("invalid email address: %s", address)
}
return NewMailbox(login, domain, httpClient)
}
// CheckInbox checks the inbox of a mailbox, and returns a list of mails.
func (m Mailbox) CheckInbox() ([]*Mail, error) {
req := m.constructRequest("GET", getMessages, map[string]string{
"login": m.Login,
"domain": m.Domain,
})
resp, err := m.client.Do(req)
if err != nil || (resp != nil && resp.StatusCode != 200) {
return nil, fmt.Errorf("check inbox failed: %w, error code: %v", err, resp.StatusCode)
}
defer resp.Body.Close()
var mails []*Mail
if err := json.NewDecoder(resp.Body).Decode(&mails); err != nil {
return nil, fmt.Errorf("decode JSON failed: %w", err)
}
return mails, nil
}
// ReadMessage retrieves a particular mail from the inbox of a mailbox.
func (m Mailbox) ReadMessage(messageID int) (*Mail, error) {
req := m.constructRequest("GET", readMessage, map[string]string{
"login": m.Login,
"domain": m.Domain,
"id": strconv.Itoa(messageID),
})
resp, err := m.client.Do(req)
if err != nil || (resp != nil && resp.StatusCode != 200) {
return nil, fmt.Errorf("read message failed: %w", err)
}
defer resp.Body.Close()
var mail *Mail
if err := json.NewDecoder(resp.Body).Decode(&mail); err != nil {
return nil, fmt.Errorf("decode JSON failed: %w", err)
}
return mail, nil
}
func (m Mailbox) DownloadAttachment(messageID int, filename string) ([]byte, error) {
req := m.constructRequest("GET", download, map[string]string{
"login": m.Login,
"domain": m.Domain,
"id": strconv.Itoa(messageID),
"file": filename,
})
resp, err := m.client.Do(req)
if err != nil || (resp != nil && resp.StatusCode != 200) {
return nil, fmt.Errorf("download attachment failed: %w", err)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response body failed: %w", err)
}
return data, nil
}
func (a API) constructRequest(method string, action mailboxAction, args map[string]string) *http.Request {
const apiBase = "https://www.1secmail.com/api/v1/"
req, _ := http.NewRequest(method, apiBase, nil)
query := req.URL.Query()
query.Add("action", fmt.Sprint(action))
for k, v := range args {
query.Add(k, v)
}
req.URL.RawQuery = query.Encode()
return req
}