info.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. package main
  2. import (
  3. "errors"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "math"
  9. "os"
  10. "strconv"
  11. "strings"
  12. "git.lattuga.net/blallo/papero/cli"
  13. "git.lattuga.net/blallo/papero/imaputils"
  14. "github.com/emersion/go-imap"
  15. )
  16. const maxUInt32 = int(^uint32(0))
  17. var infoVerbs cli.CommandMap
  18. func init() {
  19. lsMailbox := LsMailboxCmd{}
  20. lsMessages := LsMsgCmd{}
  21. catMessage := CatMsgCmd{}
  22. infoVerbs = cli.CommandMap{
  23. "list-mailboxes": lsMailbox,
  24. "list-messages": lsMessages,
  25. "display-message": catMessage,
  26. }
  27. }
  28. type InfoCmd struct{}
  29. func (i InfoCmd) Func(args []string) error {
  30. flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
  31. flagset.Usage = func() { i.Help(os.Stdout, flagset) }
  32. flagset.Parse(args[1:])
  33. subArgs := flagset.Args()
  34. if len(subArgs) == 0 {
  35. i.Help(os.Stderr, flagset)
  36. os.Exit(1)
  37. }
  38. cmd := subArgs[0]
  39. if Session.Info.Opts.Debug {
  40. Log.Debug("info verb:", cmd)
  41. }
  42. return infoVerbs[cmd].Func(subArgs)
  43. }
  44. func (i InfoCmd) Help(w io.Writer, set *flag.FlagSet) {
  45. fmt.Fprintf(w, "USAGE: %s info VERB [verb opts]\n", Session.Info.Name)
  46. fmt.Fprintf(w, "\nVERBS:\n")
  47. for verb := range infoVerbs {
  48. fmt.Fprintf(w, "\t%s\n", verb)
  49. }
  50. }
  51. // VERBS
  52. type LsMailboxCmd struct{}
  53. type mailboxInfo struct {
  54. name string
  55. attrs string
  56. }
  57. func (l LsMailboxCmd) Func(args []string) error {
  58. if Session.Info.Opts.Debug {
  59. Log.Debug("enter info list-mailboxes")
  60. }
  61. var withAttributes bool
  62. var output string
  63. var maxLen int
  64. var mailboxes []mailboxInfo
  65. flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
  66. flagset.BoolVar(&withAttributes, "with-attrs", false, "toggle attributes display")
  67. flagset.Usage = func() { l.Help(os.Stdout, flagset) }
  68. flagset.Parse(args[1:])
  69. mboxInfo, err := imaputils.ListMailboxes(Session.Config, Session.Info.Opts.Debug)
  70. if err != nil {
  71. return err
  72. }
  73. if Session.Info.Opts.Debug {
  74. Log.Debug(mboxInfo)
  75. }
  76. for _, box := range mboxInfo {
  77. if withAttributes {
  78. if nameLen := len(box.Name); nameLen > maxLen {
  79. maxLen = nameLen
  80. }
  81. mailboxes = append(mailboxes, mailboxInfo{name: box.Name, attrs: fmt.Sprint(box.Attributes)})
  82. } else {
  83. output += fmt.Sprintln(box.Name)
  84. }
  85. }
  86. if maxLen == 0 {
  87. fmt.Print(output)
  88. } else {
  89. padFmtStr := fmt.Sprintf("%%-%dv\t", maxLen)
  90. for _, m := range mailboxes {
  91. line := fmt.Sprintf(padFmtStr, m.name)
  92. line += m.attrs
  93. fmt.Println(line)
  94. }
  95. }
  96. return nil
  97. }
  98. func (l LsMailboxCmd) Help(w io.Writer, set *flag.FlagSet) {
  99. fmt.Fprintf(w, "USAGE: %s info list-mailboxes [opts]\n", Session.Info.Name)
  100. fmt.Fprintf(w, "\nOPTS:\n")
  101. set.PrintDefaults()
  102. }
  103. type LsMsgCmd struct{}
  104. func (l LsMsgCmd) Func(args []string) error {
  105. if Session.Info.Opts.Debug {
  106. Log.Debug("enter info list-messages")
  107. }
  108. var limit uint
  109. var withSeq, withFrom, withDate bool
  110. var sep string
  111. flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
  112. flagset.StringVar(&sep, "sep", "\t", "separator between fields")
  113. flagset.UintVar(&limit, "limit", 0, "maximum number of messages to display (0 means no limit)")
  114. flagset.BoolVar(&withSeq, "seq", false, "show sequence number")
  115. flagset.BoolVar(&withFrom, "from", false, "show From address")
  116. flagset.BoolVar(&withDate, "date", false, "show message date")
  117. flagset.Usage = func() { l.Help(os.Stdout, flagset) }
  118. flagset.Parse(args[1:])
  119. subArgs := flagset.Args()
  120. if len(subArgs) != 1 {
  121. l.Help(os.Stderr, flagset)
  122. os.Exit(1)
  123. }
  124. msgs, err := imaputils.ListMessages(Session.Config, uint32(limit), Session.Info.Opts.Debug, subArgs[0])
  125. if err != nil {
  126. return err
  127. }
  128. printMsgs(msgs, withSeq, withFrom, withDate, sep)
  129. return nil
  130. }
  131. func (l LsMsgCmd) Help(w io.Writer, set *flag.FlagSet) {
  132. fmt.Fprintf(w, "USAGE: %s info list-messages [opts] MAILBOX_NAME\n", Session.Info.Name)
  133. fmt.Fprintf(w, "\nOPTS:\n")
  134. set.PrintDefaults()
  135. }
  136. func numberSize(n uint32) int {
  137. return int(math.Floor(math.Log10(float64(n))))
  138. }
  139. func printMsgs(msgs []*imap.Message, withSeq, withFrom, withDate bool, sep string) {
  140. var maxSeq, maxDateLen, maxFromLen int
  141. var out, seqFmtStr, dateFmtStr, fromFmtStr string
  142. for _, msg := range msgs {
  143. if seqSize := numberSize(msg.SeqNum); withSeq && seqSize > maxSeq {
  144. maxSeq = seqSize
  145. }
  146. if withDate {
  147. date := fmt.Sprint(msg.Envelope.Date)
  148. if dateLen := len(date); dateLen > maxDateLen {
  149. maxDateLen = dateLen
  150. }
  151. }
  152. if withFrom {
  153. for _, from := range msg.Envelope.From {
  154. if fromLen := len(from.Address()); fromLen > maxFromLen {
  155. maxFromLen = fromLen
  156. }
  157. }
  158. }
  159. }
  160. if withSeq {
  161. seqFmtStr = fmt.Sprintf("%%%dv%s", maxSeq, sep)
  162. }
  163. if withDate {
  164. dateFmtStr = fmt.Sprintf("%%-%dv%s", maxDateLen, sep)
  165. }
  166. if withFrom {
  167. fromFmtStr = fmt.Sprintf("%%-%dv%s", maxFromLen, sep)
  168. }
  169. for _, msg := range msgs {
  170. var line string
  171. if withSeq {
  172. line += fmt.Sprintf(seqFmtStr, msg.SeqNum)
  173. }
  174. if withDate {
  175. line += fmt.Sprintf(dateFmtStr, msg.Envelope.Date)
  176. }
  177. if withFrom {
  178. line += fmt.Sprintf(fromFmtStr, msg.Envelope.From[0].Address())
  179. }
  180. line += msg.Envelope.Subject
  181. out += fmt.Sprintln(line)
  182. }
  183. fmt.Println(out)
  184. }
  185. type CatMsgCmd struct{}
  186. func (c CatMsgCmd) Func(args []string) error {
  187. if Session.Info.Opts.Debug {
  188. Log.Debug("enter info display-message")
  189. }
  190. var idList []uint32
  191. var withHeaders, withBody, markRead bool
  192. flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
  193. flagset.BoolVar(&withHeaders, "headers", false, "toggle headers display")
  194. flagset.BoolVar(&withBody, "no-body", false, "hide body")
  195. flagset.BoolVar(&markRead, "seen", false, "mark as seen if not yet seen")
  196. flagset.Usage = func() { c.Help(os.Stdout, flagset) }
  197. flagset.Parse(args[1:])
  198. subArgs := flagset.Args()
  199. lenSubArgs := len(subArgs)
  200. if lenSubArgs < 2 {
  201. c.Help(os.Stderr, flagset)
  202. os.Exit(1)
  203. }
  204. mailbox := subArgs[0]
  205. if lenSubArgs > 1 {
  206. for _, id := range subArgs[1:] {
  207. mailId, err := parseToUint32(id)
  208. if err != nil {
  209. return err
  210. }
  211. idList = append(idList, mailId)
  212. }
  213. }
  214. opts := &imaputils.FetchOpts{
  215. Mailbox: mailbox,
  216. IdList: idList,
  217. WithHeaders: withHeaders,
  218. WithBody: !withBody,
  219. Peek: !markRead,
  220. }
  221. if Session.Info.Opts.Debug {
  222. Log.Debug(opts)
  223. }
  224. messages, err := imaputils.FetchMessages(Session.Config, opts, Session.Info.Opts.Debug)
  225. if err != nil {
  226. return err
  227. }
  228. for _, m := range messages {
  229. err = printMessage(m, opts)
  230. if err != nil {
  231. return err
  232. }
  233. }
  234. return nil
  235. }
  236. func (c CatMsgCmd) Help(w io.Writer, set *flag.FlagSet) {
  237. fmt.Fprintf(w, "USAGE: %s info display-message [opts] MAILBOX_NAME [MESSAGE_ID1 [MESSAGE_ID2 [...]]]\n", Session.Info.Name)
  238. fmt.Fprintf(w, "\nOPTS:\n")
  239. set.PrintDefaults()
  240. }
  241. var ErrOutOfBounds = errors.New("number is out of bounds")
  242. func parseToUint32(s string) (uint32, error) {
  243. out, err := strconv.Atoi(s)
  244. if err != nil {
  245. return 0, err
  246. }
  247. if out < 0 || out > maxUInt32 {
  248. return 0, ErrOutOfBounds
  249. }
  250. return uint32(out), nil
  251. }
  252. func printMessage(m *imap.Message, opts *imaputils.FetchOpts) error {
  253. var out string
  254. if opts.WithHeaders {
  255. out += fmt.Sprintln(formatAddresses(m.Envelope.From, "From"))
  256. out += fmt.Sprintln(formatAddresses(m.Envelope.Sender, "Sender"))
  257. out += fmt.Sprintln(formatAddresses(m.Envelope.Cc, "Cc"))
  258. out += fmt.Sprintln(formatAddresses(m.Envelope.Bcc, "Bcc"))
  259. out += fmt.Sprintln(formatAddresses(m.Envelope.ReplyTo, "ReplyTo"))
  260. out += fmt.Sprintf("InReplyTo: %s\n", m.Envelope.InReplyTo)
  261. out += fmt.Sprintf("Date: %v\n", m.Envelope.Date)
  262. out += fmt.Sprintf("Subject: %s\n", m.Envelope.Subject)
  263. headersSec := &imap.BodySectionName{Peek: opts.Peek, BodyPartName: imap.BodyPartName{Specifier: imap.HeaderSpecifier}}
  264. headersReader := m.GetBody(headersSec)
  265. headers, err := ioutil.ReadAll(headersReader)
  266. if err != nil {
  267. return err
  268. }
  269. out += fmt.Sprintln(string(headers))
  270. }
  271. if opts.WithBody {
  272. bodySec := &imap.BodySectionName{Peek: opts.Peek, BodyPartName: imap.BodyPartName{Specifier: imap.TextSpecifier}}
  273. bodyReader := m.GetBody(bodySec)
  274. body, err := ioutil.ReadAll(bodyReader)
  275. if err != nil {
  276. return err
  277. }
  278. out += fmt.Sprintln(string(body))
  279. }
  280. fmt.Println(out)
  281. return nil
  282. }
  283. func formatAddresses(addresses []*imap.Address, name string) string {
  284. out := fmt.Sprintf("%s: ", name)
  285. for _, address := range addresses {
  286. if address.PersonalName != "" {
  287. out += fmt.Sprintf("%s <%s>, ", address.PersonalName, address.Address())
  288. } else {
  289. out += fmt.Sprintf("%s, ", address.Address())
  290. }
  291. }
  292. return strings.TrimRight(out, ", ")
  293. }