mirror of https://github.com/hak5/scuzzy.git
start building out automatic spam detection, push some stale changes, very incomplete
parent
e8f401801c
commit
574edde030
|
@ -204,11 +204,15 @@ func (c *Commands) ProcessMessage(s *discordgo.Session, m interface{}) {
|
|||
switch m.(type) {
|
||||
case *discordgo.MessageCreate:
|
||||
// Pass Messages to the command processor
|
||||
track(m.(*discordgo.MessageCreate).Message)
|
||||
err := c.ProcessCommand(s, m.(*discordgo.MessageCreate))
|
||||
if err != nil {
|
||||
log.Println("[!] Error: " + err.Error())
|
||||
}
|
||||
break
|
||||
case *discordgo.MessageEdit:
|
||||
//TODO logging
|
||||
break
|
||||
case *discordgo.MessageDelete:
|
||||
// Log deleted messages to the logging channel.
|
||||
err := c.ProcessMessageDelete(s, m.(*discordgo.MessageDelete))
|
||||
|
|
|
@ -2,12 +2,11 @@ package commands
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/foxtrot/scuzzy/actions"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/foxtrot/scuzzy/actions"
|
||||
)
|
||||
|
||||
func (c *Commands) handleSetSlowmode(s *discordgo.Session, m *discordgo.MessageCreate) error {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/foxtrot/scuzzy/models"
|
||||
"hash/fnv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Tracking map[uint32]models.TrackedMessage
|
||||
var windowToConsiderIrrelevant = time.Minute * 5
|
||||
var windowToConsiderDuplicate = time.Second * 30
|
||||
var crossChannelSpamCountThreshold = 1
|
||||
var spamCountThreshold = 3
|
||||
|
||||
func hashContent(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
_, err := h.Write([]byte(s))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return h.Sum32()
|
||||
}
|
||||
|
||||
func track(m *discordgo.Message) bool {
|
||||
//todo ignore self bot
|
||||
hash := hashContent(m.Content)
|
||||
record, exists := Tracking[hash]
|
||||
if !exists {
|
||||
newMessage := models.TrackedMessage{
|
||||
Author: m.Author,
|
||||
Instances: map[string]models.CachedMessage{},
|
||||
MessageContent: m.Content,
|
||||
}
|
||||
newMessage.Instances[m.ChannelID] = models.CachedMessage{
|
||||
ID: m.ID,
|
||||
CreatedAt: time.Now(),
|
||||
Count: 1,
|
||||
}
|
||||
Tracking[hash] = newMessage
|
||||
} else {
|
||||
//ModerationReason := "[DELETED + TIMEOUT] Multiple of the same exact message sent repeatedly in the same channel within a short period of time is considered spam and was removed"
|
||||
chanMessage, existsInChannel := record.Instances[m.ChannelID]
|
||||
if existsInChannel {
|
||||
chanMessage.Count++
|
||||
record.Instances[m.ChannelID] = chanMessage
|
||||
Tracking[hash] = record
|
||||
|
||||
if time.Since(chanMessage.CreatedAt) > windowToConsiderIrrelevant {
|
||||
// this tracking has "expired"
|
||||
delete(record.Instances, m.ChannelID)
|
||||
Tracking[hash] = record
|
||||
return false
|
||||
}
|
||||
countViolation := chanMessage.Count > spamCountThreshold
|
||||
frequencyViolation := time.Since(chanMessage.CreatedAt) < windowToConsiderDuplicate
|
||||
switch true {
|
||||
case countViolation && frequencyViolation:
|
||||
// remove obviously spam qualified by both frequency and quantity
|
||||
cleanupDuplicates(record.Instances)
|
||||
timeoutUser()
|
||||
return true
|
||||
case countViolation:
|
||||
cleanupDuplicates(record.Instances)
|
||||
return true
|
||||
case frequencyViolation:
|
||||
cleanupDuplicates(record.Instances)
|
||||
return true
|
||||
default:
|
||||
warnUser()
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
channelCount := 0
|
||||
smallestTimeSince := time.Duration(10000000)
|
||||
for _, _ = range record.Instances {
|
||||
channelCount++
|
||||
since := time.Since(chanMessage.CreatedAt)
|
||||
if smallestTimeSince < since {
|
||||
smallestTimeSince = since
|
||||
}
|
||||
}
|
||||
countViolation := channelCount > crossChannelSpamCountThreshold
|
||||
frequencyViolation := smallestTimeSince < windowToConsiderDuplicate
|
||||
switch true {
|
||||
case countViolation && frequencyViolation:
|
||||
// remove obviously spam qualified by both frequency and quantity
|
||||
cleanupDuplicates(record.Instances)
|
||||
timeoutUser()
|
||||
return true
|
||||
case countViolation:
|
||||
cleanupDuplicates(record.Instances)
|
||||
return true
|
||||
case frequencyViolation:
|
||||
cleanupDuplicates(record.Instances)
|
||||
return true
|
||||
default:
|
||||
warnUser()
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cleanupDuplicates(instances map[string]models.CachedMessage) {
|
||||
|
||||
}
|
||||
|
||||
func warnUser() {
|
||||
|
||||
}
|
||||
|
||||
func timeoutUser() {
|
||||
|
||||
}
|
|
@ -11,6 +11,46 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// TODO for use in command arguments
|
||||
/* Match on one two or three word arguments*/
|
||||
func aliasMatch(product models.Product, args []string) bool {
|
||||
return strings.Contains(strings.Join(product.Aliases[:], " "), args[1]) ||
|
||||
strings.Contains(strings.Join(product.Aliases, " "), strings.Join(args[1:3], " ")) ||
|
||||
strings.Contains(strings.Join(product.Aliases, " "), strings.Join(args[1:4], " "))
|
||||
}
|
||||
|
||||
func (c *Commands) matchAlias(args []string) models.Product {
|
||||
switch true {
|
||||
case aliasMatch(models.Ducky, args):
|
||||
return models.Ducky
|
||||
case aliasMatch(models.PayloadStudio, args):
|
||||
return models.PayloadStudio
|
||||
case aliasMatch(models.Bunny, args):
|
||||
return models.Bunny
|
||||
case aliasMatch(models.Croc, args):
|
||||
return models.Croc
|
||||
case aliasMatch(models.Shark, args):
|
||||
return models.Shark
|
||||
case aliasMatch(models.CloudC2, args):
|
||||
return models.CloudC2
|
||||
case aliasMatch(models.Crab, args):
|
||||
return models.Crab
|
||||
case aliasMatch(models.Pineapple, args):
|
||||
return models.Pineapple
|
||||
case aliasMatch(models.Coconut, args):
|
||||
return models.Coconut
|
||||
case aliasMatch(models.Squirrel, args):
|
||||
return models.Squirrel
|
||||
case aliasMatch(models.Turtle, args):
|
||||
return models.Turtle
|
||||
case aliasMatch(models.OMG, args):
|
||||
return models.OMG
|
||||
default:
|
||||
return models.Default
|
||||
}
|
||||
}
|
||||
|
||||
// why i didnt just use a map? because im dumb
|
||||
func (c *Commands) getProductFromChannelID(cid string) models.Product {
|
||||
switch cid {
|
||||
case models.Ducky.ChannelID:
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CachedMessage struct {
|
||||
ID string
|
||||
CreatedAt time.Time
|
||||
Count int
|
||||
}
|
||||
|
||||
type TrackedMessage struct {
|
||||
Author *discordgo.User `json:"author"`
|
||||
Instances map[string]CachedMessage
|
||||
MessageContent string
|
||||
}
|
|
@ -2,6 +2,7 @@ package models
|
|||
|
||||
type Product struct {
|
||||
ProductName string `json:"productName"`
|
||||
Aliases []string `json:"aliases"`
|
||||
Urllabel string `json:"urllabel"`
|
||||
Emoji string `json:"logo"`
|
||||
ChannelID string `json:"channelid"`
|
||||
|
@ -12,6 +13,7 @@ type Product struct {
|
|||
Githublink string `json:"githublink"`
|
||||
InviteLink string `json:"invitelink"`
|
||||
SupportLink string `json:"supportlink"`
|
||||
GettingStarted string `json:"gettingstarted"`
|
||||
}
|
||||
|
||||
var GITBOOKAPI = "https://api.gitbook.com/v1/spaces/"
|
||||
|
@ -19,6 +21,7 @@ var SEARCHENDPOINT = "/search/ask"
|
|||
|
||||
var Default = Product{
|
||||
ProductName: "",
|
||||
Aliases: []string{},
|
||||
Urllabel: "",
|
||||
Emoji: "<:hak5:1063661436187975701>",
|
||||
ChannelID: "824326326076702770",
|
||||
|
@ -29,10 +32,12 @@ var Default = Product{
|
|||
Githublink: "https://github.com/hak5",
|
||||
InviteLink: "https://discord.gg/hak5",
|
||||
SupportLink: "https://hak5.org/support",
|
||||
GettingStarted: "https://docs.hak5.org",
|
||||
}
|
||||
|
||||
var Ducky = Product{
|
||||
ProductName: "USB Rubber Ducky",
|
||||
Aliases: []string{"usb rubber ducky", "ducky", "duck", "rubber ducky"},
|
||||
Urllabel: "usb-rubber-ducky",
|
||||
Emoji: "<:Rubber_Ducky:1063661661795385384>",
|
||||
ChannelID: "522275837651714048",
|
||||
|
@ -48,6 +53,7 @@ var Ducky = Product{
|
|||
var PayloadStudio = Product{
|
||||
ProductName: "PayloadStudio",
|
||||
Urllabel: Default.Urllabel,
|
||||
Aliases: []string{"PayloadStudio", "PS", "PSP", "payload studio"},
|
||||
Emoji: "<:payload_studio:1053210968064262234>",
|
||||
ChannelID: "1006233482957164634",
|
||||
SpaceID: "RgTCQkzfO7AUWTT3gFAq",
|
||||
|
@ -61,6 +67,7 @@ var PayloadStudio = Product{
|
|||
var CloudC2 = Product{
|
||||
ProductName: "Cloud C2",
|
||||
Urllabel: Default.Urllabel,
|
||||
Aliases: []string{"CloudC2", "Cloud C2", "C2"},
|
||||
Emoji: "<:Cloud_C2:1063661578181943336>",
|
||||
ChannelID: "522276096746717184",
|
||||
SpaceID: "<:Cloud_C2:1063661578181943336>",
|
||||
|
@ -74,6 +81,7 @@ var CloudC2 = Product{
|
|||
var Crab = Product{
|
||||
ProductName: "Screen Crab",
|
||||
Urllabel: Default.Urllabel,
|
||||
Aliases: []string{"screen crab", "crab"},
|
||||
Emoji: "<:Screen_Crab:1063661831756988427>",
|
||||
ChannelID: "608057573517557809",
|
||||
SpaceID: "-MiWySN4BHDJlUatEfm3",
|
||||
|
@ -87,6 +95,7 @@ var Crab = Product{
|
|||
var Coconut = Product{
|
||||
ProductName: "WiFi Coconut",
|
||||
Urllabel: Default.Urllabel,
|
||||
Aliases: []string{"wifi coconut", "coconut"},
|
||||
Emoji: "<:WiFi_Coconut:1063661830309953606>",
|
||||
ChannelID: "1007363521098551349",
|
||||
SpaceID: "DkilLranx3TNqKgzbBZ9",
|
||||
|
@ -100,6 +109,7 @@ var Coconut = Product{
|
|||
var Pineapple = Product{
|
||||
ProductName: "WiFi Pineapple",
|
||||
Urllabel: Default.Urllabel,
|
||||
Aliases: []string{"wifi pineapple", "pineapple"},
|
||||
Emoji: "<:WiFi_Pineapple:1063661628207411251>",
|
||||
ChannelID: "522275782731497473",
|
||||
SpaceID: "-Mhuhsyl_byoEWXOc5EU",
|
||||
|
@ -114,6 +124,7 @@ var Croc = Product{
|
|||
ProductName: "Key Croc",
|
||||
Urllabel: "key-croc",
|
||||
Emoji: "<:Key_Croc:1063661834093199431>",
|
||||
Aliases: []string{"key croc", "croc"},
|
||||
ChannelID: "709235715120168970",
|
||||
SpaceID: "-MhLOzjhonMdC6SLKqRt",
|
||||
Docslink: "https://docs.hak5.org/key-croc/",
|
||||
|
@ -127,6 +138,7 @@ var Bunny = Product{
|
|||
ProductName: "Bash Bunny",
|
||||
Urllabel: "bash-bunny",
|
||||
Emoji: "<:Bash_Bunny:1063661828753858680>",
|
||||
Aliases: []string{"bash bunny", "bunny", "bb"},
|
||||
ChannelID: "1009919913877590146",
|
||||
SpaceID: "nxJgJ9UdPfrcuL1U8DpL",
|
||||
Docslink: "https://docs.hak5.org/bash-bunny/",
|
||||
|
@ -140,6 +152,7 @@ var Squirrel = Product{
|
|||
ProductName: "Packet Squirrel",
|
||||
Urllabel: "packet-squirrel",
|
||||
Emoji: "<:packet_squirrel:1063661837398315079>",
|
||||
Aliases: []string{"packet squirrel", "squirrel"},
|
||||
ChannelID: "522275913031745548",
|
||||
SpaceID: "-MiX86qQFvjhg-a6RwWr",
|
||||
Docslink: "https://docs.hak5.org/packet-squirrel",
|
||||
|
@ -152,6 +165,7 @@ var Squirrel = Product{
|
|||
var Turtle = Product{
|
||||
ProductName: "LAN Turtle",
|
||||
Urllabel: "lan-turtle",
|
||||
Aliases: []string{"lan turtle", "turtle"},
|
||||
Emoji: "<:lan_turtle:1063661838937620480>",
|
||||
ChannelID: "522275913031745548",
|
||||
SpaceID: "N8g6UIGOyv4mW7rOOuC8",
|
||||
|
@ -165,6 +179,7 @@ var Turtle = Product{
|
|||
var Shark = Product{
|
||||
ProductName: "Shark Jack",
|
||||
Urllabel: "shark-jack",
|
||||
Aliases: []string{"shark jack", "shark"},
|
||||
Emoji: "<:Shark_Jack:1063661835888381952>",
|
||||
ChannelID: "610344558655438858",
|
||||
SpaceID: "-MhxW6geyenAGJvaKW11",
|
||||
|
@ -178,6 +193,7 @@ var Shark = Product{
|
|||
var OMG = Product{
|
||||
ProductName: "O.MG Devices",
|
||||
Urllabel: "omg",
|
||||
Aliases: []string{"omg", "omg cable", "omg adapter", "omg plug", "plug", "cable"},
|
||||
Emoji: "<:omg:1063696145022468116>",
|
||||
ChannelID: "953129985131040838",
|
||||
SpaceID: Default.SpaceID,
|
||||
|
|
Loading…
Reference in New Issue