summary refs log blame commit diff stats
path: root/commands/msg/unsubscribe.go
blob: d4a7e9a610b9163dfa1e7d735d92870774120b58 (plain) (tree)






































































































                                                                                               
package msg

import (
	"bufio"
	"errors"
	"net/url"
	"strings"

	"git.sr.ht/~sircmpwn/aerc/lib"
	"git.sr.ht/~sircmpwn/aerc/widgets"
)

// Unsubscribe helps people unsubscribe from mailing lists by way of the
// List-Unsubscribe header.
type Unsubscribe struct{}

func init() {
	register(Unsubscribe{})
}

// Aliases returns a list of aliases for the :unsubscribe command
func (Unsubscribe) Aliases() []string {
	return []string{"unsubscribe"}
}

// Complete returns a list of completions
func (Unsubscribe) Complete(aerc *widgets.Aerc, args []string) []string {
	return nil
}

// Execute runs the Unsubscribe command
func (Unsubscribe) Execute(aerc *widgets.Aerc, args []string) error {
	if len(args) != 1 {
		return errors.New("Usage: unsubscribe")
	}
	widget := aerc.SelectedTab().(widgets.ProvidesMessage)
	headers := widget.SelectedMessage().RFC822Headers
	if !headers.Has("list-unsubscribe") {
		return errors.New("No List-Unsubscribe header found")
	}
	methods := parseUnsubscribeMethods(headers.Get("list-unsubscribe"))
	aerc.Logger().Printf("found %d unsubscribe methods", len(methods))
	for _, method := range methods {
		aerc.Logger().Printf("trying to unsubscribe using %v", method)
		switch method.Scheme {
		case "mailto":
			return unsubscribeMailto(aerc, method)
		case "http", "https":
			return unsubscribeHTTP(method)
		default:
			aerc.Logger().Printf("skipping unrecognized scheme: %s", method.Scheme)
		}
	}
	return errors.New("no supported unsubscribe methods found")
}

// parseUnsubscribeMethods reads the list-unsubscribe header and parses it as a
// list of angle-bracket <> deliminated URLs. See RFC 2369.
func parseUnsubscribeMethods(header string) (methods []*url.URL) {
	r := bufio.NewReader(strings.NewReader(header))
	for {
		// discard until <
		_, err := r.ReadSlice('<')
		if err != nil {
			return
		}
		// read until <
		m, err := r.ReadSlice('>')
		if err != nil {
			return
		}
		m = m[:len(m)-1]
		if u, err := url.Parse(string(m)); err == nil {
			methods = append(methods, u)
		}
	}
}

func unsubscribeMailto(aerc *widgets.Aerc, u *url.URL) error {
	widget := aerc.SelectedTab().(widgets.ProvidesMessage)
	acct := widget.SelectedAccount()
	composer := widgets.NewComposer(aerc.Config(), acct.AccountConfig(),
		acct.Worker())
	composer.Defaults(map[string]string{
		"To":      u.Opaque,
		"Subject": u.Query().Get("subject"),
	})
	composer.SetContents(strings.NewReader(u.Query().Get("body")))
	tab := aerc.NewTab(composer, "unsubscribe")
	composer.OnSubjectChange(func(subject string) {
		if subject == "" {
			tab.Name = "unsubscribe"
		} else {
			tab.Name = subject
		}
		tab.Content.Invalidate()
	})
	return nil
}

func unsubscribeHTTP(u *url.URL) error {
	return lib.OpenFile(u.String())
}