987 lines
25 KiB
Go
987 lines
25 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
)
|
|
|
|
type ConfigStruct struct {
|
|
BotToken string `json:"botToken"`
|
|
BotAppId string `json:"botAppId"`
|
|
BotAllowedHosts map[string]bool `json:"botAllowedHosts"`
|
|
BotTransferShUrl string `json:"botTransferShUrl"`
|
|
BotWebhookUrls map[string]string `json:"botWebhookUrls"`
|
|
ServerPort int `json:"serverPort"`
|
|
ApiRootUrl string `json:"apiRootUrl"`
|
|
ApiAdminPrefix string `json:"apiAdminPrefix"`
|
|
TextFile string `json:"textFile"`
|
|
AudioFile string `json:"audioFile"`
|
|
WebUiFile string `json:"webUiFile"`
|
|
ApiLogFile string `json:"apiLogFile"`
|
|
DiscordLogFile string `json:"discordLogFile"`
|
|
BannedIpsFile string `json:"bannedIpsFile"`
|
|
BannedDiscordUsersFile string `json:"bannedDiscordUsersFile"`
|
|
Voices [][]string `json:"voices"`
|
|
TtsCommands map[string][][]string `json:"ttsCommands"`
|
|
GetDurationCommand []string `json:"getDurationCommand"`
|
|
KillCommand []string `json:"killCommand"`
|
|
Timeout int `json:"timeout"`
|
|
TimeoutTickerInterval int `json:"timeoutTickerInterval"`
|
|
AllowedCharacters string `json:"allowedCharacters"`
|
|
TextLengthLimit int `json:"textLengthLimit"`
|
|
RateLimit []int `json:"rateLimit"`
|
|
}
|
|
|
|
type ApiLogStruct struct {
|
|
DateTimeString string `json:"dateTimeString"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
UserAgent string `json:"userAgent"`
|
|
Ip string `json:"ip"`
|
|
TextLength int `json:"textLength"`
|
|
Voice string `json:"voice"`
|
|
}
|
|
|
|
type DiscordLogStruct struct {
|
|
DateTimeString string `json:"dateTimeString"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
GuildId string `json:"guildId"`
|
|
ChannelId string `json:"channelId"`
|
|
UserId string `json:"userId"`
|
|
Command string `json:"command"`
|
|
TextHost string `json:"textHost"`
|
|
TextLength int `json:"textLength"`
|
|
Voice string `json:"voice"`
|
|
}
|
|
|
|
type ServerInfoStruct struct {
|
|
Busy bool `json:"busy"`
|
|
Requests []int `json:"requests"`
|
|
}
|
|
|
|
var (
|
|
configFile = flag.String("configfile", "config.json", "Config file")
|
|
enableBot = flag.Bool("enablebot", false, "Enable bot")
|
|
allInterfaces = flag.Bool("allinterfaces", false, "Listen on all interfaces instead of localhost only")
|
|
allowedCharacters = map[rune]bool{}
|
|
bannedIps = map[string]bool{}
|
|
bannedDiscordUsers = map[string]bool{}
|
|
c = make(chan int)
|
|
)
|
|
|
|
var config ConfigStruct
|
|
var dg *discordgo.Session
|
|
var botName string
|
|
var botAvatar string
|
|
var apiLogFile *os.File
|
|
var discordLogFile *os.File
|
|
|
|
func GetIp(r *http.Request) string {
|
|
address := r.Header.Get("X-Real-Ip")
|
|
if address == "" {
|
|
address = r.Header.Get("X-Forwarded-For")
|
|
}
|
|
if address == "" {
|
|
address = r.RemoteAddr
|
|
}
|
|
|
|
ip, _, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return address
|
|
}
|
|
|
|
return ip
|
|
}
|
|
|
|
func HttpHandler(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasPrefix(r.URL.Path, config.ApiAdminPrefix) {
|
|
if r.Method != http.MethodGet {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
if r.URL.Path == config.ApiAdminPrefix {
|
|
http.Error(w, "404", http.StatusNotFound)
|
|
} else if r.URL.Path == fmt.Sprintf("%s/text", config.ApiAdminPrefix) {
|
|
textFile, err := os.Open(config.TextFile)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
io.Copy(w, textFile)
|
|
textFile.Close()
|
|
} else if r.URL.Path == fmt.Sprintf("%s/updatebotcommands", config.ApiAdminPrefix) {
|
|
if *enableBot {
|
|
q := r.URL.Query()
|
|
|
|
dg2, err := discordgo.New("Bot " + config.BotToken)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var voiceChoices []*discordgo.ApplicationCommandOptionChoice
|
|
if q.Has("ttsd") || q.Has("ttst") {
|
|
for _, v := range config.Voices {
|
|
voiceChoices = append(voiceChoices, &discordgo.ApplicationCommandOptionChoice{
|
|
Name: v[1],
|
|
Value: v[0],
|
|
})
|
|
}
|
|
}
|
|
|
|
if q.Has("ttsd") && q.Has("ttst") {
|
|
_, err = dg2.ApplicationCommandBulkOverwrite(config.BotAppId, q.Get("s"), []*discordgo.ApplicationCommand{
|
|
{
|
|
Name: "ttsd",
|
|
Description: "No description",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "text",
|
|
Description: "Text link or JSON string",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
MaxLength: config.TextLengthLimit,
|
|
Required: true,
|
|
},
|
|
{
|
|
Name: "voice",
|
|
Description: "Voice",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
Choices: voiceChoices,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ttst",
|
|
Description: "No description",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "text",
|
|
Description: "Text link or JSON string",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
MaxLength: config.TextLengthLimit,
|
|
Required: true,
|
|
},
|
|
{
|
|
Name: "voice",
|
|
Description: "Voice",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
Choices: voiceChoices,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
} else if q.Has("ttsd") {
|
|
_, err = dg2.ApplicationCommandBulkOverwrite(config.BotAppId, q.Get("s"), []*discordgo.ApplicationCommand{
|
|
{
|
|
Name: "ttsd",
|
|
Description: "No description",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "text",
|
|
Description: "Text link or JSON string",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
MaxLength: config.TextLengthLimit,
|
|
Required: true,
|
|
},
|
|
{
|
|
Name: "voice",
|
|
Description: "Voice",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
Choices: voiceChoices,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
} else if q.Has("ttst") {
|
|
_, err = dg2.ApplicationCommandBulkOverwrite(config.BotAppId, q.Get("s"), []*discordgo.ApplicationCommand{
|
|
{
|
|
Name: "ttst",
|
|
Description: "No description",
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "text",
|
|
Description: "Text link or JSON string",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
MaxLength: config.TextLengthLimit,
|
|
Required: true,
|
|
},
|
|
{
|
|
Name: "voice",
|
|
Description: "Voice",
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
Choices: voiceChoices,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
} else {
|
|
_, err = dg2.ApplicationCommandBulkOverwrite(config.BotAppId, q.Get("s"), []*discordgo.ApplicationCommand{})
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
} else {
|
|
http.Error(w, "404", http.StatusNotFound)
|
|
}
|
|
} else if r.URL.Path == fmt.Sprintf("%s/info", config.ApiAdminPrefix) {
|
|
c <- 2
|
|
} else if r.URL.Path == fmt.Sprintf("%s/reload", config.ApiAdminPrefix) {
|
|
c <- 3
|
|
} else {
|
|
c <- 4
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
ip := GetIp(r)
|
|
|
|
if bannedIps[ip] {
|
|
http.Error(w, "403", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
if r.URL.Path == "/" {
|
|
if r.Method == http.MethodGet {
|
|
http.ServeFile(w, r, config.WebUiFile)
|
|
} else if r.Method == http.MethodPost {
|
|
decoder := json.NewDecoder(r.Body)
|
|
var strs []string
|
|
|
|
err := decoder.Decode(&strs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(strs) != 2 || len(strings.TrimSpace(strs[1])) == 0 || len(strs[1]) > config.TextLengthLimit {
|
|
http.Error(w, "400", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
for _, char := range strs[1] {
|
|
if !allowedCharacters[char] {
|
|
http.Error(w, "400", http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
if ttsCommands, found := config.TtsCommands[strs[0]]; found {
|
|
c <- 0
|
|
|
|
switch <-c {
|
|
case 429:
|
|
http.Error(w, "429", http.StatusTooManyRequests)
|
|
return
|
|
case 503:
|
|
http.Error(w, "503", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
defer func() {
|
|
os.Remove(config.TextFile)
|
|
os.Remove(config.AudioFile)
|
|
c <- 1
|
|
}()
|
|
|
|
textFile, err := os.Create(config.TextFile)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
_, err = textFile.WriteString(strs[1])
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
textFile.Close()
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
err = textFile.Close()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
for _, c := range ttsCommands {
|
|
cmd := exec.Command(c[0], c[1:]...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
getDurationOut, err := exec.Command(config.GetDurationCommand[0], config.GetDurationCommand[1:]...).Output()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
durationString := strings.TrimSpace(string(getDurationOut))
|
|
if durationString == "" {
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
durationFloat, err := strconv.ParseFloat(durationString, 64)
|
|
if err != nil {
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
d := time.Duration(durationFloat * float64(time.Second))
|
|
if d < 100*time.Millisecond {
|
|
http.Error(w, "400", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
audioFile, err := os.Open(config.AudioFile)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
http.Error(w, "500", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Duration", d.String())
|
|
switch filepath.Ext(config.AudioFile) {
|
|
case ".wav":
|
|
w.Header().Set("Content-Type", "audio/wav")
|
|
case ".flac":
|
|
w.Header().Set("Content-Type", "audio/flac")
|
|
default:
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
}
|
|
io.Copy(w, audioFile)
|
|
audioFile.Close()
|
|
|
|
if apiLogFile != nil {
|
|
t := time.Now()
|
|
|
|
jsonBytes, err := json.Marshal(ApiLogStruct{
|
|
DateTimeString: t.Format(time.RFC3339),
|
|
Timestamp: t.Unix(),
|
|
UserAgent: r.Header.Get("User-Agent"),
|
|
Ip: ip,
|
|
TextLength: len(strs[1]),
|
|
Voice: strs[0],
|
|
})
|
|
|
|
if err == nil {
|
|
fmt.Fprintln(apiLogFile, string(jsonBytes))
|
|
} else {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
} else {
|
|
http.Error(w, "400", http.StatusBadRequest)
|
|
}
|
|
} else {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
} else {
|
|
http.Error(w, "404", http.StatusNotFound)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
configBytes, err := os.ReadFile(*configFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = json.Unmarshal(configBytes, &config)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if config.ServerPort > 0 {
|
|
bannedIpsBytes, err := os.ReadFile(config.BannedIpsFile)
|
|
if err == nil {
|
|
json.Unmarshal(bannedIpsBytes, &bannedIps)
|
|
}
|
|
|
|
for _, ch := range config.AllowedCharacters {
|
|
allowedCharacters[ch] = true
|
|
}
|
|
|
|
if config.ApiLogFile != "" {
|
|
apiLogFile, err = os.OpenFile(config.ApiLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
defer apiLogFile.Close()
|
|
}
|
|
}
|
|
|
|
if *enableBot {
|
|
if config.DiscordLogFile != "" {
|
|
discordLogFile, err = os.OpenFile(config.DiscordLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
defer discordLogFile.Close()
|
|
}
|
|
|
|
bannedDiscordUsersBytes, err := os.ReadFile(config.BannedDiscordUsersFile)
|
|
if err == nil {
|
|
json.Unmarshal(bannedDiscordUsersBytes, &bannedDiscordUsersBytes)
|
|
}
|
|
|
|
dg, err := discordgo.New("Bot " + config.BotToken)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
dg.AddHandler(func(
|
|
s *discordgo.Session,
|
|
r *discordgo.Ready,
|
|
) {
|
|
botName = r.User.Username
|
|
botAvatar = r.User.Avatar
|
|
})
|
|
|
|
dg.AddHandler(func(
|
|
s *discordgo.Session,
|
|
i *discordgo.InteractionCreate,
|
|
) {
|
|
if bannedDiscordUsers[i.Interaction.Member.User.ID] {
|
|
return
|
|
}
|
|
|
|
data := i.ApplicationCommandData()
|
|
|
|
if data.Name == "ttsd" && config.BotWebhookUrls[i.Interaction.ChannelID] == "" {
|
|
return
|
|
}
|
|
|
|
values := map[string]string{}
|
|
for _, o := range data.Options {
|
|
values[o.Name] = strings.TrimSpace(o.StringValue())
|
|
}
|
|
|
|
text := ""
|
|
textHost := ""
|
|
|
|
if strings.HasPrefix(values["text"], "\"") {
|
|
err := json.Unmarshal([]byte(values["text"]), &text)
|
|
if err != nil {
|
|
dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: "Error",
|
|
},
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if data.Name == "ttsd" {
|
|
err = dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
|
|
})
|
|
} else {
|
|
err = dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Flags: discordgo.MessageFlagsEphemeral,
|
|
},
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
_, err := url.ParseRequestURI(values["text"])
|
|
if err != nil {
|
|
dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: "Error",
|
|
},
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
textUrl, err := url.Parse(values["text"])
|
|
if err != nil {
|
|
dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: "Error",
|
|
},
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if !config.BotAllowedHosts[textUrl.Host] {
|
|
dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: fmt.Sprintf("`%s` is not in allowed host list", textUrl.Host),
|
|
},
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if textUrl.Host == "pastebin.com" && !strings.HasPrefix(textUrl.Path, "/raw/") {
|
|
textUrl.Path = fmt.Sprintf("/raw%s", textUrl.Path)
|
|
}
|
|
|
|
if textUrl.Host == "bin.sohamsen.me" && textUrl.Fragment != "" {
|
|
textUrl.RawQuery = "k=" + textUrl.EscapedFragment()
|
|
textUrl.Fragment = ""
|
|
}
|
|
|
|
if data.Name == "ttsd" {
|
|
err = dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
|
|
})
|
|
} else {
|
|
err = dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Flags: discordgo.MessageFlagsEphemeral,
|
|
},
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
textResponse, err := http.Get(textUrl.String())
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
defer textResponse.Body.Close()
|
|
|
|
if textResponse.StatusCode != http.StatusOK {
|
|
content := fmt.Sprintf("`%s` returned error `%s`", textUrl.Host, textResponse.Status)
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if !strings.HasPrefix(textResponse.Header.Get("Content-Type"), "text/plain") {
|
|
content := fmt.Sprintf("`%s` did not return plain text", textUrl.Host)
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
textResponseBodyBytes, err := io.ReadAll(textResponse.Body)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
text = string(textResponseBodyBytes)
|
|
textHost = textUrl.Host
|
|
}
|
|
|
|
apiJsonBytes, err := json.Marshal([]string{values["voice"], text})
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
apiResponse, err := http.Post(config.ApiRootUrl, "application/json", bytes.NewBuffer(apiJsonBytes))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
defer apiResponse.Body.Close()
|
|
|
|
if apiResponse.StatusCode != http.StatusOK {
|
|
content := fmt.Sprintf("TTS API returned error `%s`", apiResponse.Status)
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
audioFile, err := io.ReadAll(apiResponse.Body)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
uploadRequestBody := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(uploadRequestBody)
|
|
|
|
if data.Name == "ttsd" {
|
|
jsonPart, err := writer.CreateFormField("payload_json")
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
jsonPart.Write([]byte(fmt.Sprintf("{\"username\":\"%s\",\"avatar_url\":\"https://cdn.discordapp.com/avatars/%s/%s.png\"}", botName, config.BotAppId, botAvatar)))
|
|
}
|
|
|
|
var filename string
|
|
switch apiResponse.Header.Get("Content-Type") {
|
|
case "audio/wav":
|
|
filename = "file.wav"
|
|
case "audio/flac":
|
|
filename = "file.flac"
|
|
default:
|
|
filename = "file"
|
|
}
|
|
|
|
audioFilePart, err := writer.CreateFormFile("file", filename)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
_, err = audioFilePart.Write(audioFile)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
writer.Close()
|
|
|
|
var uploadUrl string
|
|
|
|
if data.Name == "ttsd" {
|
|
uploadUrl = config.BotWebhookUrls[i.Interaction.ChannelID]
|
|
} else {
|
|
uploadUrl = config.BotTransferShUrl
|
|
}
|
|
|
|
uploadRequest, err := http.NewRequest("POST", uploadUrl, uploadRequestBody)
|
|
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
uploadRequest.Header.Add("Content-Type", writer.FormDataContentType())
|
|
if data.Name == "ttst" {
|
|
uploadRequest.Header.Add("Max-Days", "1")
|
|
}
|
|
|
|
client := &http.Client{}
|
|
uploadResponse, err := client.Do(uploadRequest)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
defer uploadResponse.Body.Close()
|
|
|
|
if uploadResponse.StatusCode != http.StatusOK {
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
uploadResponseBodyBytes, err := io.ReadAll(uploadResponse.Body)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
|
|
content := "Error"
|
|
|
|
dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if data.Name == "ttsd" {
|
|
content := fmt.Sprintf("Duration: %s", apiResponse.Header.Get("Duration"))
|
|
|
|
_, err = dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
} else {
|
|
content := fmt.Sprintf("Duration: %s\nLink: <%s>", apiResponse.Header.Get("Duration"), strings.TrimSpace(string(uploadResponseBodyBytes)))
|
|
|
|
_, err = dg.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
|
Content: &content,
|
|
})
|
|
}
|
|
|
|
if err == nil && discordLogFile != nil {
|
|
t := time.Now()
|
|
|
|
jsonBytes, err := json.Marshal(DiscordLogStruct{
|
|
DateTimeString: t.Format(time.RFC3339),
|
|
Timestamp: t.Unix(),
|
|
GuildId: i.Interaction.GuildID,
|
|
ChannelId: i.Interaction.ChannelID,
|
|
UserId: i.Interaction.Member.User.ID,
|
|
Command: data.Name,
|
|
TextHost: textHost,
|
|
TextLength: len(text),
|
|
Voice: values["voice"],
|
|
})
|
|
|
|
if err == nil {
|
|
fmt.Fprintln(discordLogFile, string(jsonBytes))
|
|
} else {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
})
|
|
|
|
err = dg.Open()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
defer dg.Close()
|
|
}
|
|
|
|
if config.ServerPort > 0 {
|
|
go func() {
|
|
http.HandleFunc("/", HttpHandler)
|
|
|
|
if *allInterfaces {
|
|
http.ListenAndServe(fmt.Sprintf(":%d", config.ServerPort), nil)
|
|
} else {
|
|
http.ListenAndServe(fmt.Sprintf("localhost:%d", config.ServerPort), nil)
|
|
}
|
|
}()
|
|
|
|
timeoutTicker := time.NewTicker(time.Duration(config.TimeoutTickerInterval) * time.Millisecond)
|
|
rateLimitTicker := time.NewTicker(time.Duration(config.RateLimit[1]) * time.Millisecond)
|
|
done := make(chan bool)
|
|
|
|
go func() {
|
|
requests := config.RateLimit[0]
|
|
startTime := time.Time{}
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case i := <-c:
|
|
if i == 0 {
|
|
if startTime.IsZero() {
|
|
if requests == 0 {
|
|
c <- 429
|
|
} else {
|
|
startTime = time.Now()
|
|
requests--
|
|
c <- 0
|
|
}
|
|
} else {
|
|
c <- 503
|
|
}
|
|
} else if i == 1 {
|
|
startTime = time.Time{}
|
|
} else if i == 2 {
|
|
jsonBytes, err := json.Marshal(ServerInfoStruct{
|
|
Busy: !startTime.IsZero(),
|
|
Requests: []int{requests, config.RateLimit[0], config.RateLimit[1]},
|
|
})
|
|
|
|
if err == nil {
|
|
fmt.Println(string(jsonBytes))
|
|
}
|
|
} else if i == 3 {
|
|
configBytes, err := os.ReadFile(*configFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for k := range config.BotWebhookUrls {
|
|
delete(config.BotWebhookUrls, k)
|
|
}
|
|
|
|
for k := range config.BotAllowedHosts {
|
|
delete(config.BotAllowedHosts, k)
|
|
}
|
|
|
|
for k := range config.TtsCommands {
|
|
delete(config.TtsCommands, k)
|
|
}
|
|
|
|
err = json.Unmarshal(configBytes, &config)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for k := range bannedIps {
|
|
delete(bannedIps, k)
|
|
}
|
|
|
|
bannedIpsBytes, err := os.ReadFile(config.BannedIpsFile)
|
|
if err == nil {
|
|
json.Unmarshal(bannedIpsBytes, &bannedIps)
|
|
}
|
|
|
|
if *enableBot {
|
|
for k := range bannedDiscordUsers {
|
|
delete(bannedDiscordUsers, k)
|
|
}
|
|
|
|
bannedDiscordUsersBytes, err := os.ReadFile(config.BannedDiscordUsersFile)
|
|
if err == nil {
|
|
json.Unmarshal(bannedDiscordUsersBytes, &bannedDiscordUsers)
|
|
}
|
|
}
|
|
} else if i == 4 {
|
|
requests = config.RateLimit[0]
|
|
}
|
|
case t := <-timeoutTicker.C:
|
|
if !startTime.IsZero() && t.After(startTime.Add(time.Duration(config.Timeout)*time.Millisecond)) {
|
|
cmd := exec.Command(config.KillCommand[0], config.KillCommand[1:]...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
case <-rateLimitTicker.C:
|
|
requests = config.RateLimit[0]
|
|
}
|
|
}
|
|
}()
|
|
|
|
fmt.Scanln()
|
|
timeoutTicker.Stop()
|
|
rateLimitTicker.Stop()
|
|
done <- true
|
|
} else {
|
|
fmt.Scanln()
|
|
}
|
|
}
|