From 0b7a459d5b86070a6144a66acd5ce82c53b3e1b7 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 26 Dec 2023 14:00:13 -0600 Subject: [PATCH] next iteration on unsubscribing --- errors.go | 1 + go.mod | 9 ++++ go.sum | 19 ++++++++ logs.go | 31 ++++++++++++ main.go | 140 ++++++++++++++++++++++++++++++++++++++++++++---------- 5 files changed, 176 insertions(+), 24 deletions(-) create mode 100644 errors.go create mode 100644 go.sum create mode 100644 logs.go diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/errors.go @@ -0,0 +1 @@ +package main diff --git a/go.mod b/go.mod index 0301109..9de31d0 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,12 @@ module unsubscribe go 1.21.3 + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/rs/zerolog v1.31.0 // indirect + github.com/samber/lo v1.39.0 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect + golang.org/x/sys v0.12.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4737678 --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/logs.go b/logs.go new file mode 100644 index 0000000..5c0ff64 --- /dev/null +++ b/logs.go @@ -0,0 +1,31 @@ +package main + +import ( + "os" + + "github.com/rs/zerolog" +) + +const timeFormat = "2006-01-02 15:04:05" + +var ( + standardOut = zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: timeFormat} + errorOut = zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: timeFormat} +) + +// logSplitter implements zerolog.LevelWriter +type logSplitter struct{} + +// Write should not be called +func (l logSplitter) Write(p []byte) (n int, err error) { + return os.Stdout.Write(p) +} + +// WriteLevel write to the appropriate output +func (l logSplitter) WriteLevel(level zerolog.Level, p []byte) (n int, err error) { + if level <= zerolog.WarnLevel { + return standardOut.Write(p) + } else { + return errorOut.Write(p) + } +} diff --git a/main.go b/main.go index fc4a721..8ea83e6 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,25 @@ package main import ( + "crypto/sha256" + "encoding/json" + "fmt" + "io" "net/http" "net/url" "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/samber/lo" ) +var client = &http.Client{} + +func init() { + log.Logger = zerolog.New(logSplitter{}).With().Timestamp().Logger() +} + type Confirmation struct { FormId string `json:"formId"` FollowUpUrl string `json:"followUpUrl"` @@ -14,35 +28,113 @@ type Confirmation struct { AliId string `json:"aliId"` } -func Unsubscribe() { - values := url.Values{ - "Email": {"ryan.walters@my.utsa.edu"}, - "Unsubscribed": {"Yes"}, - "formid": {"1"}, - "lpId": {"1"}, - "subId": {"98"}, - "munchkinId": {"839-MOL-552"}, - "lpurl": {"http://839-MOL-552.mktoweb.com/lp/839-MOL-552/UnsubscribePage.html?cr={creative}&kw={keyword}"}, - "followupLpId": {"2"}, - "cr": {""}, - "kw": {""}, - "q": {""}, - "_mkt_trk": {""}, - "formVid": {"1"}, - "mkt_tok": {"ODM5LU1PTC01NTIAAAGQRiDbOUWzUhLliVDxTHjxLfZDD1y0MxC47Wf_1C9UTbwEej3Tckhn_QteZR7p5Mpl3_f0ioPUyQ8XUceJ9a0PiOUJb_O3YIj8PwKNQEm4SseaSw"}, - "_mktoReferrer": {"http://www2.thescla.org/UnsubscribePage.html?mkt_unsubscribe=1&mkt_tok=ODM5LU1PTC01NTIAAAGQRiDbOUWzUhLliVDxTHjxLfZDD1y0MxC47Wf_1C9UTbwEej3Tckhn_QteZR7p5Mpl3_f0ioPUyQ8XUceJ9a0PiOUJb_O3YIj8PwKNQEm4SseaSw"}, - "checksumFields": {"Email,Unsubscribed,formid,lpId,subId,munchkinId,lpurl,followupLpId,cr,kw,q,_mkt_trk,formVid,mkt_tok,_mktoReferrer"}, - "checksum": {"062bb614ebe52a624577123e91c0e7e0d6d069178d2d23d7bd1fa9db85b05472"}, +func DoRequest(req *http.Request) (*http.Response, []byte, error) { + log.Debug().Str("method", req.Method).Str("host", req.Host).Str("path", req.URL.Path).Msg("Request") + resp, err := client.Do(req) + + if err != nil { + log.Error().Err(err).Msg("Error making request") + return nil, nil, err } - req, _ := http.NewRequest("POST", "http://www2.thescla.org/index.php/leadCapture/save2", strings.NewReader(values.Encode())) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("X-Requested-With", "XMLHttpRequest") + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Msg("Error reading response body") + return nil, nil, err + } + + log.Debug().Int("code", resp.StatusCode).Str("content-type", resp.Header.Get("Content-Type")).Int("content-length", len(body)).Msg("Response") + + return resp, body, nil +} + +func ApplyHeaders(req *http.Request) { req.Header.Set("Origin", "http://www2.thescla.org") req.Header.Set("Connection", "keep-alive") - req.Header.Set("Referer", "http://www2.thescla.org/UnsubscribePage.html?mkt_unsubscr") + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0") + req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01") + req.Header.Set("Accept-Language", "en-US,en;q=0.5") + req.Header.Set("Accept-Encoding", "gzip, deflate") +} + +func Unsubscribe(email string) *Confirmation { + mktTok := "ODM5LU1PTC01NTIAAAGQRiDbOUWzUhLliVDxTHjxLfZDD1y0MxC47Wf_1C9UTbwEej3Tckhn_QteZR7p5Mpl3_f0ioPUyQ8XUceJ9a0PiOUJb_O3YIj8PwKNQEm4SseaSw" + + // Build referrer URL + referrerUrl, _ := url.Parse("http://www2.thescla.org/UnsubscribePage.html") + query := referrerUrl.Query() + query.Add("mkt_unsubscribe", "1") + query.Add("mkt_tok", mktTok) + referrerUrl.RawQuery = query.Encode() + + thing := "839-MOL-552" + lpUrl := fmt.Sprintf("http://%s.mktoweb.com/lp/%s/UnsubscribePage.html?cr={creative}&kw={keyword}", thing, thing) + + values := url.Values{ + "Email": {email}, + "Unsubscribed": {"Yes"}, + "formid": {"1"}, + "lpId": {"1"}, + "subId": {"98"}, + "munchkinId": {thing}, + "lpurl": {lpUrl}, + "followupLpId": {"2"}, + "cr": {""}, + "kw": {""}, + "q": {""}, + "_mkt_trk": {""}, + "formVid": {"1"}, + "mkt_tok": {mktTok}, + "_mktoReferrer": {referrerUrl.String()}, + } + + fields := []string{"Email", "Unsubscribed", "formid", "lpId", "subId", "munchkinId", "lpurl", "followupLpId", "cr", "kw", "q", "_mkt_trk", "formVid", "mkt_tok", "_mktoReferrer"} + // fields := make([]string, 0, len(values)) + // for key, _ := range values { + // fields = append(fields, key) + // } + values.Set("checksumFields", strings.Join(fields, ",")) + log.Printf("%+v", values.Get("checksumFields")) + + // Calculate checksum + h := sha256.New() + h.Write([]byte(strings.Join( + lo.Map(fields, func(field string, _ int) string { + return values.Get(field) + }), "|"))) + + hex := fmt.Sprintf("%x", h.Sum(nil)) + values.Set("checksum", hex) + log.Debug().Str("hex", hex).Msg("Checksum") + + // Make request + request, _ := http.NewRequest("POST", "http://www2.thescla.org/index.php/leadCapture/save2", strings.NewReader(values.Encode())) + request.Header.Set("Referer", "http://www2.thescla.org/UnsubscribePage.html?mkt_unsubscribe=1") + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + request.Header.Set("X-Requested-With", "XMLHttpRequest") + ApplyHeaders(request) + + response, body, err := DoRequest(request) + if err != nil { + panic(err) + } + + if response.StatusCode != 200 { + log.Print(string(body)) + return nil + } + + var confirmation Confirmation + json.Unmarshal(body, &confirmation) + return &confirmation } func main() { - + conf := Unsubscribe("ryan.walters@my.utsa.edu") + if conf != nil { + log.Info().Str("formId", conf.FormId).Str("followUpUrl", conf.FollowUpUrl).Str("deliveryType", conf.DeliveryType).Str("followUpStreamValue", conf.FollowUpStreamValue).Str("aliId", conf.AliId).Msg("Unsubscribed") + } else { + log.Error().Msg("Failed to Unsubscribe") + } }