commit c95413d6ce7857ed77579a3c8bbcce6e00d0cf92 from: Dustin Sallings date: Wed Feb 22 09:33:12 2012 UTC Starting to be a functional nntp client. commit - /dev/null commit + c95413d6ce7857ed77579a3c8bbcce6e00d0cf92 blob - /dev/null blob + 32e47df45d5a504f9960679933f6a37c1dc4aeae (mode 644) --- /dev/null +++ .gitignore @@ -0,0 +1,3 @@ +*~ +#* +/examples/client/client blob - /dev/null blob + f7fad25b82812636e323f6614970f86d1298339c (mode 644) --- /dev/null +++ client/client.go @@ -0,0 +1,124 @@ +package nntpclient + +import ( + "errors" + "io" + "net/textproto" + "strconv" + "strings" + + "github.com/dustin/go-nntp" +) + +type Client struct { + conn *textproto.Conn + Banner string +} + +func New(net, addr string) (*Client, error) { + conn, err := textproto.Dial(net, addr) + if err != nil { + return nil, err + } + + _, msg, err := conn.ReadCodeLine(200) + if err != nil { + return nil, err + } + + return &Client{ + conn: conn, + Banner: msg, + }, nil +} + +func (c *Client) Authenticate(user, pass string) (msg string, err error) { + err = c.conn.PrintfLine("authinfo user %s", user) + if err != nil { + return + } + _, _, err = c.conn.ReadCodeLine(381) + if err != nil { + return + } + + err = c.conn.PrintfLine("authinfo pass %s", pass) + if err != nil { + return + } + _, msg, err = c.conn.ReadCodeLine(281) + return +} + +func (c *Client) Group(name string) (rv nntp.Group, err error) { + var msg string + _, msg, err = c.Command("GROUP "+name, 211) + if err != nil { + return + } + // count first last name + parts := strings.Split(msg, " ") + if len(parts) != 4 { + err = errors.New("Don't know how to parse result: " + msg) + } + rv.Count, err = strconv.ParseInt(parts[0], 10, 64) + if err != nil { + return + } + rv.Low, err = strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return + } + rv.High, err = strconv.ParseInt(parts[2], 10, 64) + if err != nil { + return + } + rv.Name = parts[3] + + return +} + +func (c *Client) Article(specifier string) (int64, string, io.Reader, error) { + err := c.conn.PrintfLine("ARTICLE %s", specifier) + if err != nil { + return 0, "", nil, err + } + return c.articleish(220) +} + +func (c *Client) Head(specifier string) (int64, string, io.Reader, error) { + err := c.conn.PrintfLine("HEAD %s", specifier) + if err != nil { + return 0, "", nil, err + } + return c.articleish(221) +} + +func (c *Client) Body(specifier string) (int64, string, io.Reader, error) { + err := c.conn.PrintfLine("BODY %s", specifier) + if err != nil { + return 0, "", nil, err + } + return c.articleish(222) +} + +func (c *Client) articleish(expected int) (int64, string, io.Reader, error) { + _, msg, err := c.conn.ReadCodeLine(expected) + if err != nil { + return 0, "", nil, err + } + parts := strings.SplitN(msg, " ", 2) + n, err := strconv.ParseInt(parts[0], 10, 64) + if err != nil { + return 0, "", nil, err + } + return n, parts[1], c.conn.DotReader(), nil +} + +func (c *Client) Command(cmd string, expectCode int) (int, string, error) { + err := c.conn.PrintfLine(cmd) + if err != nil { + return 0, "", err + } + return c.conn.ReadCodeLine(expectCode) +} blob - /dev/null blob + 657477b660e0d4cd844499e0883fc7df0fffa9c8 (mode 644) --- /dev/null +++ examples/client/exampleclient.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "io" + "log" + "os" + + "github.com/dustin/go-nntp/client" +) + +func maybefatal(s string, e error) { + if e != nil { + log.Fatalf("Error in %s: %v", s, e) + } +} + +func main() { + server, user, pass := os.Args[1], os.Args[2], os.Args[3] + c, err := nntpclient.New("tcp", server) + maybefatal("connecting", err) + log.Printf("Got banner: %v", c.Banner) + + // Set the reader mode + _, _, err = c.Command("mode reader", 2) + maybefatal("setting reader mode", err) + + // Authenticate + msg, err := c.Authenticate(user, pass) + maybefatal("authenticating", err) + log.Printf("Post authentication message: %v", msg) + + g, err := c.Group("comp.lang.lisp") + maybefatal("grouping", err) + log.Printf("Got %#v", g) + + n, id, r, err := c.Head(fmt.Sprintf("%v", g.High-1)) + maybefatal("getting head", err) + log.Printf("msg %d has id %v and the following headers", n, id) + io.Copy(os.Stdout, r) + + n, id, r, err = c.Body(fmt.Sprintf("%v", n)) + maybefatal("getting body", err) + log.Printf("Body of message %v", id) + io.Copy(os.Stdout, r) + + n, id, r, err = c.Article(fmt.Sprintf("%v", n)) + maybefatal("getting the whole thing", err) + log.Printf("Full message %v", id) + io.Copy(os.Stdout, r) +} blob - /dev/null blob + d8ec5e9ca77aa52080bf3c80ed7f36a5a6ecbae8 (mode 644) --- /dev/null +++ nntp.go @@ -0,0 +1,18 @@ +package nntp + +type PostingStatus byte + +const ( + Unknown = PostingStatus(0) + PostingPermitted = PostingStatus('y') + PostingNotPermitted = PostingStatus('n') + PostingModerated = PostingStatus('m') +) + +type Group struct { + Name string + Count int64 + High int64 + Low int64 + Posting PostingStatus +}