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
|
diff --git a/README.md b/README.md
index 4751ddd2bdbb922c253ac5fc0f52ced1ae62c57f..43e8950c3ee7bebc4ba0f0f3a099492892d6086e 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ | OVER | OVER | ☓ | RFC3977 |
| LIST OVERVIEW.FMT | OVER | ☓ | RFC3977 |
| POST | POST | ☓ | RFC3977 |
| ARTICLE | READER | 🗸 | RFC3977 |
-| BODY | READER | ☓ | RFC3977 |
+| BODY | READER | 🗸 | RFC3977 |
| DATE | READER | ☓ | RFC3977 |
| GROUP | READER | 🗸 | RFC3977 |
| LAST | READER | ☓ | RFC3977 |
diff --git a/reader.go b/reader.go
index d68f64711105cbb07344e97ca03590b73389b7ca..0285c7ee2ca93d2bae93ab1c59be5d13bff8b6ff 100644
--- a/reader.go
+++ b/reader.go
@@ -31,6 +31,32 @@ return resp, ErrUnexpectedResponse
}
}
+func (c *Conn) Body(ctx context.Context, msg MessageIdentifier) (*Response, error) {
+ if c.caps&CapReader == 0 {
+ return nil, ErrCapabilityNotSupported
+ }
+
+ resp, err := c.Cmd(ctx, "BODY %s", msg.msgID())
+ if err != nil {
+ return nil, fmt.Errorf("failed to retrieve article '%s': %w", msg.msgID(), err)
+ }
+
+ switch resp.Status.Code {
+ case StatusBodyFollows:
+ return resp, nil
+ case StatusNoSuchNewsgroup:
+ return resp, ErrNoNewsgroupSelected
+ case StatusCurrentArticleNumberInvalid:
+ return resp, ErrCurrentArticleNumberInvalid
+ case StatusNoArticleWithGivenMessageID:
+ return resp, ErrNoArticleWithGivenMessageID
+ case StatusNoArticleWithGivenNumber:
+ return resp, ErrNoArticleWithGivenNumber
+ default:
+ return resp, ErrUnexpectedResponse
+ }
+}
+
func (c *Conn) Group(ctx context.Context, group string) error {
if c.caps&CapReader == 0 {
return ErrCapabilityNotSupported
diff --git a/reader_test.go b/reader_test.go
index 8394e287e0add6f8318f272aa054e67df0465479..4d20970b2185d548f38b8184e02891d90f67be1b 100644
--- a/reader_test.go
+++ b/reader_test.go
@@ -86,6 +86,68 @@ })
}
}
+func TestBody(t *testing.T) {
+ if NewsServerSecure == "" || NewsServerUser == "" || NewsServerPassword == "" {
+ t.Log("secure server address, username, and password required in variables_test.go")
+ t.SkipNow()
+ }
+ tests := []struct {
+ name string
+ message nntp.MessageIdentifier
+ expectedError error
+ validate func(*testing.T, *nntp.Response)
+ }{
+ /*{ // Eweka sends 411 instead of 412
+ name: "no-selected-group",
+ message: nntp.ArticleNumber(54160654),
+ expectedError: nntp.ErrNoSuchNewsgroup,
+ validate: func(t *testing.T, resp *nntp.Response) { fmt.Println(resp.Status.String()); t.Fail() },
+ },*/
+ {
+ name: "non-existant-article",
+ message: nntp.MessageID("probably-not-valid-@some-client.lol"),
+ expectedError: nntp.ErrNoArticleWithGivenMessageID,
+ validate: func(t *testing.T, r *nntp.Response) {},
+ },
+ {
+ name: "existing-article",
+ message: nntp.MessageID("HtIsMuKsOrQwViIoPiBzAsZy-1654899257340@nyuu"),
+ expectedError: nil,
+ validate: func(t *testing.T, r *nntp.Response) {
+ if len(r.Body) != 98 {
+ t.Errorf("length of body '%d' does not match expectation 98", len(r.Body))
+ }
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ c, err := nntp.Dial(NewsServerSecure)
+ if err != nil {
+ t.Skipf("connection to '%s' failed: %v", NewsServerSecure, err)
+ }
+ defer c.Close(context.Background())
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ err = c.LoginUserPass(ctx, NewsServerUser, NewsServerPassword)
+ if err != nil {
+ t.Skipf("login failed: %v", err)
+ }
+
+ ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ resp, err := c.Body(ctx, test.message)
+ if !errors.Is(err, test.expectedError) {
+ t.Errorf("unexpected result:\n\tgot: %v\n\texpected: %v\n", err, test.expectedError)
+ }
+
+ test.validate(t, resp)
+ })
+ }
+}
+
func TestGroup(t *testing.T) {
if NewsServerSecure == "" || NewsServerUser == "" || NewsServerPassword == "" {
t.Log("secure server address, username, and password required in variables_test.go")
diff --git a/status.go b/status.go
index d604742694b2e22e17368fb686b1bf5ba32b3420..b884707119a292035598616fa66db11ee917c7dc 100644
--- a/status.go
+++ b/status.go
@@ -9,6 +9,7 @@ StatusServiceNoPosting = 201
StatusConnectionClosing = 205
StatusGroupSelected = 211
StatusArticleFollows = 220
+ StatusBodyFollows = 222
StatusHeadersFollow = 221
StatusArticleExists = 223
StatusAuthAccepted = 281
|