123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- package main
- import (
- "errors"
- "flag"
- "fmt"
- "io"
- "io/ioutil"
- "math"
- "os"
- "strconv"
- "strings"
- "git.lattuga.net/blallo/papero/cli"
- "git.lattuga.net/blallo/papero/imaputils"
- "github.com/emersion/go-imap"
- )
- const maxUInt32 = int(^uint32(0))
- var infoVerbs cli.CommandMap
- func init() {
- infoVerbs = cli.CommandMap{
- "list-mailboxes": LsMailboxCmd{},
- // "list-subscribed": LsSubscribedCmd{},
- "list-messages": LsMsgCmd{},
- "display-message": CatMsgCmd{},
- }
- }
- type InfoCmd struct{}
- func (i InfoCmd) Func(args []string) error {
- flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
- flagset.Usage = func() { i.Help(os.Stdout, flagset) }
- flagset.Parse(args[1:])
- subArgs := flagset.Args()
- if len(subArgs) == 0 {
- i.Help(os.Stderr, flagset)
- os.Exit(1)
- }
- cmd := subArgs[0]
- if Session.Info.Opts.Debug {
- Log.Debug("info verb:", cmd)
- }
- return infoVerbs[cmd].Func(subArgs)
- }
- func (i InfoCmd) Help(w io.Writer, set *flag.FlagSet) {
- fmt.Fprintf(w, "USAGE: %s info VERB [verb opts]\n", Session.Info.Name)
- fmt.Fprintf(w, "\nVERBS:\n")
- for verb := range infoVerbs {
- fmt.Fprintf(w, "\t%s\n", verb)
- }
- }
- // VERBS
- type LsMailboxCmd struct{}
- type mailboxInfo struct {
- name string
- attrs string
- }
- func (l LsMailboxCmd) Func(args []string) error {
- if Session.Info.Opts.Debug {
- Log.Debug("enter info list-mailboxes")
- }
- var withAttributes bool
- var output string
- var maxLen int
- var mailboxes []mailboxInfo
- flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
- flagset.BoolVar(&withAttributes, "with-attrs", false, "toggle attributes display")
- flagset.Usage = func() { l.Help(os.Stdout, flagset) }
- flagset.Parse(args[1:])
- mboxInfo, err := imaputils.ListMailboxes(Session.Config, Session.Info.Opts.Debug)
- if err != nil {
- return err
- }
- if Session.Info.Opts.Debug {
- Log.Debug(mboxInfo)
- }
- for _, box := range mboxInfo {
- if withAttributes {
- if nameLen := len(box.Name); nameLen > maxLen {
- maxLen = nameLen
- }
- mailboxes = append(mailboxes, mailboxInfo{name: box.Name, attrs: fmt.Sprint(box.Attributes)})
- } else {
- output += fmt.Sprintln(box.Name)
- }
- }
- if maxLen == 0 {
- fmt.Print(output)
- } else {
- padFmtStr := fmt.Sprintf("%%-%dv\t", maxLen)
- for _, m := range mailboxes {
- line := fmt.Sprintf(padFmtStr, m.name)
- line += m.attrs
- fmt.Println(line)
- }
- }
- return nil
- }
- func (l LsMailboxCmd) Help(w io.Writer, set *flag.FlagSet) {
- fmt.Fprintf(w, "USAGE: %s info list-mailboxes [opts]\n", Session.Info.Name)
- fmt.Fprintf(w, "\nOPTS:\n")
- set.PrintDefaults()
- }
- type LsMsgCmd struct{}
- func (l LsMsgCmd) Func(args []string) error {
- if Session.Info.Opts.Debug {
- Log.Debug("enter info list-messages")
- }
- var limit uint
- var withSeq, withFrom, withDate, withFlags bool
- var sep string
- flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
- flagset.StringVar(&sep, "sep", "\t", "separator between fields")
- flagset.UintVar(&limit, "limit", 0, "maximum number of messages to display (0 means no limit)")
- flagset.BoolVar(&withSeq, "seq", false, "show sequence number")
- flagset.BoolVar(&withFrom, "from", false, "show From address")
- flagset.BoolVar(&withDate, "date", false, "show message date")
- flagset.BoolVar(&withFlags, "flags", false, "show message flags")
- flagset.Usage = func() { l.Help(os.Stdout, flagset) }
- flagset.Parse(args[1:])
- subArgs := flagset.Args()
- if len(subArgs) != 1 {
- l.Help(os.Stderr, flagset)
- os.Exit(1)
- }
- msgs, err := imaputils.ListMessages(
- Session.Config,
- &imaputils.ListMessagesOpts{
- Mailbox: subArgs[0],
- Limit: uint32(limit),
- },
- Session.Info.Opts.Debug,
- )
- if err != nil {
- return err
- }
- printMsgs(msgs, withSeq, withFrom, withDate, withFlags, sep)
- return nil
- }
- func (l LsMsgCmd) Help(w io.Writer, set *flag.FlagSet) {
- fmt.Fprintf(w, "USAGE: %s info list-messages [opts] MAILBOX_NAME\n", Session.Info.Name)
- fmt.Fprintf(w, "\nOPTS:\n")
- set.PrintDefaults()
- }
- func numberSize(n uint32) int {
- return int(math.Floor(math.Log10(float64(n))))
- }
- func printMsgs(msgs []*imap.Message, withSeq, withFrom, withDate, withFlags bool, sep string) {
- var maxSeq, maxDateLen, maxFromLen, maxFlagsLen int
- var out, seqFmtStr, dateFmtStr, fromFmtStr, flagsFmtStr string
- for _, msg := range msgs {
- if seqSize := numberSize(msg.SeqNum); withSeq && seqSize > maxSeq {
- maxSeq = seqSize
- }
- if withDate {
- date := fmt.Sprint(msg.Envelope.Date)
- if dateLen := len(date); dateLen > maxDateLen {
- maxDateLen = dateLen
- }
- }
- if withFrom {
- for _, from := range msg.Envelope.From {
- if fromLen := len(from.Address()); fromLen > maxFromLen {
- maxFromLen = fromLen
- }
- }
- }
- if withFlags {
- var flags string
- for _, f := range msg.Flags {
- flags += fmt.Sprintf("%s ", f)
- }
- flags = strings.TrimRight(flags, " ")
- if flagsLen := len(flags); flagsLen > maxFlagsLen {
- maxFlagsLen = flagsLen
- }
- }
- }
- if withSeq {
- seqFmtStr = fmt.Sprintf("%%%dv%s", maxSeq, sep)
- }
- if withDate {
- dateFmtStr = fmt.Sprintf("%%-%dv%s", maxDateLen, sep)
- }
- if withFrom {
- fromFmtStr = fmt.Sprintf("%%-%dv%s", maxFromLen, sep)
- }
- if withFlags {
- flagsFmtStr = fmt.Sprintf("%%-%dv%s", maxFlagsLen, sep)
- }
- for _, msg := range msgs {
- var line string
- if withSeq {
- line += fmt.Sprintf(seqFmtStr, msg.SeqNum)
- }
- if withDate {
- line += fmt.Sprintf(dateFmtStr, msg.Envelope.Date)
- }
- if withFrom {
- line += fmt.Sprintf(fromFmtStr, msg.Envelope.From[0].Address())
- }
- if withFlags {
- var flags string
- for _, f := range msg.Flags {
- flags += fmt.Sprintf("%s ", f)
- }
- line += fmt.Sprintf(flagsFmtStr, strings.TrimRight(flags, " "))
- }
- line += msg.Envelope.Subject
- out += fmt.Sprintln(line)
- }
- fmt.Println(out)
- }
- type CatMsgCmd struct{}
- func (c CatMsgCmd) Func(args []string) error {
- if Session.Info.Opts.Debug {
- Log.Debug("enter info display-message")
- }
- var idList []uint32
- var withHeaders, withBody, withFlags, markRead bool
- flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
- flagset.BoolVar(&withHeaders, "headers", false, "toggle headers display")
- flagset.BoolVar(&withBody, "no-body", false, "hide body")
- flagset.BoolVar(&withFlags, "flags", false, "show flags")
- flagset.BoolVar(&markRead, "seen", false, "mark as seen if not yet seen")
- flagset.Usage = func() { c.Help(os.Stdout, flagset) }
- flagset.Parse(args[1:])
- subArgs := flagset.Args()
- lenSubArgs := len(subArgs)
- if lenSubArgs < 2 {
- c.Help(os.Stderr, flagset)
- os.Exit(1)
- }
- mailbox := subArgs[0]
- if lenSubArgs > 1 {
- for _, id := range subArgs[1:] {
- mailId, err := parseToUint32(id)
- if err != nil {
- return err
- }
- idList = append(idList, mailId)
- }
- }
- opts := &imaputils.FetchOpts{
- Mailbox: mailbox,
- IdList: idList,
- WithHeaders: withHeaders,
- WithBody: !withBody,
- WithFlags: withFlags,
- Peek: !markRead,
- }
- if Session.Info.Opts.Debug {
- Log.Debug(opts)
- }
- messages, err := imaputils.FetchMessages(Session.Config, opts, Session.Info.Opts.Debug)
- if err != nil {
- return err
- }
- for _, m := range messages {
- err = printMessage(m, opts, withFlags)
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (c CatMsgCmd) Help(w io.Writer, set *flag.FlagSet) {
- fmt.Fprintf(w, "USAGE: %s info display-message [opts] MAILBOX_NAME [MESSAGE_ID1 [MESSAGE_ID2 [...]]]\n", Session.Info.Name)
- fmt.Fprintf(w, "\nOPTS:\n")
- set.PrintDefaults()
- }
- var ErrOutOfBounds = errors.New("number is out of bounds")
- func parseToUint32(s string) (uint32, error) {
- out, err := strconv.Atoi(s)
- if err != nil {
- return 0, err
- }
- if out < 0 || out > maxUInt32 {
- return 0, ErrOutOfBounds
- }
- return uint32(out), nil
- }
- func printMessage(m *imap.Message, opts *imaputils.FetchOpts, withFlags bool) error {
- var out string
- if withFlags {
- if Session.Info.Opts.Debug {
- Log.Debug(m.Flags)
- }
- for _, f := range m.Flags {
- out += fmt.Sprintf("%s ", f)
- }
- out = strings.TrimRight(out, " ")
- out += "\n"
- }
- if opts.WithHeaders {
- out += fmt.Sprintln(formatAddresses(m.Envelope.From, "From"))
- out += fmt.Sprintln(formatAddresses(m.Envelope.Sender, "Sender"))
- out += fmt.Sprintln(formatAddresses(m.Envelope.Cc, "Cc"))
- out += fmt.Sprintln(formatAddresses(m.Envelope.Bcc, "Bcc"))
- out += fmt.Sprintln(formatAddresses(m.Envelope.ReplyTo, "ReplyTo"))
- out += fmt.Sprintf("InReplyTo: %s\n", m.Envelope.InReplyTo)
- out += fmt.Sprintf("Date: %v\n", m.Envelope.Date)
- out += fmt.Sprintf("Subject: %s\n", m.Envelope.Subject)
- headersSec := &imap.BodySectionName{Peek: opts.Peek, BodyPartName: imap.BodyPartName{Specifier: imap.HeaderSpecifier}}
- headersReader := m.GetBody(headersSec)
- headers, err := ioutil.ReadAll(headersReader)
- if err != nil {
- return err
- }
- out += fmt.Sprintln(string(headers))
- }
- if opts.WithBody {
- bodySec := &imap.BodySectionName{Peek: opts.Peek, BodyPartName: imap.BodyPartName{Specifier: imap.TextSpecifier}}
- bodyReader := m.GetBody(bodySec)
- body, err := ioutil.ReadAll(bodyReader)
- if err != nil {
- return err
- }
- out += fmt.Sprintln(string(body))
- }
- fmt.Println(out)
- return nil
- }
- func formatAddresses(addresses []*imap.Address, name string) string {
- out := fmt.Sprintf("%s: ", name)
- for _, address := range addresses {
- if address.PersonalName != "" {
- out += fmt.Sprintf("%s <%s>, ", address.PersonalName, address.Address())
- } else {
- out += fmt.Sprintf("%s, ", address.Address())
- }
- }
- return strings.TrimRight(out, ", ")
- }
|