Add anti-raid and anti-nuke protection
Join Gate (account age, avatar, advertising and username filters, bot checks), raid detection with invite pausing, anti-nuke quarantine of admins who mass delete, manual and automatic lockdowns, a decaying heat score against spam, role and channel snapshots with restore, and alert DMs to the owner and trusted users. Everything that weakens protection is limited to the owner and the trusted list. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d294b3463b
commit
3f87e68a2c
26 files changed
+6022
No files matched your search
@@ -0,0 +1,557 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"velox-bot/internal/db/repos/securityrepo"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
// fakeStore keeps everything in memory with the same rules as the SQL.
|
||||
type fakeStore struct {
|
||||
mu sync.Mutex
|
||||
settings map[int64]*securityrepo.Settings
|
||||
trusted map[int64][]securityrepo.Trusted
|
||||
quarantines map[string]securityrepo.Quarantine
|
||||
lockdowns map[string]securityrepo.Lockdown
|
||||
events []securityrepo.Event
|
||||
cmds []securityrepo.Command
|
||||
done map[int64]bool
|
||||
raids map[int64]securityrepo.Raid
|
||||
caught map[int64][]securityrepo.Caught
|
||||
snapshots []securityrepo.Snapshot
|
||||
}
|
||||
|
||||
func newStore() *fakeStore {
|
||||
return &fakeStore{
|
||||
settings: map[int64]*securityrepo.Settings{}, trusted: map[int64][]securityrepo.Trusted{},
|
||||
quarantines: map[string]securityrepo.Quarantine{}, lockdowns: map[string]securityrepo.Lockdown{}, done: map[int64]bool{},
|
||||
raids: map[int64]securityrepo.Raid{}, caught: map[int64][]securityrepo.Caught{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeStore) SaveRaid(_ context.Context, x securityrepo.Raid) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if old, ok := f.raids[x.GuildID]; ok {
|
||||
x.StartedAt = old.StartedAt
|
||||
}
|
||||
f.raids[x.GuildID] = x
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) ListRaids(context.Context) ([]securityrepo.Raid, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []securityrepo.Raid
|
||||
for _, x := range f.raids {
|
||||
out = append(out, x)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (f *fakeStore) DeleteRaid(_ context.Context, g int64) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
delete(f.raids, g)
|
||||
delete(f.caught, g)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) AddRaidCaught(_ context.Context, g int64, c securityrepo.Caught) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
for _, x := range f.caught[g] {
|
||||
if x.UserID == c.UserID {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
f.caught[g] = append(f.caught[g], c)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) ListRaidCaught(_ context.Context, g int64, limit int) ([]securityrepo.Caught, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
out := f.caught[g]
|
||||
if len(out) > limit {
|
||||
out = out[:limit]
|
||||
}
|
||||
return append([]securityrepo.Caught(nil), out...), nil
|
||||
}
|
||||
func (f *fakeStore) SaveSnapshot(_ context.Context, s securityrepo.Snapshot) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
s.ID = int64(len(f.snapshots) + 1)
|
||||
s.CreatedAt = time.Date(2026, 10, 20, 6, 0, 0, 0, time.UTC)
|
||||
f.snapshots = append(f.snapshots, s)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) LatestSnapshot(_ context.Context, g int64) (*securityrepo.Snapshot, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
for i := len(f.snapshots) - 1; i >= 0; i-- {
|
||||
if f.snapshots[i].GuildID == g {
|
||||
s := f.snapshots[i]
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeStore) GuildsDueForSnapshot(context.Context, time.Duration) ([]int64, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func key(a, b int64) string { return fmt.Sprint(a, "/", b) }
|
||||
|
||||
func (f *fakeStore) set(g int64) *securityrepo.Settings {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
s, ok := f.settings[g]
|
||||
if !ok {
|
||||
s = securityrepo.Defaults(g)
|
||||
s.Configured = true
|
||||
f.settings[g] = s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *fakeStore) GetSettings(_ context.Context, g int64) (*securityrepo.Settings, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if s, ok := f.settings[g]; ok {
|
||||
cp := *s
|
||||
return &cp, nil
|
||||
}
|
||||
return securityrepo.Defaults(g), nil
|
||||
}
|
||||
func (f *fakeStore) SetEnabled(_ context.Context, g int64, feature string, on bool) error {
|
||||
s := f.set(g)
|
||||
switch feature {
|
||||
case securityrepo.FeatureJoinGate:
|
||||
s.JoinGateEnabled = on
|
||||
case securityrepo.FeatureAntiRaid:
|
||||
s.AntiRaidEnabled = on
|
||||
case securityrepo.FeatureAntiNuke:
|
||||
s.AntiNukeEnabled = on
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) SetAlerts(_ context.Context, g, ch, role int64) error {
|
||||
s := f.set(g)
|
||||
s.AlertChannelID, s.AlertRoleID = ch, role
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) SetQuarantineRole(_ context.Context, g, role int64) error {
|
||||
f.set(g).QuarantineRoleID = role
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) SetJoinLock(_ context.Context, g int64, mode string) error {
|
||||
f.set(g).JoinLock = mode
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) ListTrusted(_ context.Context, g int64) ([]securityrepo.Trusted, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return append([]securityrepo.Trusted(nil), f.trusted[g]...), nil
|
||||
}
|
||||
func (f *fakeStore) AddTrusted(_ context.Context, g int64, t securityrepo.Trusted) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.trusted[g] = append(f.trusted[g], t)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) RemoveTrusted(_ context.Context, g, target int64) (bool, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var keep []securityrepo.Trusted
|
||||
found := false
|
||||
for _, t := range f.trusted[g] {
|
||||
if t.TargetID == target {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
keep = append(keep, t)
|
||||
}
|
||||
f.trusted[g] = keep
|
||||
return found, nil
|
||||
}
|
||||
func (f *fakeStore) SaveQuarantine(_ context.Context, q securityrepo.Quarantine) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if _, ok := f.quarantines[key(q.GuildID, q.UserID)]; !ok {
|
||||
f.quarantines[key(q.GuildID, q.UserID)] = q
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) GetQuarantine(_ context.Context, g, u int64) (*securityrepo.Quarantine, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if q, ok := f.quarantines[key(g, u)]; ok {
|
||||
return &q, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeStore) ListQuarantines(_ context.Context, g int64) ([]securityrepo.Quarantine, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []securityrepo.Quarantine
|
||||
for _, q := range f.quarantines {
|
||||
if q.GuildID == g {
|
||||
out = append(out, q)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (f *fakeStore) DeleteQuarantine(_ context.Context, g, u int64) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
delete(f.quarantines, key(g, u))
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) SaveLockdown(_ context.Context, l securityrepo.Lockdown) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if _, ok := f.lockdowns[key(l.GuildID, l.ChannelID)]; !ok {
|
||||
f.lockdowns[key(l.GuildID, l.ChannelID)] = l
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) GetLockdown(_ context.Context, g, ch int64) (*securityrepo.Lockdown, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if l, ok := f.lockdowns[key(g, ch)]; ok {
|
||||
return &l, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeStore) ListLockdowns(_ context.Context, g int64, serverOnly bool) ([]securityrepo.Lockdown, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []securityrepo.Lockdown
|
||||
for _, l := range f.lockdowns {
|
||||
if l.GuildID == g && (!serverOnly || l.ServerWide) {
|
||||
out = append(out, l)
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].ChannelID < out[j].ChannelID })
|
||||
return out, nil
|
||||
}
|
||||
func (f *fakeStore) DeleteLockdown(_ context.Context, g, ch int64) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
delete(f.lockdowns, key(g, ch))
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) AddEvent(_ context.Context, e securityrepo.Event) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.events = append(f.events, e)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeStore) eventKinds() []string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []string
|
||||
for _, e := range f.events {
|
||||
out = append(out, e.Kind)
|
||||
}
|
||||
return out
|
||||
}
|
||||
func (f *fakeStore) ListUnprocessedCommands(context.Context) ([]securityrepo.Command, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []securityrepo.Command
|
||||
for _, c := range f.cmds {
|
||||
if !f.done[c.ID] {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (f *fakeStore) MarkCommandProcessed(_ context.Context, id int64) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.done[id] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// fakeDiscord is a tiny in-memory guild.
|
||||
type fakeDiscord struct {
|
||||
mu sync.Mutex
|
||||
ownerID string
|
||||
roles []*discordgo.Role
|
||||
members map[string]*discordgo.Member
|
||||
users map[string]*discordgo.User
|
||||
channels map[string]*discordgo.Channel
|
||||
kicked []string
|
||||
banned []string
|
||||
timeouts map[string]*time.Time
|
||||
sent []*discordgo.MessageSend
|
||||
sentTo []string
|
||||
files map[string]string
|
||||
requests []string // "METHOD url body"
|
||||
deletedMsgs []string
|
||||
nextID int
|
||||
failEdits error
|
||||
rejectRole string // edits that assign this role fail, like a role above Velox's
|
||||
lookups int // member and role lookups, to check harmless entries cost nothing
|
||||
closedDMs map[string]bool
|
||||
guildName string
|
||||
}
|
||||
|
||||
const guild = "100"
|
||||
|
||||
func newDiscord() *fakeDiscord {
|
||||
return &fakeDiscord{
|
||||
ownerID: "1",
|
||||
roles: []*discordgo.Role{
|
||||
{ID: guild, Name: "@everyone", Position: 0},
|
||||
{ID: "200", Name: "Admin", Position: 5, Permissions: discordgo.PermissionAdministrator},
|
||||
{ID: "201", Name: "Mod", Position: 3, Permissions: discordgo.PermissionKickMembers},
|
||||
{ID: "202", Name: "Member", Position: 1},
|
||||
{ID: "203", Name: "Quarantine", Position: 2},
|
||||
{ID: "204", Name: "Booster", Position: 1, Managed: true},
|
||||
{ID: "299", Name: "Velox", Position: 9, Managed: true, Permissions: discordgo.PermissionAdministrator},
|
||||
},
|
||||
members: map[string]*discordgo.Member{
|
||||
"1": {User: &discordgo.User{ID: "1", Username: "owner"}},
|
||||
"999": {User: &discordgo.User{ID: "999", Username: "velox", Bot: true}, Roles: []string{"299"}},
|
||||
},
|
||||
users: map[string]*discordgo.User{},
|
||||
channels: map[string]*discordgo.Channel{},
|
||||
timeouts: map[string]*time.Time{},
|
||||
files: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeDiscord) addMember(id string, roles ...string) *discordgo.Member {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
m := &discordgo.Member{User: &discordgo.User{ID: id, Username: "user" + id, Avatar: "a"}, Roles: roles}
|
||||
f.members[id] = m
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *fakeDiscord) Guild(g string, _ ...discordgo.RequestOption) (*discordgo.Guild, error) {
|
||||
return &discordgo.Guild{ID: g, OwnerID: f.ownerID, Name: f.guildName}, nil
|
||||
}
|
||||
func (f *fakeDiscord) UserChannelCreate(u string, _ ...discordgo.RequestOption) (*discordgo.Channel, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.closedDMs[u] {
|
||||
return nil, errors.New("Cannot send messages to this user")
|
||||
}
|
||||
return &discordgo.Channel{ID: "dm-" + u, Type: discordgo.ChannelTypeDM}, nil
|
||||
}
|
||||
|
||||
// dmsTo lists who got a DM, in order.
|
||||
func (f *fakeDiscord) dmsTo() []string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []string
|
||||
for _, ch := range f.sentTo {
|
||||
if len(ch) > 3 && ch[:3] == "dm-" {
|
||||
out = append(out, ch[3:])
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
func (f *fakeDiscord) User(id string, _ ...discordgo.RequestOption) (*discordgo.User, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if u, ok := f.users[id]; ok {
|
||||
return u, nil
|
||||
}
|
||||
return nil, errors.New("unknown user")
|
||||
}
|
||||
func (f *fakeDiscord) GuildRoles(string, ...discordgo.RequestOption) ([]*discordgo.Role, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.lookups++
|
||||
return f.roles, nil
|
||||
}
|
||||
func (f *fakeDiscord) GuildChannels(g string, _ ...discordgo.RequestOption) ([]*discordgo.Channel, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []*discordgo.Channel
|
||||
for _, c := range f.channels {
|
||||
if c.GuildID == g {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
|
||||
return out, nil
|
||||
}
|
||||
func (f *fakeDiscord) GuildMember(_, u string, _ ...discordgo.RequestOption) (*discordgo.Member, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.lookups++
|
||||
if m, ok := f.members[u]; ok {
|
||||
cp := *m
|
||||
cp.Roles = append([]string(nil), m.Roles...)
|
||||
return &cp, nil
|
||||
}
|
||||
return nil, errors.New("unknown member")
|
||||
}
|
||||
func (f *fakeDiscord) GuildMemberEdit(_, u string, data *discordgo.GuildMemberParams, _ ...discordgo.RequestOption) (*discordgo.Member, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.failEdits != nil {
|
||||
return nil, f.failEdits
|
||||
}
|
||||
if data.Roles != nil && f.rejectRole != "" {
|
||||
for _, r := range *data.Roles {
|
||||
if r == f.rejectRole {
|
||||
return nil, errors.New("403 Missing Permissions")
|
||||
}
|
||||
}
|
||||
}
|
||||
m, ok := f.members[u]
|
||||
if !ok {
|
||||
return nil, errors.New("unknown member")
|
||||
}
|
||||
if data.Roles != nil {
|
||||
m.Roles = append([]string(nil), (*data.Roles)...)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
func (f *fakeDiscord) GuildMemberTimeout(_, u string, until *time.Time, _ ...discordgo.RequestOption) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.timeouts[u] = until
|
||||
return nil
|
||||
}
|
||||
func (f *fakeDiscord) GuildMemberDeleteWithReason(_, u, _ string, _ ...discordgo.RequestOption) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.kicked = append(f.kicked, u)
|
||||
delete(f.members, u)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeDiscord) GuildBanCreateWithReason(_, u, _ string, _ int, _ ...discordgo.RequestOption) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.banned = append(f.banned, u)
|
||||
delete(f.members, u)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeDiscord) Channel(id string, _ ...discordgo.RequestOption) (*discordgo.Channel, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if c, ok := f.channels[id]; ok {
|
||||
cp := *c
|
||||
cp.PermissionOverwrites = nil
|
||||
for _, o := range c.PermissionOverwrites {
|
||||
oc := *o
|
||||
cp.PermissionOverwrites = append(cp.PermissionOverwrites, &oc)
|
||||
}
|
||||
return &cp, nil
|
||||
}
|
||||
return nil, errors.New("unknown channel")
|
||||
}
|
||||
func (f *fakeDiscord) ChannelPermissionSet(ch, target string, t discordgo.PermissionOverwriteType, allow, deny int64, _ ...discordgo.RequestOption) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
c := f.channels[ch]
|
||||
for _, o := range c.PermissionOverwrites {
|
||||
if o.ID == target {
|
||||
o.Allow, o.Deny = allow, deny
|
||||
return nil
|
||||
}
|
||||
}
|
||||
c.PermissionOverwrites = append(c.PermissionOverwrites, &discordgo.PermissionOverwrite{ID: target, Type: t, Allow: allow, Deny: deny})
|
||||
return nil
|
||||
}
|
||||
func (f *fakeDiscord) ChannelPermissionDelete(ch, target string, _ ...discordgo.RequestOption) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
c := f.channels[ch]
|
||||
var keep []*discordgo.PermissionOverwrite
|
||||
for _, o := range c.PermissionOverwrites {
|
||||
if o.ID != target {
|
||||
keep = append(keep, o)
|
||||
}
|
||||
}
|
||||
c.PermissionOverwrites = keep
|
||||
return nil
|
||||
}
|
||||
func (f *fakeDiscord) ChannelMessageSendComplex(ch string, data *discordgo.MessageSend, _ ...discordgo.RequestOption) (*discordgo.Message, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.sentTo = append(f.sentTo, ch)
|
||||
for _, file := range data.Files {
|
||||
raw, _ := io.ReadAll(file.Reader)
|
||||
f.files[file.Name] = string(raw)
|
||||
}
|
||||
f.sent = append(f.sent, data)
|
||||
return &discordgo.Message{ID: "m"}, nil
|
||||
}
|
||||
func (f *fakeDiscord) RequestWithBucketID(method, url string, data interface{}, _ string, _ ...discordgo.RequestOption) ([]byte, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.requests = append(f.requests, fmt.Sprintf("%s %s %v", method, url, data))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// sentCount counts channel alerts (not DMs).
|
||||
func (f *fakeDiscord) sentCount() int {
|
||||
return len(f.channelSends())
|
||||
}
|
||||
|
||||
// channelSends lists messages sent to channels, leaving out DMs.
|
||||
func (f *fakeDiscord) channelSends() []*discordgo.MessageSend {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
var out []*discordgo.MessageSend
|
||||
for i, m := range f.sent {
|
||||
if i >= len(f.sentTo) || len(f.sentTo[i]) < 3 || f.sentTo[i][:3] != "dm-" {
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (f *fakeDiscord) newID() string {
|
||||
f.nextID++
|
||||
return fmt.Sprint(8000 + f.nextID)
|
||||
}
|
||||
func (f *fakeDiscord) ChannelMessageDelete(ch, msg string, _ ...discordgo.RequestOption) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.deletedMsgs = append(f.deletedMsgs, msg)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeDiscord) GuildRoleCreate(_ string, p *discordgo.RoleParams, _ ...discordgo.RequestOption) (*discordgo.Role, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
r := &discordgo.Role{ID: f.newID(), Name: p.Name}
|
||||
if p.Permissions != nil {
|
||||
r.Permissions = *p.Permissions
|
||||
}
|
||||
f.roles = append(f.roles, r)
|
||||
return r, nil
|
||||
}
|
||||
func (f *fakeDiscord) GuildRoleEdit(_, roleID string, p *discordgo.RoleParams, _ ...discordgo.RequestOption) (*discordgo.Role, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
for _, r := range f.roles {
|
||||
if r.ID == roleID {
|
||||
if p.Permissions != nil {
|
||||
r.Permissions = *p.Permissions
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("unknown role")
|
||||
}
|
||||
func (f *fakeDiscord) GuildChannelCreateComplex(g string, d discordgo.GuildChannelCreateData, _ ...discordgo.RequestOption) (*discordgo.Channel, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
c := &discordgo.Channel{ID: f.newID(), GuildID: g, Name: d.Name, Type: d.Type, ParentID: d.ParentID, PermissionOverwrites: d.PermissionOverwrites}
|
||||
f.channels[c.ID] = c
|
||||
return c, nil
|
||||
}
|
||||
Reference in new issue
Block a user