Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discord threads #191

Merged
merged 9 commits into from Mar 9, 2023
Merged
8 changes: 7 additions & 1 deletion .github/dependabot.yml
Expand Up @@ -15,6 +15,8 @@ updates:
commit-message:
prefix: "chore"
include: "scope"
labels:
- "Type: Maintenance"

# Maintain dependencies for go modules
- package-ecosystem: "gomod"
Expand All @@ -25,6 +27,8 @@ updates:
commit-message:
prefix: "chore"
include: "scope"
labels:
- "Type: Maintenance"

# Maintain dependencies for docker
- package-ecosystem: "docker"
Expand All @@ -34,4 +38,6 @@ updates:
target-branch: "dev"
commit-message:
prefix: "chore"
include: "scope"
include: "scope"
labels:
- "Type: Maintenance"
1 change: 0 additions & 1 deletion .github/workflows/build-test.yml
Expand Up @@ -3,7 +3,6 @@ on:
pull_request:
workflow_dispatch:


jobs:
build:
name: Test Builds
Expand Down
4 changes: 2 additions & 2 deletions internal/runner/runner.go
Expand Up @@ -3,7 +3,7 @@ package runner
import (
"bufio"
"crypto/tls"
"io/ioutil"
"io"
"log"
"net/http"
"net/url"
Expand Down Expand Up @@ -51,7 +51,7 @@ func NewRunner(options *types.Options) (*Runner, error) {
}

// Discard all internal logs
shoutrrr.SetLogger(log.New(ioutil.Discard, "", 0))
shoutrrr.SetLogger(log.New(io.Discard, "", 0))

prClient, err := providers.New(&providerOptions, options)
if err != nil {
Expand Down
56 changes: 36 additions & 20 deletions pkg/providers/discord/discord.go
Expand Up @@ -2,7 +2,6 @@ package discord

import (
"fmt"
"net/url"

"github.com/containrrr/shoutrrr"
"github.com/oriser/regroup"
Expand All @@ -14,6 +13,8 @@ import (
sliceutil "github.com/projectdiscovery/utils/slice"
)

var reDiscordWebhook = regroup.MustCompile(`(?P<scheme>https?):\/\/(?P<domain>(?:ptb\.|canary\.)?discord(?:app)?\.com)\/api(?:\/)?(?P<api_version>v\d{1,2})?\/webhooks\/(?P<webhook_identifier>\d{17,19})\/(?P<webhook_token>[\w\-]{68})`)

type Provider struct {
Discord []*Options `yaml:"discord,omitempty"`
counter int
Expand All @@ -24,6 +25,8 @@ type Options struct {
DiscordWebHookURL string `yaml:"discord_webhook_url,omitempty"`
DiscordWebHookUsername string `yaml:"discord_username,omitempty"`
DiscordWebHookAvatarURL string `yaml:"discord_avatar,omitempty"`
DiscordThreads bool `yaml:"discord_threads,omitempty"`
DiscordThreadID string `yaml:"discord_thread_id,omitempty"`
DiscordFormat string `yaml:"discord_format,omitempty"`
}

Expand All @@ -41,31 +44,44 @@ func New(options []*Options, ids []string) (*Provider, error) {
return provider, nil
}
func (p *Provider) Send(message, CliFormat string) error {
var DiscordErr error
p.counter++
var errs []error
for _, pr := range p.Discord {
msg := utils.FormatMessage(message, utils.SelectFormat(CliFormat, pr.DiscordFormat), p.counter)

discordWebhookRegex := regroup.MustCompile(`(?P<scheme>https?):\/\/(?P<domain>(?:ptb\.|canary\.)?discord(?:app)?\.com)\/api(?:\/)?(?P<api_version>v\d{1,2})?\/webhooks\/(?P<webhook_identifier>\d{17,19})\/(?P<webhook_token>[\w\-]{68})`)
matchedGroups, err := discordWebhookRegex.Groups(pr.DiscordWebHookURL)
if pr.DiscordThreads {
if pr.DiscordThreadID == "" {
err := fmt.Errorf("thread_id value is required when discord_threads is set to true. check your configuration at id: %s", pr.ID)
errs = append(errs, err)
continue
}
if err := pr.SendThreaded(msg); err != nil {
err = errors.Wrapf(err, "failed to send discord notification for id: %s ", pr.ID)
errs = append(errs, err)
continue
}

if err != nil {
err := fmt.Errorf("incorrect discord configuration for id: %s ", pr.ID)
DiscordErr = multierr.Append(DiscordErr, err)
continue
}
} else {
matchedGroups, err := reDiscordWebhook.Groups(pr.DiscordWebHookURL)
if err != nil {
err := fmt.Errorf("incorrect discord configuration for id: %s ", pr.ID)
errs = append(errs, err)
continue
}

webhookID, webhookToken := matchedGroups["webhook_identifier"], matchedGroups["webhook_token"]
url := fmt.Sprintf("discord://%s@%s?splitlines=no&username=%s", webhookToken, webhookID,
url.QueryEscape(pr.DiscordWebHookUsername))
webhookID, webhookToken := matchedGroups["webhook_identifier"], matchedGroups["webhook_token"]

sendErr := shoutrrr.Send(url, msg)
if sendErr != nil {
sendErr = errors.Wrap(sendErr, fmt.Sprintf("failed to send discord notification for id: %s ", pr.ID))
DiscordErr = multierr.Append(DiscordErr, sendErr)
continue
//Reference: https://containrrr.dev/shoutrrr/v0.6/getting-started/
url := fmt.Sprintf("discord://%s@%s?username=%s&avatarurl=%s&splitlines=no",
webhookToken,
webhookID,
pr.DiscordWebHookUsername,
pr.DiscordWebHookAvatarURL)
if err := shoutrrr.Send(url, msg); err != nil {
errs = append(errs, errors.Wrapf(err, "failed to send discord notification for id: %s ", pr.ID))
}
}

gologger.Verbose().Msgf("discord notification sent for id: %s", pr.ID)
}
return DiscordErr
}
return multierr.Combine(errs...)
}
41 changes: 41 additions & 0 deletions pkg/providers/discord/discord_api.go
@@ -0,0 +1,41 @@
package discord

import (
"bytes"
"encoding/json"
"fmt"
"net/http"

"github.com/projectdiscovery/notify/pkg/utils/httpreq"
)

func (options *Options) SendThreaded(message string) error {

payload := APIRequest{
Content: message,
Username: options.DiscordWebHookUsername,
AvatarURL: options.DiscordWebHookAvatarURL,
}

encoded, err := json.Marshal(payload)
if err != nil {
return err
}

body := bytes.NewReader(encoded)

webHookURL := fmt.Sprintf("%s?thread_id=%s", options.DiscordWebHookURL, options.DiscordThreadID)

req, err := http.NewRequest(http.MethodPost, webHookURL, body)
req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}

_, err = httpreq.NewClient().Do(req)
if err != nil {
return err
}

return nil
}
7 changes: 7 additions & 0 deletions pkg/providers/discord/discord_types.go
@@ -0,0 +1,7 @@
package discord

type APIRequest struct {
Content string `json:"content,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
Username string `json:"username,omitempty"`
}