diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a7bfa11..ee6b436 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,4 +26,14 @@ jobs: - name: Build run: go build . - working-directory: cmd/notify/ \ No newline at end of file + working-directory: cmd/notify/ + + - name: Integration Tests + env: + DISCORD_WEBHOOK_URL: "${{ secrets.DISCORD_WEBHOOK_URL }}" + SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}" + CUSTOM_WEBHOOK_URL: "${{ secrets.CUSTOM_WEBHOOK_URL }}" + run: | + chmod +x action-run.sh + bash action-run.sh + working-directory: cmd/integration-test/ \ No newline at end of file diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml index 6aad142..6d0c288 100644 --- a/.github/workflows/release-binary.yml +++ b/.github/workflows/release-binary.yml @@ -8,14 +8,13 @@ on: jobs: release: runs-on: ubuntu-latest - steps: - - - name: "Check out code" + steps: + - name: "Check out code" uses: actions/checkout@v3 with: fetch-depth: 0 - - - name: "Set up Go" + + - name: "Set up Go" uses: actions/setup-go@v3 with: go-version: 1.18 @@ -26,12 +25,11 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - - - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: "Create release on GitHub" + - name: "Create release on GitHub" uses: goreleaser/goreleaser-action@v3 with: args: "release --rm-dist" version: latest workdir: . + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/README.md b/README.md index 2f8a77c..2b75b6f 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,9 @@ $ docker run --rm \ # Features - Supports for Slack / Discord / Telegram -- Supports for Pushover / Email / Teams -- Supports for File / Pipe output +- Supports for Pushover / Email +- Supports for Microsoft Teams / Google Chat +- Supports for File / Pipe input - Supports Line by Line / Bulk Post - Supports using Single / Multiple providers - Supports Custom Web-hooks @@ -87,20 +88,23 @@ notify -h This will display help for the tool. Here are all the switches it supports. -| Flag | Description | Example | -|--------------------|----------------------------------------------------|------------------------------| -| `-config` | Notify configuration file | `notify -config config.yaml` | -| `-silent` | Don't print the banner | `notify -silent` | -| `-version` | Show version of notify | `notify -version` | -| `-v` | Show Verbose output | `notify -v` | -| `-no-color` | Don't Use colors in output | `notify -nc` | -| `-data` | File path to read data from | `notify -i test.txt` | -| `-bulk` | Read and send data in bulk | `notify -bulk` | -| `-char-limit` | Character limit for message (default 4000) | `notify -cl 2000` | -| `-provider-config` | provider config path | `notify -pc provider.yaml` | -| `-provider` | provider to send the notification to (optional) | `notify -p slack,telegram` | -| `-id` | id to send the notification to (optional) | `notify -id recon,scans` | -| `-rate-limit` | maximum number of HTTP requests to send per second | `notify -rl 1` | +| Flag | Description | Example | +|--------------------|----------------------------------------------------|---------------------------------------| +| `-bulk` | enable bulk processing | `notify -bulk` | +| `-char-limit` | max character limit per message (default 4000) | `notify -cl 2000` | +| `-config` | notify configuration file | `notify -config config.yaml` | +| `-data` | input file to send for notify | `notify -i test.txt` | +| `-delay` | delay in seconds between each notification | `notify -d 2` | +| `-id` | id to send the notification to (optional) | `notify -id recon,scans` | +| `-msg-format` | add custom formatting to message | `notify -mf Hey {{data}}` | +| `-no-color` | disable colors in output | `notify -nc` | +| `-provider-config` | provider config path | `notify -pc provider.yaml` | +| `-provider` | provider to send the notification to (optional) | `notify -p slack,telegram` | +| `-proxy` | http proxy to use with notify | `notify -proxy http://127.0.0.1:8080` | +| `-rate-limit` | maximum number of HTTP requests to send per second | `notify -rl 1` | +| `-silent` | enable silent mode | `notify -silent` | +| `-verbose` | enable verbose mode | `notify -version` | +| `-version` | display version | `notify -version` | # Notify Installation @@ -145,6 +149,7 @@ telegram: telegram_api_key: "XXXXXXXXXXXX" telegram_chat_id: "XXXXXXXX" telegram_format: "{{data}}" + telegram_parsemode: "Markdown" # None/Markdown/MarkdownV2/HTML (https://core.telegram.org/bots/api#formatting-options) pushover: - id: "push" @@ -165,9 +170,21 @@ smtp: smtp_format: "{{data}}" subject: "Email subject" +googlechat: + - id: "gc" + key: "XXXXXXXX" + token: "XXXXXX" + space: "XXXXXX" + google_chat_format: "{{data}}" + +teams: + - id: "recon" + teams_webhook_url: "https://.webhook.office.com/webhookb2/xx@xx/IncomingWebhook/xx" + teams_format: "{{data}}" + custom: - id: webhook - custom_webook_url: http://host/api/webhook + custom_webhook_url: http://host/api/webhook custom_method: GET custom_format: '{{data}}' custom_headers: diff --git a/cmd/integration-test/action-run.sh b/cmd/integration-test/action-run.sh new file mode 100755 index 0000000..53958c7 --- /dev/null +++ b/cmd/integration-test/action-run.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +rm -f final-config.yaml temp.yaml +( echo "cat <final-config.yaml"; + cat test-config.yaml; + echo "EOF"; +) >temp.yaml +. temp.yaml +rm integration-test notify 2>/dev/null + +go build ../notify +go build + +DEBUG=true ./integration-test --provider-config final-config.yaml +if [ $? -eq 0 ] +then + exit 0 +else + exit 1 +fi diff --git a/cmd/integration-test/integration.go b/cmd/integration-test/integration.go new file mode 100644 index 0000000..9aa55b9 --- /dev/null +++ b/cmd/integration-test/integration.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/logrusorgru/aurora" + "github.com/projectdiscovery/notify/internal/testutils" +) + +var ( + providerConfig = flag.String("provider-config", "", "provider config to use for testing") + debug = os.Getenv("DEBUG") == "true" + errored = false + success = aurora.Green("[✓]").String() + failed = aurora.Red("[✘]").String() + testCases = map[string]testutils.TestCase{ + "discord": &discord{}, + "slack": &slack{}, + "custom": &custom{}, + // "telegram": &telegram{}, + // "teams": &teams{}, + // "smtp": &smtp{}, + // "pushover": &pushover{}, + } +) + +func main() { + flag.Parse() + + for name, test := range testCases { + fmt.Printf("Running test cases for \"%s\"\n", aurora.Blue(name)) + err := test.Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, name, err) + errored = true + } else { + fmt.Printf("%s Test \"%s\" passed!\n", success, name) + } + } + if errored { + os.Exit(1) + } +} diff --git a/cmd/integration-test/providers.go b/cmd/integration-test/providers.go new file mode 100644 index 0000000..7cbef0a --- /dev/null +++ b/cmd/integration-test/providers.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/projectdiscovery/notify/internal/testutils" +) + +func run(provider string) error { + args := []string{"--provider", provider} + if *providerConfig != "" { + args = append(args, "--provider-config", *providerConfig) + } + results, err := testutils.RunNotifyAndGetResults(debug, args...) + if err != nil { + return err + } + if len(results) < 1 { + return errIncorrectResultsCount(results) + } + for _, r := range results { + if !strings.Contains(r, provider) { + return fmt.Errorf("incorrect result %s", results[0]) + } + } + return nil +} + +type discord struct{} + +func (h *discord) Execute() error { + return run("discord") +} + +type custom struct{} + +func (h *custom) Execute() error { + return run("custom") +} + +type slack struct{} + +func (h *slack) Execute() error { + return run("slack") +} + +// type pushover struct{} +// +// func (h *pushover) Execute() error { +// return run("pushover") +// } +// +// type smtp struct{} +// +// func (h *smtp) Execute() error { +// return run("smtp") +// } +// +// type teams struct{} +// +// func (h *teams) Execute() error { +// return run("teams") +// } +// +// type telegram struct{} +// +// func (h *telegram) Execute() error { +// return run("telegram") +// } + +func errIncorrectResultsCount(results []string) error { + return fmt.Errorf("incorrect number of results %s", strings.Join(results, "\n\t")) +} diff --git a/cmd/integration-test/run.sh b/cmd/integration-test/run.sh new file mode 100755 index 0000000..15abb9f --- /dev/null +++ b/cmd/integration-test/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +rm integration-test notify 2>/dev/null + +go build ../notify +go build + +./integration-test +if [ $? -eq 0 ] +then + exit 0 +else + exit 1 +fi diff --git a/cmd/integration-test/test-config.yaml b/cmd/integration-test/test-config.yaml new file mode 100644 index 0000000..483f223 --- /dev/null +++ b/cmd/integration-test/test-config.yaml @@ -0,0 +1,42 @@ +discord: + - id: "disocord-integration-test" + discord_webhook_url: "${DISCORD_WEBHOOK_URL}" + discord_format: "{{data}}" +slack: + - id: "slack-integration-test" + slack_channel: "random" + slack_username: "test" + slack_webhook_url: "${SLACK_WEBHOOK_URL}" + slack_format: "{{data}}" +telegram: + - id: "telegram-integration-test" + telegram_api_key: "${telegram_api_key}" + telegram_chat_id: "${telegram_chat_id}" + telegram_format: "{{data}}" +custom: + - id: "custom-integration-test" + custom_webhook_url: "${CUSTOM_WEBHOOK_URL}" + custom_method: POST + custom_format: '{{data}}' + custom_headers: + Content-Type: application/json +pushover: + - id: "push" + pushover_user_key: "${pushover_user_key}" + pushover_api_token: "${pushover_api_token}" + pushover_format: "{{data}}" + pushover_devices: + - "iphone" +smtp: + - id: email + smtp_server: "${smtp_server}" + smtp_username: "${smtp_username}" + smtp_password: "${smtp_password}" + from_address: "${smtp_from_address}" + smtp_cc: + - "${smtp_cc}" + smtp_format: "{{data}}" +teams: + - id: teams-integration-test + teams_webhook_url: "${teams_webhook_url}" + teams_format: "{{data}}" diff --git a/cmd/notify/notify.go b/cmd/notify/notify.go index fb576bd..aed49ba 100644 --- a/cmd/notify/notify.go +++ b/cmd/notify/notify.go @@ -56,12 +56,13 @@ func readConfig() { set.StringVar(&cfgFile, "config", "", "notify configuration file") set.StringVarP(&options.ProviderConfig, "provider-config", "pc", "", "provider config path (default: $HOME/.config/notify/provider-config.yaml)") set.StringVarP(&options.Data, "data", "i", "", "input file to send for notify") - set.NormalizedStringSliceVarP(&options.Providers, "provider", "p", []string{}, "provider to send the notification to (optional)") - set.NormalizedStringSliceVar(&options.IDs, "id", []string{}, "id to send the notification to (optional)") + set.StringSliceVarP(&options.Providers, "provider", "p", []string{}, "provider to send the notification to (optional)", goflags.NormalizedStringSliceOptions) + set.StringSliceVar(&options.IDs, "id", []string{}, "id to send the notification to (optional)", goflags.NormalizedStringSliceOptions) set.IntVarP(&options.RateLimit, "rate-limit", "rl", 1, "maximum number of HTTP requests to send per second") + set.IntVarP(&options.Delay, "delay", "d", 0, "delay in seconds between each notification") set.BoolVar(&options.Bulk, "bulk", false, "enable bulk processing") set.IntVarP(&options.CharLimit, "char-limit", "cl", 4000, "max character limit per message") - set.StringVarP(&options.MessageFormat, "msg-format", "mf", "{{data}}", "add custom formatting to message") + set.StringVarP(&options.MessageFormat, "msg-format", "mf", "", "add custom formatting to message") set.BoolVar(&options.Silent, "silent", false, "enable silent mode") set.BoolVarP(&options.Verbose, "verbose", "v", false, "enable verbose mode") set.BoolVar(&options.Version, "version", false, "display version") diff --git a/go.mod b/go.mod index dc08802..a414190 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,15 @@ go 1.17 require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/containrrr/shoutrrr v0.4.5-0.20220522113502-c91dc3cf1279 + github.com/containrrr/shoutrrr v0.6.1 github.com/json-iterator/go v1.1.12 + github.com/logrusorgru/aurora v2.0.3+incompatible github.com/oriser/regroup v0.0.0-20210730155327-fca8d7531263 github.com/pkg/errors v0.9.1 - github.com/projectdiscovery/fileutil v0.0.0-20220510111557-fba17e05663f - github.com/projectdiscovery/goflags v0.0.7 + github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963 + github.com/projectdiscovery/goflags v0.1.0 github.com/projectdiscovery/gologger v1.1.4 + github.com/projectdiscovery/sliceutil v0.0.0-20220625085859-c3a4ecb669f4 go.uber.org/multierr v1.8.0 go.uber.org/ratelimit v0.2.0 gopkg.in/yaml.v2 v2.4.0 @@ -19,17 +21,20 @@ require ( require ( github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect github.com/fatih/color v1.10.0 // indirect - github.com/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/gorilla/css v1.0.0 // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.12 // indirect + github.com/microcosm-cc/bluemonday v1.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/projectdiscovery/stringsutil v0.0.0-20220422150559-b54fb5dc6833 // indirect + github.com/projectdiscovery/stringsutil v0.0.1 // indirect + github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect - golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect + golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect google.golang.org/protobuf v1.25.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 178de23..a1f2d8d 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgp github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -18,8 +20,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= -github.com/containrrr/shoutrrr v0.4.5-0.20220522113502-c91dc3cf1279 h1:yfK5DlNtPcbQzVYFq0PgY1Nt/YQQmDMXJJMRI4jX6kg= -github.com/containrrr/shoutrrr v0.4.5-0.20220522113502-c91dc3cf1279/go.mod h1:PZKYVjVIabjVkRTbvK5N3t01xaZkD2d0hiRgzlHV6eA= +github.com/containrrr/shoutrrr v0.6.1 h1:6ih7jA6mo3t6C97MZbd3SxL/kRizOE3bI9CpBQZ6wzg= +github.com/containrrr/shoutrrr v0.6.1/go.mod h1:ye9jGX5YzMnJ76waaNVWlJ4luhMEyt1EWU5unYTQSb0= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -68,6 +70,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -103,6 +107,8 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y= +github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -133,15 +139,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/projectdiscovery/fileutil v0.0.0-20220510111557-fba17e05663f h1:6SdZmaGf/XQGkBtOjN8I9HOnvjYufNxDk+9iPVHYMxw= -github.com/projectdiscovery/fileutil v0.0.0-20220510111557-fba17e05663f/go.mod h1:qcx3tdBzVDlUvr5nMKrgzNkCNQpCV7kGbYlXBAtXz0o= -github.com/projectdiscovery/goflags v0.0.7 h1:aykmRkrOgDyRwcvGrK3qp+9aqcjGfAMs/+LtRmtyxwk= -github.com/projectdiscovery/goflags v0.0.7/go.mod h1:Jjwsf4eEBPXDSQI2Y+6fd3dBumJv/J1U0nmpM+hy2YY= +github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963 h1:4o97N9ftX1J3iKlIRVMPVOVZs4qbCczJvoFF2WA40t4= +github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963/go.mod h1:DaY7wmLPMleyHDCD/14YApPCDtrARY4J8Eny2ZGsG/g= +github.com/projectdiscovery/goflags v0.1.0 h1:Z7sUVK8wgH6aGJWinmGQEtsn+GNf/0RQ+z1wQcpCeeA= +github.com/projectdiscovery/goflags v0.1.0/go.mod h1:/YBPA+1igSkQbwD7a91o0HUIwMDlsmQDRZL2oSYSyEQ= github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTtMKunQ8O2VI= github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY= -github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I= -github.com/projectdiscovery/stringsutil v0.0.0-20220422150559-b54fb5dc6833 h1:yo7hCL47BOHl8X/aMmPeRQwiqUrH6TZ2WjgqItaSPcc= +github.com/projectdiscovery/sliceutil v0.0.0-20220625085859-c3a4ecb669f4 h1:C04j5gVVMXqFyBIetAz92SyPRYCpkFgIwZw0L/pps9Q= +github.com/projectdiscovery/sliceutil v0.0.0-20220625085859-c3a4ecb669f4/go.mod h1:RxDaccMjPzIuF7F8XbdGl1yOcqxN4YPiHr9xHpfCkGI= github.com/projectdiscovery/stringsutil v0.0.0-20220422150559-b54fb5dc6833/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I= +github.com/projectdiscovery/stringsutil v0.0.1 h1:a6TCMT+D1aUsoZxNiYf9O30wiDOoLOHDwj89HBjr5BQ= +github.com/projectdiscovery/stringsutil v0.0.1/go.mod h1:TDi2LEqR3OML0BxGoMbbfAHSk5AdfHX762Oc302sgmM= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -154,6 +162,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -171,13 +181,16 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -208,8 +221,9 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -229,16 +243,15 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -289,7 +302,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/runner/banner.go b/internal/runner/banner.go index 6ad47e1..c7a6425 100644 --- a/internal/runner/banner.go +++ b/internal/runner/banner.go @@ -8,12 +8,12 @@ const banner = ` __ _ ___ ___ ___ / /_(_) _/_ __ / _ \/ _ \/ __/ / _/ // / -/_//_/\___/\__/_/_/ \_, / v1.0.2 +/_//_/\___/\__/_/_/ \_, / v1.0.4 /___/ ` // Version is the current version -const Version = `1.0.2` +const Version = `1.0.4` // showBanner is used to show the banner to the user func showBanner() { diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 785c2e6..79b4d97 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -212,6 +212,9 @@ To use a specific one, post to http://%s/PROFILE func (r *Runner) sendMessage(msg string) error { if len(msg) > 0 { + if r.options.Delay > 0 { + time.Sleep(time.Duration(r.options.Delay) * time.Second) + } gologger.Silent().Msgf("%s\n", msg) err := r.providers.Send(msg) if err != nil { diff --git a/internal/testutils/integration.go b/internal/testutils/integration.go new file mode 100644 index 0000000..fa4887e --- /dev/null +++ b/internal/testutils/integration.go @@ -0,0 +1,41 @@ +package testutils + +import ( + "fmt" + "os/exec" + "strings" +) + +// RunNotifyAndGetResults returns a list of results for a template +func RunNotifyAndGetResults(debug bool, args ...string) ([]string, error) { + cmd := exec.Command("bash", "-c") + cmdLine := `echo "hello from notify integration test :)"` + ` | ./notify ` + cmdLine += strings.Join(args, " ") + + cmdLine += " --v" + + cmd.Args = append(cmd.Args, cmdLine) + data, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + parts := []string{} + items := strings.Split(string(data), "\n") + for _, i := range items { + if i != "" { + if debug { + fmt.Printf("%s\n", i) + } + if strings.Contains(i, "notification sent for id:") { + parts = append(parts, i) + } + } + } + return parts, nil +} + +// TestCase is a single integration test case +type TestCase interface { + // Execute executes a test case and returns any errors if occurred + Execute() error +} diff --git a/pkg/providers/custom/custom.go b/pkg/providers/custom/custom.go index 890c61a..98e9069 100644 --- a/pkg/providers/custom/custom.go +++ b/pkg/providers/custom/custom.go @@ -6,10 +6,12 @@ import ( "net/http" "github.com/pkg/errors" + "go.uber.org/multierr" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/utils" "github.com/projectdiscovery/notify/pkg/utils/httpreq" - "go.uber.org/multierr" + "github.com/projectdiscovery/sliceutil" ) type Provider struct { @@ -18,7 +20,7 @@ type Provider struct { type Options struct { ID string `yaml:"id,omitempty"` - CustomWebhookURL string `yaml:"custom_webook_url,omitempty"` + CustomWebhookURL string `yaml:"custom_webhook_url,omitempty"` CustomMethod string `yaml:"custom_method,omitempty"` CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` CustomFormat string `yaml:"custom_format,omitempty"` @@ -28,7 +30,7 @@ func New(options []*Options, ids []string) (*Provider, error) { provider := &Provider{} for _, o := range options { - if len(ids) == 0 || utils.Contains(ids, o.ID) { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { provider.Custom = append(provider.Custom, o) } } diff --git a/pkg/providers/discord/discord.go b/pkg/providers/discord/discord.go index 880c6fd..cc59d8e 100644 --- a/pkg/providers/discord/discord.go +++ b/pkg/providers/discord/discord.go @@ -4,11 +4,12 @@ import ( "fmt" "github.com/containrrr/shoutrrr" + "github.com/oriser/regroup" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" "go.uber.org/multierr" - "github.com/oriser/regroup" ) type Provider struct { @@ -27,7 +28,7 @@ func New(options []*Options, ids []string) (*Provider, error) { provider := &Provider{} for _, o := range options { - if len(ids) == 0 || utils.Contains(ids, o.ID) { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { provider.Discord = append(provider.Discord, o) } } @@ -39,16 +40,16 @@ func (p *Provider) Send(message, CliFormat string) error { for _, pr := range p.Discord { msg := utils.FormatMessage(message, utils.SelectFormat(CliFormat, pr.DiscordFormat)) - + discordWebhookRegex := regroup.MustCompile(`(?Phttps?):\/\/(?P(?:ptb\.|canary\.)?discord(?:app)?\.com)\/api(?:\/)?(?Pv\d{1,2})?\/webhooks\/(?P\d{17,19})\/(?P[\w\-]{68})`) matchedGroups, err := discordWebhookRegex.Groups(pr.DiscordWebHookURL) - + if err != nil { err := fmt.Errorf("incorrect discord configuration for id: %s ", pr.ID) DiscordErr = multierr.Append(DiscordErr, err) continue } - + webhookID, webhookToken := matchedGroups["webhook_identifier"], matchedGroups["webhook_token"] url := fmt.Sprintf("discord://%s@%s?splitlines=no", webhookToken, webhookID) sendErr := shoutrrr.Send(url, msg) diff --git a/pkg/providers/googlechat/googlechat.go b/pkg/providers/googlechat/googlechat.go new file mode 100644 index 0000000..085e75a --- /dev/null +++ b/pkg/providers/googlechat/googlechat.go @@ -0,0 +1,52 @@ +package googlechat + +import ( + "fmt" + + "github.com/containrrr/shoutrrr" + "github.com/pkg/errors" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" + "go.uber.org/multierr" +) + +type Provider struct { + GoogleChat []*Options `yaml:"googleChat,omitempty"` +} + +type Options struct { + ID string `yaml:"id,omitempty"` + Space string `yaml:"space,omitempty"` + Key string `yaml:"key,omitempty"` + Token string `yaml:"token,omitempty"` + GoogleChatFormat string `yaml:"google_chat_format,omitempty"` +} + +func New(options []*Options, ids []string) (*Provider, error) { + provider := &Provider{} + + for _, o := range options { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { + provider.GoogleChat = append(provider.GoogleChat, o) + } + } + + return provider, nil +} + +func (p *Provider) Send(message, CliFormat string) error { + var GoogleChatErr error + for _, pr := range p.GoogleChat { + msg := utils.FormatMessage(message, utils.SelectFormat(CliFormat, pr.GoogleChatFormat)) + url := fmt.Sprintf("googlechat://chat.googleapis.com/v1/spaces/%s/messages?key=%s&token=%s", pr.Space, pr.Key, pr.Token) + err := shoutrrr.Send(url, msg) + if err != nil { + err = errors.Wrap(err, fmt.Sprintf("failed to send googleChat notification for id: %s ", pr.ID)) + GoogleChatErr = multierr.Append(GoogleChatErr, err) + continue + } + gologger.Verbose().Msgf("googleChat notification sent for id: %s", pr.ID) + } + return GoogleChatErr +} diff --git a/pkg/providers/providers.go b/pkg/providers/providers.go index 501bb87..dc0ff4e 100644 --- a/pkg/providers/providers.go +++ b/pkg/providers/providers.go @@ -8,25 +8,27 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/providers/custom" "github.com/projectdiscovery/notify/pkg/providers/discord" + "github.com/projectdiscovery/notify/pkg/providers/googlechat" "github.com/projectdiscovery/notify/pkg/providers/pushover" "github.com/projectdiscovery/notify/pkg/providers/slack" "github.com/projectdiscovery/notify/pkg/providers/smtp" "github.com/projectdiscovery/notify/pkg/providers/teams" "github.com/projectdiscovery/notify/pkg/providers/telegram" "github.com/projectdiscovery/notify/pkg/types" - "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" "go.uber.org/multierr" ) // ProviderOptions is configuration for notify providers type ProviderOptions struct { - Slack []*slack.Options `yaml:"slack,omitempty"` - Discord []*discord.Options `yaml:"discord,omitempty"` - Pushover []*pushover.Options `yaml:"pushover,omitempty"` - SMTP []*smtp.Options `yaml:"smtp,omitempty"` - Teams []*teams.Options `yaml:"teams,omitempty"` - Telegram []*telegram.Options `yaml:"telegram,omitempty"` - Custom []*custom.Options `yaml:"custom,omitempty"` + Slack []*slack.Options `yaml:"slack,omitempty"` + Discord []*discord.Options `yaml:"discord,omitempty"` + Pushover []*pushover.Options `yaml:"pushover,omitempty"` + SMTP []*smtp.Options `yaml:"smtp,omitempty"` + Teams []*teams.Options `yaml:"teams,omitempty"` + Telegram []*telegram.Options `yaml:"telegram,omitempty"` + GoogleChat []*googlechat.Options `yaml:"googlechat,omitempty"` + Custom []*custom.Options `yaml:"custom,omitempty"` } // Provider is an interface implemented by providers @@ -45,7 +47,7 @@ func New(providerOptions *ProviderOptions, options *types.Options) (*Client, err client := &Client{providerOptions: providerOptions, options: options} totalProviders := 0 - if providerOptions.Slack != nil && (len(options.Providers) == 0 || utils.Contains(options.Providers, "slack")) { + if providerOptions.Slack != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "slack")) { provider, err := slack.New(providerOptions.Slack, options.IDs) if err != nil { @@ -55,7 +57,7 @@ func New(providerOptions *ProviderOptions, options *types.Options) (*Client, err client.providers = append(client.providers, provider) totalProviders += len(provider.Slack) } - if providerOptions.Discord != nil && (len(options.Providers) == 0 || utils.Contains(options.Providers, "discord")) { + if providerOptions.Discord != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "discord")) { provider, err := discord.New(providerOptions.Discord, options.IDs) if err != nil { @@ -64,7 +66,7 @@ func New(providerOptions *ProviderOptions, options *types.Options) (*Client, err client.providers = append(client.providers, provider) totalProviders += len(provider.Discord) } - if providerOptions.Pushover != nil && (len(options.Providers) == 0 || utils.Contains(options.Providers, "pushover")) { + if providerOptions.Pushover != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "pushover")) { provider, err := pushover.New(providerOptions.Pushover, options.IDs) if err != nil { @@ -73,7 +75,15 @@ func New(providerOptions *ProviderOptions, options *types.Options) (*Client, err client.providers = append(client.providers, provider) totalProviders += len(provider.Pushover) } - if providerOptions.SMTP != nil && (len(options.Providers) == 0 || utils.Contains(options.Providers, "smtp")) { + if providerOptions.GoogleChat != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "googlechat")) { + + provider, err := googlechat.New(providerOptions.GoogleChat, options.IDs) + if err != nil { + return nil, errors.Wrap(err, "could not create googlechat provider client") + } + client.providers = append(client.providers, provider) + } + if providerOptions.SMTP != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "smtp")) { provider, err := smtp.New(providerOptions.SMTP, options.IDs) if err != nil { @@ -82,7 +92,7 @@ func New(providerOptions *ProviderOptions, options *types.Options) (*Client, err client.providers = append(client.providers, provider) totalProviders += len(provider.SMTP) } - if providerOptions.Teams != nil && (len(options.Providers) == 0 || utils.Contains(options.Providers, "teams")) { + if providerOptions.Teams != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "teams")) { provider, err := teams.New(providerOptions.Teams, options.IDs) if err != nil { @@ -91,7 +101,7 @@ func New(providerOptions *ProviderOptions, options *types.Options) (*Client, err client.providers = append(client.providers, provider) totalProviders += len(provider.Teams) } - if providerOptions.Telegram != nil && (len(options.Providers) == 0 || utils.Contains(options.Providers, "telegram")) { + if providerOptions.Telegram != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "telegram")) { provider, err := telegram.New(providerOptions.Telegram, options.IDs) if err != nil { @@ -101,7 +111,7 @@ func New(providerOptions *ProviderOptions, options *types.Options) (*Client, err totalProviders += len(provider.Telegram) } - if providerOptions.Custom != nil && (len(options.Providers) == 0 || utils.Contains(options.Providers, "custom")) { + if providerOptions.Custom != nil && (len(options.Providers) == 0 || sliceutil.Contains(options.Providers, "custom")) { provider, err := custom.New(providerOptions.Custom, options.IDs) if err != nil { diff --git a/pkg/providers/pushover/pushover.go b/pkg/providers/pushover/pushover.go index 5fdce66..e78d13c 100644 --- a/pkg/providers/pushover/pushover.go +++ b/pkg/providers/pushover/pushover.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" "go.uber.org/multierr" ) @@ -27,7 +28,7 @@ func New(options []*Options, ids []string) (*Provider, error) { provider := &Provider{} for _, o := range options { - if len(ids) == 0 || utils.Contains(ids, o.ID) { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { provider.Pushover = append(provider.Pushover, o) } } diff --git a/pkg/providers/slack/slack.go b/pkg/providers/slack/slack.go index 43d5272..4cf6893 100644 --- a/pkg/providers/slack/slack.go +++ b/pkg/providers/slack/slack.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" "go.uber.org/multierr" ) @@ -31,7 +32,7 @@ func New(options []*Options, ids []string) (*Provider, error) { provider := &Provider{} for _, o := range options { - if len(ids) == 0 || utils.Contains(ids, o.ID) { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { provider.Slack = append(provider.Slack, o) } } @@ -78,7 +79,7 @@ func (p *Provider) Send(message, CliFormat string) error { continue } } - gologger.Verbose().Msgf("Slack notification sent successfully for id: %s", pr.ID) + gologger.Verbose().Msgf("Slack notification sent for id: %s", pr.ID) } return SlackErr diff --git a/pkg/providers/smtp/smtp.go b/pkg/providers/smtp/smtp.go index 72b1870..5d2033a 100644 --- a/pkg/providers/smtp/smtp.go +++ b/pkg/providers/smtp/smtp.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" "go.uber.org/multierr" ) @@ -30,7 +31,7 @@ func New(options []*Options, ids []string) (*Provider, error) { provider := &Provider{} for _, o := range options { - if len(ids) == 0 || utils.Contains(ids, o.ID) { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { provider.SMTP = append(provider.SMTP, o) } } diff --git a/pkg/providers/teams/teams.go b/pkg/providers/teams/teams.go index 57c6c1c..93da3d6 100644 --- a/pkg/providers/teams/teams.go +++ b/pkg/providers/teams/teams.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" "go.uber.org/multierr" ) @@ -25,7 +26,7 @@ func New(options []*Options, ids []string) (*Provider, error) { provider := &Provider{} for _, o := range options { - if len(ids) == 0 || utils.Contains(ids, o.ID) { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { provider.Teams = append(provider.Teams, o) } } @@ -37,10 +38,14 @@ func (p *Provider) Send(message, CliFormat string) error { var TeamsErr error for _, pr := range p.Teams { msg := utils.FormatMessage(message, utils.SelectFormat(CliFormat, pr.TeamsFormat)) - - teamsTokens := strings.TrimPrefix(pr.TeamsWebHookURL, "https://outlook.office.com/webhook/") - teamsTokens = strings.ReplaceAll(teamsTokens, "IncomingWebhook/", "") - url := fmt.Sprintf("teams://%s", teamsTokens) + webhookParts := strings.Split(pr.TeamsWebHookURL, "/webhookb2/") + if len(webhookParts) != 2 { + err := fmt.Errorf("teams: invalid webhook url for id: %s ", pr.ID) + TeamsErr = multierr.Append(TeamsErr, err) + } + teamsHost := strings.TrimPrefix(webhookParts[0], "https://") + teamsTokens := strings.ReplaceAll(webhookParts[1], "IncomingWebhook/", "") + url := fmt.Sprintf("teams://%s?host=%s", teamsTokens, teamsHost) err := shoutrrr.Send(url, msg) if err != nil { err = errors.Wrap(err, fmt.Sprintf("failed to send teams notification for id: %s ", pr.ID)) diff --git a/pkg/providers/telegram/telegram.go b/pkg/providers/telegram/telegram.go index 7c70043..8252675 100644 --- a/pkg/providers/telegram/telegram.go +++ b/pkg/providers/telegram/telegram.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/notify/pkg/utils" + "github.com/projectdiscovery/sliceutil" "go.uber.org/multierr" ) @@ -15,17 +16,18 @@ type Provider struct { } type Options struct { - ID string `yaml:"id,omitempty"` - TelegramAPIKey string `yaml:"telegram_api_key,omitempty"` - TelegramChatID string `yaml:"telegram_chat_id,omitempty"` - TelegramFormat string `yaml:"telegram_format,omitempty"` + ID string `yaml:"id,omitempty"` + TelegramAPIKey string `yaml:"telegram_api_key,omitempty"` + TelegramChatID string `yaml:"telegram_chat_id,omitempty"` + TelegramFormat string `yaml:"telegram_format,omitempty"` + TelegramParseMode string `yaml:"telegram_parsemode,omitempty"` } func New(options []*Options, ids []string) (*Provider, error) { provider := &Provider{} for _, o := range options { - if len(ids) == 0 || utils.Contains(ids, o.ID) { + if len(ids) == 0 || sliceutil.Contains(ids, o.ID) { provider.Telegram = append(provider.Telegram, o) } } @@ -37,8 +39,10 @@ func (p *Provider) Send(message, CliFormat string) error { var TelegramErr error for _, pr := range p.Telegram { msg := utils.FormatMessage(message, utils.SelectFormat(CliFormat, pr.TelegramFormat)) - - url := fmt.Sprintf("telegram://%s@telegram?channels=%s", pr.TelegramAPIKey, pr.TelegramChatID) + if pr.TelegramParseMode == "" { + pr.TelegramParseMode = "None" + } + url := fmt.Sprintf("telegram://%s@telegram?channels=%s&parsemode=%s", pr.TelegramAPIKey, pr.TelegramChatID, pr.TelegramParseMode) err := shoutrrr.Send(url, msg) if err != nil { err = errors.Wrap(err, fmt.Sprintf("failed to send telegram notification for id: %s ", pr.ID)) diff --git a/pkg/types/types.go b/pkg/types/types.go index cdfdd31..2e881fe 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -3,17 +3,18 @@ package types import "github.com/projectdiscovery/goflags" type Options struct { - Verbose bool `yaml:"verbose,omitempty"` - NoColor bool `yaml:"no_color,omitempty"` - Silent bool `yaml:"silent,omitempty"` - Version bool `yaml:"version,omitempty"` - ProviderConfig string `yaml:"provider_config,omitempty"` - Providers goflags.NormalizedStringSlice `yaml:"providers,omitempty"` - IDs goflags.NormalizedStringSlice `yaml:"ids,omitempty"` - Proxy string `yaml:"proxy,omitempty"` - RateLimit int `yaml:"rate_limit,omitempty"` - Web bool `yaml:"web,omitempty"` - WebBind string `yaml:"web_bind,omitempty"` + Verbose bool `yaml:"verbose,omitempty"` + NoColor bool `yaml:"no_color,omitempty"` + Silent bool `yaml:"silent,omitempty"` + Version bool `yaml:"version,omitempty"` + ProviderConfig string `yaml:"provider_config,omitempty"` + Providers goflags.StringSlice `yaml:"providers,omitempty"` + IDs goflags.StringSlice `yaml:"ids,omitempty"` + Proxy string `yaml:"proxy,omitempty"` + RateLimit int `yaml:"rate_limit,omitempty"` + Delay int `yaml:"delay,omitempty"` + Web bool `yaml:"web,omitempty"` + WebBind string `yaml:"web_bind,omitempty"` MessageFormat string `yaml:"message_format,omitempty"` diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 97e5737..71192f9 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -6,25 +6,19 @@ import ( const defaultFormat = "{{data}}" -func Contains(slice []string, item string) bool { - set := make(map[string]struct{}, len(slice)) - for _, s := range slice { - set[s] = struct{}{} - } - - _, ok := set[item] - return ok - -} - +// FormatMessage formats the message according to the format string func FormatMessage(msg, format string) string { return strings.Replace(format, defaultFormat, msg, -1) } +// SelectFormat returns the format string in the following order of precedence: +// 1. cliFormat +// 2. configFormat +// 3. defaulFormat func SelectFormat(cliFormat, configFormat string) string { - if cliFormat != "" && cliFormat != defaultFormat { + if cliFormat != "" { return cliFormat - } else if configFormat != "" && configFormat != defaultFormat { + } else if configFormat != "" { return configFormat } return defaultFormat