123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- package main
- import (
- "fmt"
- "io"
- "log"
- "math/rand"
- "net/http"
- "net/url"
- "os"
- "runtime/debug"
- "strconv"
- "strings"
- "sync"
- "time"
- tirc "github.com/gempir/go-twitch-irc/v4"
- "github.com/google/uuid"
- "github.com/gopxl/beep"
- "github.com/gopxl/beep/mp3"
- "github.com/gopxl/beep/speaker"
- "github.com/gopxl/beep/wav"
- "github.com/nicklaw5/helix"
- )
- type IRC_CLIENT struct {
- Name string
- Client *tirc.Client
- // 1 == main channel
- // 2 == sub channel
- ChannelMap map[string]*IRC_CHANNEL
- }
- type IRC_CHANNEL struct {
- Name string
- BroadCasterID string
- Type int
- }
- func (C *IRC_CLIENT) ReplyToUser(userName string, msg string, channel string) {
- if channel == "" {
- C.Client.Say(C.Name, userName+" >> "+msg)
- } else {
- C.Client.Say(channel, userName+" >> "+msg)
- }
- }
- func (C *IRC_CLIENT) Reply(msg string, channel string) {
- if channel == "" {
- C.Client.Say(C.Name, msg)
- } else {
- C.Client.Say(channel, msg)
- }
- }
- func (C *IRC_CLIENT) GetAllChannelEmotes() {
- for _, v := range C.ChannelMap {
- log.Println("GETTING EMOTED FOR CHANNEL: ", v.Name)
- GetChannelEmotes(v.BroadCasterID)
- }
- }
- func NewUserSubReSubRaidMessage(user tirc.UserNoticeMessage) {
- TWITCH_CLIENT.ReplyToUser(user.User.DisplayName, "thank you!", "")
- }
- func USER_TEST(user tirc.UserStateMessage) {
- log.Println("State:", user)
- }
- func (C *IRC_CLIENT) POST_INFO() {
- defer func() {
- r := recover()
- if r != nil {
- log.Println(r, string(debug.Stack()))
- }
- monitor <- 1337
- }()
- time.Sleep(1 * time.Hour)
- if C.Client != nil {
- returnText, ok := TextCommands["!x"]
- if ok {
- TWITCH_CLIENT.Reply(returnText, "")
- }
- time.Sleep(30 * time.Second)
- returnText, ok = TextCommands["!discord"]
- if ok {
- TWITCH_CLIENT.Reply(returnText, "")
- }
- time.Sleep(30 * time.Second)
- returnText, ok = TextCommands["!vpn"]
- if ok {
- TWITCH_CLIENT.Reply(returnText, "")
- }
- }
- }
- func (C *IRC_CLIENT) Connect() {
- defer func() {
- r := recover()
- if r != nil {
- log.Println(r, string(debug.Stack()))
- }
- monitor <- 10
- }()
- log.Println("KEY LENGTH: ", len(os.Getenv("TWITCH_KEY")))
- C.Client = tirc.NewClient(C.Name, os.Getenv("TWITCH_KEY"))
- C.Client.SendPings = true
- C.Client.IdlePingInterval = time.Duration(time.Second * 10)
- C.Client.PongTimeout = time.Duration(time.Second * 60)
- C.Client.OnPrivateMessage(NewMessage)
- C.Client.OnUserNoticeMessage(NewUserSubReSubRaidMessage)
- // C.Client.OnUserStateMessage(USER_TEST)
- go func() {
- time.Sleep(3 * time.Second)
- C.JoinChannels()
- time.Sleep(3 * time.Second)
- go TWITCH_CLIENT.POST_INFO()
- TWITCH_CLIENT.Reply("BOT ONLINE!", "")
- }()
- err := C.Client.Connect()
- if err != nil {
- log.Println(err)
- }
- }
- func PlaceEventOnSoundQueue(t string, data string) {
- select {
- case SoundQueue <- SoundEvent{
- T: t,
- Data: data,
- }:
- default:
- fmt.Println("SOUND QUEUE FULL")
- fmt.Println("SOUND QUEUE FULL")
- fmt.Println("SOUND QUEUE FULL")
- fmt.Println("SOUND QUEUE FULL")
- }
- return
- }
- func (C *IRC_CLIENT) JoinChannels() {
- for _, v := range C.ChannelMap {
- log.Println("JOINING CHANNEL: ", v)
- C.Client.Join(v.Name)
- GetChannelEmotes(v.BroadCasterID)
- }
- }
- func NewMessage(msg tirc.PrivateMessage) {
- ProcessMessage(msg)
- }
- func ProcessMessage(msg tirc.PrivateMessage) {
- defer func() {
- if r := recover(); r != nil {
- log.Println(r)
- log.Println(string(debug.Stack()))
- }
- }()
- // lowerUser := strings.ToLower(msg.User.DisplayName)
- // fmt.Println("---------------------")
- // fmt.Println(msg.FirstMessage)
- // fmt.Println(msg.Action)
- // fmt.Println(msg.Bits)
- // fmt.Println(msg.Tags)
- // fmt.Println(msg.Type)
- // for _, v := range msg.Emotes {
- // fmt.Println(v.ID, v.Name, v.Count)
- // for _, vv := range v.Positions {
- // fmt.Println(vv.Start, vv.End)
- // }
- // }
- // fmt.Println(msg.CustomRewardID)
- // fmt.Println(msg.User.ID)
- // fmt.Println(msg.User.Name)
- // fmt.Println(lowerUser)
- // fmt.Println(msg.Channel, msg.Message)
- // fmt.Println("---------------------")
- U, err := FindOrUpsertUser(&msg.User)
- if err != nil {
- U.DisplayName = msg.User.DisplayName
- U.Name = msg.User.Name
- U.ID = msg.User.ID
- U.Color = msg.User.Color
- U.Badges = msg.User.Badges
- err = IncrementUserPoints(U, 500)
- if err == nil {
- U.Points = 100
- }
- } else {
- _ = IncrementUserPoints(U, 5)
- }
- log.Println("CUSTOM REWARD ID:", msg.CustomRewardID)
- returnText, ok := TextCommands[msg.Message]
- if ok {
- TWITCH_CLIENT.Reply(returnText, "")
- return
- }
- if CheckCustomReward(U, msg) {
- return
- }
- mp3, ok := MP3Map[msg.CustomRewardID]
- if ok {
- go PlaceEventOnSoundQueue("mp3", mp3)
- // go PlayMP3(mp3)
- return
- }
- if strings.Contains(msg.Message, "!time") {
- TWITCH_CLIENT.Reply(time.Now().Format(time.RFC3339), "")
- return
- }
- if strings.Contains(msg.Message, "!bot") {
- if msg.User.DisplayName == "KEYB1ND_" {
- go askTheBot(msg.Message)
- }
- return
- }
- // banword := ""
- // isBanned := false
- // if strings.Contains(msg.Message, " tailwind ") || strings.HasPrefix(msg.Message, "tailwind") {
- // banword = "tailwind"
- // isBanned = true
- // }
- // if strings.Contains(msg.Message, " rust ") || strings.HasPrefix(msg.Message, "rust") {
- // banword = "rust"
- // isBanned = true
- // }
- // if isBanned {
- // TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "You said a naughty word: "+banword+" -1000 points for you.", "")
- // _ = IncrementUserPoints(U, -1000)
- // return
- // }
- // if strings.Contains(msg.Message, "!tts") {
- // go CustomTTS(*U, msg)
- // return
- // }
- if strings.Contains(msg.Message, "!quote help") || msg.Message == "!quote" {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "To make a quote use 'Quote:' before your sentence...... example: 'Quote: This is a quote!' ", "")
- return
- }
- if strings.Contains(msg.Message, "!points") {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you have >> "+strconv.Itoa(U.Points)+" Points", "")
- return
- }
- if strings.Contains(msg.Message, "!quote") {
- RandQuote(&msg)
- return
- }
- if strings.Contains(msg.Message, "!top10") {
- Top10Command()
- return
- }
- if strings.Contains(msg.Message, "!roll") {
- err := UserRollCommand(U, &msg)
- if err != nil {
- log.Println("Error While Rolling: ", err)
- return
- }
- return
- }
- _ = SaveMessage(&msg)
- // log.Println("USER FROM DB: ", U)
- }
- var (
- RollTimeout = make(map[string]time.Time)
- RollLock sync.Mutex
- )
- func RandQuote(msg *tirc.PrivateMessage) {
- splitMatch := strings.Split(msg.Message, " ")
- if len(splitMatch) < 2 || len(splitMatch) > 2 {
- return
- }
- userToLower := strings.ToLower(splitMatch[1])
- userToLower = strings.Replace(userToLower, "@", "", -1)
- msgs, err := FindUserMessagesFromMatch(userToLower, "Quote:")
- if err != nil {
- return
- }
- if len(msgs) == 0 {
- TWITCH_CLIENT.Reply("No qoutes found for "+userToLower, "")
- return
- }
- random := rand.Intn(len(msgs))
- selectedMSG := msgs[random]
- outMSG := strings.Replace(selectedMSG.Message, "Quote:", "", -1)
- go PlayTTS(outMSG)
- TWITCH_CLIENT.Reply(selectedMSG.User.DisplayName+" '' "+outMSG+" '' - "+selectedMSG.Time.Format("Mon 02 Jan 15:04:05 MST 2006"), "")
- }
- func PlayMP3(tag string) {
- // cmd := exec.Command("ffplay", "-v", "0", "-nodisp", "-autoexit", "./mp3/"+tag+".mp3")
- // out, err := cmd.CombinedOutput()
- // if err != nil {
- // log.Println(err, string(out))
- // }
- f, err := os.Open("./mp3/" + tag + ".mp3")
- if err != nil {
- log.Fatal(err)
- }
- streamer, format, err := mp3.Decode(f)
- if err != nil {
- log.Fatal(err)
- }
- defer streamer.Close()
- speaker.Init(format.SampleRate, format.SampleRate.N(1000))
- done := make(chan bool)
- speaker.Play(beep.Seq(streamer, beep.Callback(func() {
- done <- true
- })))
- <-done
- }
- func Top10Command() {
- userList := GetTop10()
- outMsg := ""
- rank := 1
- for _, v := range userList {
- if v.Name == USERNAME {
- continue
- }
- outMsg += strconv.Itoa(rank) + "#" + v.DisplayName + "(" + strconv.Itoa(v.Points) + ") ......."
- rank++
- }
- TWITCH_CLIENT.Reply(outMsg, "")
- }
- func UserRollCommand(user *User, msg *tirc.PrivateMessage) (err error) {
- rollSplit := strings.Split(msg.Message, " ")
- if len(rollSplit) < 2 {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
- return
- }
- rollAmount, err := strconv.Atoi(rollSplit[1])
- if err != nil {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
- return
- }
- if user.Points < rollAmount || rollAmount < 0 {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you do not have enough points to gamba", "")
- return
- }
- lastRoll, ok := RollTimeout[msg.User.ID]
- if ok {
- seconds := time.Since(lastRoll).Seconds()
- if seconds < 20 {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, strconv.Itoa(int(20-seconds))+" seconds until you can roll again", "")
- return
- }
- }
- RollTimeout[msg.User.ID] = time.Now()
- random := rand.Intn(100) + 1
- if random < 30 {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
- _ = IncrementUserPoints(user, -rollAmount)
- } else if random%2 == 0 {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins "+strconv.Itoa(rollAmount), "")
- _ = IncrementUserPoints(user, rollAmount)
- } else {
- TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
- _ = IncrementUserPoints(user, -rollAmount)
- }
- return
- }
- func CreateAPIClient() {
- twitchkey := os.Getenv("TWITCH_KEY")
- twitchkey = strings.Split(twitchkey, ":")[1]
- var err error
- HELIX_CLIENT, err = helix.NewClient(&helix.Options{
- AppAccessToken: twitchkey,
- ClientSecret: os.Getenv("CLIENT_SECRET"),
- ClientID: os.Getenv("CLIENT_ID"),
- })
- if err != nil {
- log.Println("UNABLE TO CREATE API CLIENT: ", err)
- }
- }
- func GetGlobalEmotes() {
- resp, err := HELIX_CLIENT.GetGlobalEmotes()
- if err != nil {
- log.Println("ERROR GETTING GLOBAL EMOTES:", err)
- return
- }
- for _, v := range resp.Data.Emotes {
- EmoteMap[v.ID] = v
- }
- }
- func GetChannelEmotes(BroadCasterID string) {
- resp, err := HELIX_CLIENT.GetChannelEmotes(&helix.GetChannelEmotesParams{
- BroadcasterID: BroadCasterID,
- })
- if err != nil {
- log.Println("ERROR GETTING CHANNEL EMOTES:", err)
- return
- }
- for _, v := range resp.Data.Emotes {
- EmoteMap[v.ID] = v
- }
- }
- // https://github.com/coqui-ai/TTS
- // https://github.com/coqui-ai/TTS
- // python3 TTS/server/server.py --vocoder_name vocoder_models/en/ljspeech/hifigan_v2
- // jenny is good too
- func CustomTTS(user User, msg tirc.PrivateMessage) {
- if user.Points < 50 {
- TWITCH_CLIENT.ReplyToUser(user.DisplayName, "You need 200 points for TTS", "")
- return
- }
- splitTTS := strings.Split(msg.Message, "tts")
- if len(splitTTS) < 2 {
- TWITCH_CLIENT.ReplyToUser(user.DisplayName, "TTS was badly formatted.. try: !tts [MSG]", "")
- return
- }
- err := IncrementUserPoints(&user, -50)
- if err != nil {
- TWITCH_CLIENT.ReplyToUser(user.DisplayName, "We could not play your sound clip!", "")
- return
- }
- PlayTTS(splitTTS[1])
- }
- func PlayTTS(msg string) {
- defer func() {
- r := recover()
- if r != nil {
- log.Println(r, string(debug.Stack()))
- }
- }()
- // http://127.0.0.1:5002/api/tts?text=whats%20up&speaker_id=&style_wav=&language_id=
- params := url.Values{}
- params.Add("text", msg)
- httpClient := new(http.Client)
- // urlmsg := url.QueryEscape(msg)
- req, err := http.NewRequest("GET", "http://127.0.0.1:5002/api/tts?"+params.Encode(), nil)
- if err != nil {
- log.Println(err)
- return
- }
- resp, err := httpClient.Do(req)
- if err != nil {
- log.Println(err)
- return
- }
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- log.Println(err)
- return
- }
- fileName := uuid.NewString() + "-" + strconv.Itoa(int(time.Now().UnixNano()))
- f, err := os.Create("./tts/" + fileName + ".wav")
- if err != nil {
- log.Println(err)
- return
- }
- defer f.Close()
- f2, err := os.Create("./tts/" + fileName + ".txt")
- if err != nil {
- log.Println(err)
- return
- }
- defer f2.Close()
- _, err = f.Write(bytes)
- if err != nil {
- log.Println(err)
- return
- }
- _, err = f2.Write([]byte(msg))
- if err != nil {
- log.Println(err)
- return
- }
- log.Println("BYTES:", len(bytes))
- PlayTTSFile(f.Name())
- }
- func PlayTTSFile(tag string) {
- f, err := os.Open(tag)
- if err != nil {
- log.Println("error opening tts file:", err)
- return
- }
- streamer, format, err := wav.Decode(f)
- if err != nil {
- log.Fatal(err)
- }
- defer streamer.Close()
- speaker.Init(format.SampleRate, format.SampleRate.N(time.Second))
- done := make(chan bool)
- speaker.Play(beep.Seq(streamer, beep.Callback(func() {
- done <- true
- })))
- <-done
- }
|