läuft
This commit is contained in:
@@ -29,7 +29,8 @@ ENV LOBBY_CHANNEL_ID=0 \
|
||||
DISCORD_TOKEN=0 \
|
||||
GUILD_ID=0 \
|
||||
CATEGORY_ID=0 \
|
||||
TIMEOUT_MIN=1
|
||||
TIMEOUT_MIN=1 \
|
||||
CONFIG_PATH=/data/guild_config.json
|
||||
|
||||
|
||||
|
||||
|
15
go.mod
15
go.mod
@@ -4,8 +4,21 @@ go 1.24.4
|
||||
|
||||
require github.com/bwmarrin/discordgo v0.29.0
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
modernc.org/libc v1.66.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
modernc.org/sqlite v1.38.2
|
||||
)
|
||||
|
50
go.sum
50
go.sum
@@ -1,12 +1,60 @@
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
|
||||
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
BIN
guild_config.db
Normal file
BIN
guild_config.db
Normal file
Binary file not shown.
1
guild_config.json
Normal file
1
guild_config.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
20
language.json
Normal file
20
language.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"translations": [
|
||||
{
|
||||
"language": "de",
|
||||
"messages": {
|
||||
"system.default.lobbyname": "➕ Erstelle privaten Raum",
|
||||
"system.default.category": "Private Räume",
|
||||
"system.log.initdb.01": "Datenbank-Fehler: %v",
|
||||
"system.log.initdb.02": "Fehler beim Erstellen der Tabelle: %v"
|
||||
}
|
||||
},
|
||||
{
|
||||
"language": "en",
|
||||
"messages": {
|
||||
"hello": "Hallo, %s!",
|
||||
"goodbye": "Tschüss, %s!"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
420
main.go
420
main.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -11,63 +12,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
_ "modernc.org/sqlite" // SQLite3-Treiber importieren
|
||||
)
|
||||
|
||||
// ===== Defaults / Names (pro Guild per Commands konfigurierbar) =====
|
||||
const (
|
||||
var (
|
||||
pollInterval = 15 * time.Second
|
||||
defaultLobbyName = "➕ Erstelle privaten Raum"
|
||||
defaultCategory = "Private Räume"
|
||||
)
|
||||
|
||||
// Pfad zur Persistenz-Datei (überschreibbar via CONFIG_PATH)
|
||||
var configPath = func() string {
|
||||
if v := os.Getenv("CONFIG_PATH"); v != "" {
|
||||
// Pfad zur SQLite-Datenbank
|
||||
var dbPath = func() string {
|
||||
if v := os.Getenv("DB_PATH"); v != "" {
|
||||
return v
|
||||
}
|
||||
return "guild_config.json"
|
||||
return "guild_config.db"
|
||||
}()
|
||||
|
||||
// Persistenz der Guild-Konfiguration
|
||||
func loadGuildCfgs() error {
|
||||
f, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var m map[string]*GuildConfig
|
||||
if err := json.NewDecoder(f).Decode(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfgMu.Lock()
|
||||
for k, v := range m {
|
||||
guildCfgs[k] = v
|
||||
}
|
||||
cfgMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveGuildCfgs() error {
|
||||
tmp := configPath + ".tmp"
|
||||
f, err := os.Create(tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
cfgMu.RLock()
|
||||
err = enc.Encode(guildCfgs)
|
||||
cfgMu.RUnlock()
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, configPath)
|
||||
}
|
||||
|
||||
// ===== Per-Guild Config (in-memory) =====
|
||||
type GuildConfig struct {
|
||||
LobbyName string `json:"lobby_name"`
|
||||
@@ -75,29 +37,116 @@ type GuildConfig struct {
|
||||
TimeoutMin int `json:"timeout_min"`
|
||||
}
|
||||
|
||||
var (
|
||||
cfgMu sync.RWMutex
|
||||
guildCfgs = map[string]*GuildConfig{}
|
||||
createdCmds = map[string][]*discordgo.ApplicationCommand{} // guildID -> cmds
|
||||
registerOnce sync.Once
|
||||
)
|
||||
var db *sql.DB
|
||||
|
||||
// ===== Global variables for sync.Map =====
|
||||
var guildCfgs sync.Map // Für Guild-Konfigurationen
|
||||
var createdCmds sync.Map // Für erstellte Commands
|
||||
|
||||
// Initialize DB
|
||||
func initDB() {
|
||||
var err error
|
||||
db, err = sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Datenbank-Fehler: %v", err)
|
||||
}
|
||||
|
||||
// Tabelle erstellen, falls sie noch nicht existiert
|
||||
createTableSQL := `CREATE TABLE IF NOT EXISTS guild_config (
|
||||
guild_id TEXT PRIMARY KEY,
|
||||
lobby_name TEXT,
|
||||
category_name TEXT,
|
||||
timeout_min INTEGER
|
||||
);`
|
||||
_, err = db.Exec(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatalf("Fehler beim Erstellen der Tabelle: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Close DB connection
|
||||
func closeDB() {
|
||||
if err := db.Close(); err != nil {
|
||||
log.Fatalf("Fehler beim Schließen der DB: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Laden der Guild-Konfiguration aus der Datenbank
|
||||
func loadGuildCfgs() error {
|
||||
rows, err := db.Query("SELECT guild_id, lobby_name, category_name, timeout_min FROM guild_config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var guildID, lobbyName, categoryName string
|
||||
var timeoutMin int
|
||||
if err := rows.Scan(&guildID, &lobbyName, &categoryName, &timeoutMin); err != nil {
|
||||
return err
|
||||
}
|
||||
guildCfgs.Store(guildID, &GuildConfig{
|
||||
LobbyName: lobbyName,
|
||||
CategoryName: categoryName,
|
||||
TimeoutMin: timeoutMin,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Speichern der Guild-Konfiguration in die Datenbank
|
||||
func saveGuildCfgs() error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
guildCfgs.Range(func(key, value interface{}) bool {
|
||||
guildID := key.(string)
|
||||
cfg := value.(*GuildConfig)
|
||||
|
||||
_, err := tx.Exec(`
|
||||
INSERT INTO guild_config (guild_id, lobby_name, category_name, timeout_min)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(guild_id)
|
||||
DO UPDATE SET
|
||||
lobby_name = excluded.lobby_name,
|
||||
category_name = excluded.category_name,
|
||||
timeout_min = excluded.timeout_min
|
||||
`, guildID, cfg.LobbyName, cfg.CategoryName, cfg.TimeoutMin)
|
||||
if err != nil {
|
||||
log.Printf("Fehler beim Speichern der Guild-Konfiguration: %v", err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Transaktion abschließen
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Guild-Konfiguration laden oder Standardwerte zurückgeben
|
||||
func getCfg(guildID string) *GuildConfig {
|
||||
cfgMu.RLock()
|
||||
c, ok := guildCfgs[guildID]
|
||||
cfgMu.RUnlock()
|
||||
if ok {
|
||||
return c
|
||||
if cfg, ok := guildCfgs.Load(guildID); ok {
|
||||
return cfg.(*GuildConfig)
|
||||
}
|
||||
cfgMu.Lock()
|
||||
defer cfgMu.Unlock()
|
||||
c = &GuildConfig{
|
||||
LobbyName: defaultLobbyName,
|
||||
CategoryName: defaultCategory,
|
||||
TimeoutMin: envTimeoutDefault(60),
|
||||
var guildCfg GuildConfig
|
||||
err := db.QueryRow("SELECT lobby_name, category_name, timeout_min FROM guild_config WHERE guild_id = ?", guildID).Scan(&guildCfg.LobbyName, &guildCfg.CategoryName, &guildCfg.TimeoutMin)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
log.Printf("Guild-Konfiguration für %s nicht gefunden, verwenden der Standardwerte", guildID)
|
||||
} else {
|
||||
log.Printf("Fehler beim Abrufen der Guild-Konfiguration für %s: %v", guildID, err)
|
||||
}
|
||||
guildCfg = GuildConfig{
|
||||
LobbyName: defaultLobbyName,
|
||||
CategoryName: defaultCategory,
|
||||
TimeoutMin: envTimeoutDefault(1),
|
||||
}
|
||||
}
|
||||
guildCfgs[guildID] = c
|
||||
return c
|
||||
guildCfgs.Store(guildID, &guildCfg)
|
||||
return &guildCfg
|
||||
}
|
||||
|
||||
func envTimeoutDefault(def int) int {
|
||||
@@ -262,7 +311,7 @@ func createPrivateVCAndMove(
|
||||
break
|
||||
}
|
||||
wait := time.Duration(150*(attempt+1)) * time.Millisecond
|
||||
log.Printf("Move fehlgeschlagen (Versuch %d): %v – retry in %s", attempt+1, moveErr, wait)
|
||||
log.Printf("Move fehlgeschlagen (Versuch %d): %v - retry in %s", attempt+1, moveErr, wait)
|
||||
time.Sleep(wait)
|
||||
}
|
||||
if moveErr != nil {
|
||||
@@ -427,57 +476,129 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: msg, Flags: discordgo.MessageFlagsEphemeral}})
|
||||
|
||||
case "setlobby":
|
||||
// Admin-Check zuerst
|
||||
if !isGuildAdmin(s, guildID, i.User, i.Member) {
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "❌ Nur Administratoren dürfen das.", Flags: discordgo.MessageFlagsEphemeral}})
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "❌ Nur Administratoren dürfen das.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Option "name" lesen
|
||||
var name string
|
||||
for _, o := range data.Options {
|
||||
if o.Name == "name" {
|
||||
name = o.StringValue()
|
||||
break
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "Bitte gib einen Namen an.", Flags: discordgo.MessageFlagsEphemeral}})
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Bitte gib einen Namen an.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
cfgMu.Lock()
|
||||
|
||||
// speichern
|
||||
getCfg(guildID).LobbyName = name
|
||||
cfgMu.Unlock()
|
||||
if err := saveGuildCfgs(); err != nil {
|
||||
log.Printf("Speichern fehlgeschlagen: %v", err)
|
||||
log.Printf("saveGuildCfgs failed: %v", err)
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "⚠️ Lobby-Name gesetzt, aber Speichern fehlgeschlagen. Schau die Logs.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "✅ Lobby-Name aktualisiert auf: " + name, Flags: discordgo.MessageFlagsEphemeral}})
|
||||
|
||||
// Antwort <= 3s
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "✅ Lobby-Name aktualisiert auf: " + name,
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
|
||||
case "setcategory":
|
||||
// Admin-Check zuerst
|
||||
if !isGuildAdmin(s, guildID, i.User, i.Member) {
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "❌ Nur Administratoren dürfen das.", Flags: discordgo.MessageFlagsEphemeral}})
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "❌ Nur Administratoren dürfen das.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Option "name" lesen
|
||||
var name string
|
||||
for _, o := range data.Options {
|
||||
if o.Name == "name" {
|
||||
name = o.StringValue()
|
||||
break
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "Bitte gib einen Namen an.", Flags: discordgo.MessageFlagsEphemeral}})
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Bitte gib einen Namen an.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
cfgMu.Lock()
|
||||
|
||||
// speichern
|
||||
getCfg(guildID).CategoryName = name
|
||||
cfgMu.Unlock()
|
||||
if err := saveGuildCfgs(); err != nil {
|
||||
log.Printf("Speichern fehlgeschlagen: %v", err)
|
||||
log.Printf("saveGuildCfgs failed: %v", err)
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "⚠️ Kategorie-Name gesetzt, aber Speichern fehlgeschlagen. Schau die Logs.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "✅ Kategorie-Name aktualisiert auf: " + name, Flags: discordgo.MessageFlagsEphemeral}})
|
||||
|
||||
// Antwort <= 3s
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "✅ Kategorie-Name aktualisiert auf: " + name,
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
|
||||
case "settimeout":
|
||||
if !isGuildAdmin(s, guildID, i.User, i.Member) {
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "❌ Nur Administratoren dürfen das.", Flags: discordgo.MessageFlagsEphemeral}})
|
||||
log.Println("User is not an admin")
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "❌ Nur Administratoren dürfen das.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
var minutes int64 = int64(getCfg(guildID).TimeoutMin)
|
||||
|
||||
minutes := int64(getCfg(guildID).TimeoutMin)
|
||||
for _, o := range data.Options {
|
||||
if o.Name == "minutes" {
|
||||
minutes = o.IntValue()
|
||||
@@ -486,13 +607,32 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter
|
||||
if minutes < 1 {
|
||||
minutes = 1
|
||||
}
|
||||
cfgMu.Lock()
|
||||
|
||||
log.Printf("Setting timeout for %d minutes", minutes)
|
||||
|
||||
// speichern
|
||||
getCfg(guildID).TimeoutMin = int(minutes)
|
||||
cfgMu.Unlock()
|
||||
if err := saveGuildCfgs(); err != nil {
|
||||
log.Printf("Speichern fehlgeschlagen: %v", err)
|
||||
log.Printf("saveGuildCfgs failed: %v", err)
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "⚠️ Timeout gesetzt, aber Speichern fehlgeschlagen. Schau die Logs.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: fmt.Sprintf("✅ Timeout auf %d Minuten gesetzt.", minutes), Flags: discordgo.MessageFlagsEphemeral}})
|
||||
|
||||
// Antwort <= 3s
|
||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf("✅ Timeout auf %d Minuten gesetzt.", minutes),
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
|
||||
case "adduser":
|
||||
// wer ruft auf?
|
||||
requester := i.User
|
||||
@@ -589,6 +729,27 @@ func onInteractionCreate(_ string) func(s *discordgo.Session, i *discordgo.Inter
|
||||
}
|
||||
}
|
||||
|
||||
// Handler: Commands registrieren, wenn Bot auf neuen Server kommt
|
||||
func onGuildCreate(s *discordgo.Session, g *discordgo.GuildCreate) {
|
||||
appID := s.State.User.ID
|
||||
log.Printf("Registriere Commands in neuer Guild: %s (%s)", g.Name, g.ID)
|
||||
for _, cmd := range slashCommands {
|
||||
c, err := s.ApplicationCommandCreate(appID, g.ID, cmd)
|
||||
if err != nil {
|
||||
log.Printf("Command-Registrierung in %s fehlgeschlagen: %v", g.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Commands für die Guild speichern
|
||||
// Wir verwenden sync.Map, also müssen wir die 'Load' und 'Store' Methoden verwenden.
|
||||
commands, _ := createdCmds.LoadOrStore(g.ID, []*discordgo.ApplicationCommand{})
|
||||
commands = append(commands.([]*discordgo.ApplicationCommand), c)
|
||||
createdCmds.Store(g.ID, commands)
|
||||
|
||||
log.Printf("Registriere Command %s in Guild %s", cmd.Name, g.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Commands Definition =====
|
||||
var (
|
||||
adminPerm = int64(discordgo.PermissionAdministrator)
|
||||
@@ -643,14 +804,16 @@ var (
|
||||
|
||||
// ===== main: Multi-Guild, pro Guild registrieren =====
|
||||
func main() {
|
||||
initDB()
|
||||
token := os.Getenv("DISCORD_TOKEN")
|
||||
token = "MTQwMzg1MTM5NDQ1MjI5MTU4NA.GVi04l.qjraLIbFdi_N49UcSUv_BqK89ihb6xXY648J7A"
|
||||
if token == "" {
|
||||
log.Fatal("Bitte setze DISCORD_TOKEN")
|
||||
}
|
||||
|
||||
// Persistente Konfiguration laden (optional)
|
||||
if err := loadGuildCfgs(); err != nil {
|
||||
log.Printf("Hinweis: Konnte %s nicht laden (%v). Starte mit Defaults.", configPath, err)
|
||||
log.Printf("Hinweis: Konnte %s nicht laden (%v). Starte mit Defaults.", dbPath, err)
|
||||
}
|
||||
|
||||
s, err := discordgo.New("Bot " + token)
|
||||
@@ -661,25 +824,8 @@ func main() {
|
||||
s.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildVoiceStates
|
||||
|
||||
s.AddHandler(onVoiceStateUpdate)
|
||||
s.AddHandler(onGuildCreate)
|
||||
s.AddHandler(onInteractionCreate(""))
|
||||
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
|
||||
log.Printf("Eingeloggt als %s", r.User.Username)
|
||||
registerOnce.Do(func() {
|
||||
appID := s.State.User.ID
|
||||
for _, g := range s.State.Guilds {
|
||||
log.Printf("Registriere Commands in Guild: %s (%s)", g.Name, g.ID)
|
||||
for _, cmd := range slashCommands {
|
||||
c, err := s.ApplicationCommandCreate(appID, g.ID, cmd)
|
||||
if err != nil {
|
||||
log.Printf("Command-Registrierung in %s fehlgeschlagen: %v", g.Name, err)
|
||||
continue
|
||||
}
|
||||
createdCmds[g.ID] = append(createdCmds[g.ID], c)
|
||||
}
|
||||
}
|
||||
log.Printf("Defaults → Lobby: %q | Kategorie: %q | Timeout: %d min", defaultLobbyName, defaultCategory, envTimeoutDefault(60))
|
||||
})
|
||||
})
|
||||
|
||||
if err := s.Open(); err != nil {
|
||||
log.Fatalf("Gateway-Start fehlgeschlagen: %v", err)
|
||||
@@ -692,15 +838,75 @@ func main() {
|
||||
log.Println("Fahre herunter…")
|
||||
|
||||
appID := s.State.User.ID
|
||||
for guildID, cmds := range createdCmds {
|
||||
for _, c := range cmds {
|
||||
if delErr := s.ApplicationCommandDelete(appID, guildID, c.ID); delErr != nil {
|
||||
log.Printf("Cmd-Delete (%s/%s) fehlgeschlagen: %v", guildID, c.Name, delErr)
|
||||
createdCmds.Range(func(key, value interface{}) bool {
|
||||
for _, c := range value.([]*discordgo.ApplicationCommand) {
|
||||
if delErr := s.ApplicationCommandDelete(appID, key.(string), c.ID); delErr != nil {
|
||||
log.Printf("Cmd-Delete (%s / %s) fehlgeschlagen: %v", key, c.Name, delErr)
|
||||
} else {
|
||||
log.Printf("Cmd-Delete (%s / %s) ok", key, c.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err := saveGuildCfgs(); err != nil {
|
||||
log.Printf("Speichern zum Shutdown fehlgeschlagen: %v", err)
|
||||
}
|
||||
_ = s.Close()
|
||||
}
|
||||
|
||||
// Die Struktur für die Übersetzungen.
|
||||
type TranslationsStruct struct {
|
||||
Language string `json:"language"`
|
||||
Messages map[string]string `json:"messages"`
|
||||
}
|
||||
|
||||
// Temporäre Struktur zur Deserialisierung.
|
||||
type TranslationFile struct {
|
||||
Translations []TranslationsStruct `json:"translations"`
|
||||
}
|
||||
|
||||
// Globale Variable für die Übersetzungen
|
||||
var translations map[string]map[string]string
|
||||
|
||||
// Funktion zur Deserialisierung und Umwandlung in map[string]map[string]string
|
||||
func loadTranslations(jsonData string) (map[string]map[string]string, error) {
|
||||
var translationFile TranslationFile
|
||||
|
||||
// Deserialisierung des JSON in die Struktur
|
||||
if err := json.Unmarshal([]byte(jsonData), &translationFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Umwandlung der Struktur in eine Map
|
||||
result := make(map[string]map[string]string)
|
||||
for _, trans := range translationFile.Translations {
|
||||
result[trans.Language] = trans.Messages
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Funktion zur Deserialisierung und Umwandlung in map[string]map[string]string
|
||||
func loadTranslationsFromFile(filename string) (map[string]map[string]string, error) {
|
||||
// Dateiinhalt lesen
|
||||
fileData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fehler beim lesen der datei: %w", err)
|
||||
}
|
||||
|
||||
var translationFile TranslationFile
|
||||
|
||||
// Deserialisierung des JSON in die Struktur
|
||||
if err := json.Unmarshal(fileData, &translationFile); err != nil {
|
||||
return nil, fmt.Errorf("fehler beim deserialisieren der json-daten: %w", err)
|
||||
}
|
||||
|
||||
// Umwandlung der Struktur in eine Map
|
||||
result := make(map[string]map[string]string)
|
||||
for _, trans := range translationFile.Translations {
|
||||
result[trans.Language] = trans.Messages
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user