twitch-client.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "log"
  6. "math/rand"
  7. "net/http"
  8. "net/url"
  9. "os"
  10. "runtime/debug"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. tirc "github.com/gempir/go-twitch-irc/v4"
  16. "github.com/google/uuid"
  17. "github.com/gopxl/beep"
  18. "github.com/gopxl/beep/mp3"
  19. "github.com/gopxl/beep/speaker"
  20. "github.com/gopxl/beep/wav"
  21. "github.com/nicklaw5/helix"
  22. )
  23. type IRC_CLIENT struct {
  24. Name string
  25. Client *tirc.Client
  26. // 1 == main channel
  27. // 2 == sub channel
  28. ChannelMap map[string]*IRC_CHANNEL
  29. }
  30. type IRC_CHANNEL struct {
  31. Name string
  32. BroadCasterID string
  33. Type int
  34. }
  35. func (C *IRC_CLIENT) ReplyToUser(userName string, msg string, channel string) {
  36. if channel == "" {
  37. C.Client.Say(C.Name, userName+" >> "+msg)
  38. } else {
  39. C.Client.Say(channel, userName+" >> "+msg)
  40. }
  41. }
  42. func (C *IRC_CLIENT) Reply(msg string, channel string) {
  43. if channel == "" {
  44. C.Client.Say(C.Name, msg)
  45. } else {
  46. C.Client.Say(channel, msg)
  47. }
  48. }
  49. func (C *IRC_CLIENT) GetAllChannelEmotes() {
  50. for _, v := range C.ChannelMap {
  51. log.Println("GETTING EMOTED FOR CHANNEL: ", v.Name)
  52. GetChannelEmotes(v.BroadCasterID)
  53. }
  54. }
  55. func NewUserSubReSubRaidMessage(user tirc.UserNoticeMessage) {
  56. TWITCH_CLIENT.ReplyToUser(user.User.DisplayName, "thank you!", "")
  57. }
  58. func USER_TEST(user tirc.UserStateMessage) {
  59. log.Println("State:", user)
  60. }
  61. func (C *IRC_CLIENT) POST_INFO() {
  62. defer func() {
  63. r := recover()
  64. if r != nil {
  65. log.Println(r, string(debug.Stack()))
  66. }
  67. monitor <- 1337
  68. }()
  69. time.Sleep(1 * time.Hour)
  70. if C.Client != nil {
  71. returnText, ok := TextCommands["!x"]
  72. if ok {
  73. TWITCH_CLIENT.Reply(returnText, "")
  74. }
  75. time.Sleep(30 * time.Second)
  76. returnText, ok = TextCommands["!discord"]
  77. if ok {
  78. TWITCH_CLIENT.Reply(returnText, "")
  79. }
  80. time.Sleep(30 * time.Second)
  81. returnText, ok = TextCommands["!vpn"]
  82. if ok {
  83. TWITCH_CLIENT.Reply(returnText, "")
  84. }
  85. }
  86. }
  87. func (C *IRC_CLIENT) Connect() {
  88. defer func() {
  89. r := recover()
  90. if r != nil {
  91. log.Println(r, string(debug.Stack()))
  92. }
  93. monitor <- 10
  94. }()
  95. log.Println("KEY LENGTH: ", len(os.Getenv("TWITCH_KEY")))
  96. C.Client = tirc.NewClient(C.Name, os.Getenv("TWITCH_KEY"))
  97. C.Client.SendPings = true
  98. C.Client.IdlePingInterval = time.Duration(time.Second * 10)
  99. C.Client.PongTimeout = time.Duration(time.Second * 60)
  100. C.Client.OnPrivateMessage(NewMessage)
  101. C.Client.OnUserNoticeMessage(NewUserSubReSubRaidMessage)
  102. // C.Client.OnUserStateMessage(USER_TEST)
  103. go func() {
  104. time.Sleep(3 * time.Second)
  105. C.JoinChannels()
  106. time.Sleep(3 * time.Second)
  107. go TWITCH_CLIENT.POST_INFO()
  108. TWITCH_CLIENT.Reply("Eve is online..", "")
  109. }()
  110. err := C.Client.Connect()
  111. if err != nil {
  112. log.Println(err)
  113. }
  114. }
  115. func PlaceBotEventInQueue(t string, data string, original string) (ok bool) {
  116. select {
  117. case BotEventQueue <- BotEvent{
  118. T: t,
  119. Data: data,
  120. Original: original,
  121. }:
  122. return true
  123. default:
  124. fmt.Println("BOT QUEUE FULL")
  125. fmt.Println("BOT QUEUE FULL")
  126. fmt.Println("BOT QUEUE FULL")
  127. fmt.Println("BOT QUEUE FULL")
  128. }
  129. return
  130. }
  131. func PlaceSoundEventInQueue(t string, data string) (ok bool) {
  132. select {
  133. case SoundEventQueue <- SoundEvent{
  134. T: t,
  135. Data: data,
  136. }:
  137. return true
  138. default:
  139. fmt.Println("SOUND QUEUE FULL")
  140. fmt.Println("SOUND QUEUE FULL")
  141. fmt.Println("SOUND QUEUE FULL")
  142. fmt.Println("SOUND QUEUE FULL")
  143. }
  144. return
  145. }
  146. func (C *IRC_CLIENT) JoinChannels() {
  147. for _, v := range C.ChannelMap {
  148. log.Println("JOINING CHANNEL: ", v)
  149. C.Client.Join(v.Name)
  150. GetChannelEmotes(v.BroadCasterID)
  151. }
  152. }
  153. func NewMessage(msg tirc.PrivateMessage) {
  154. ProcessMessage(msg)
  155. }
  156. func ProcessMessage(msg tirc.PrivateMessage) {
  157. defer func() {
  158. if r := recover(); r != nil {
  159. log.Println(r)
  160. log.Println(string(debug.Stack()))
  161. }
  162. }()
  163. // lowerUser := strings.ToLower(msg.User.DisplayName)
  164. // fmt.Println("---------------------")
  165. // fmt.Println(msg.FirstMessage)
  166. // fmt.Println(msg.Action)
  167. // fmt.Println(msg.Bits)
  168. // fmt.Println(msg.Tags)
  169. // fmt.Println(msg.Type)
  170. // for _, v := range msg.Emotes {
  171. // fmt.Println(v.ID, v.Name, v.Count)
  172. // for _, vv := range v.Positions {
  173. // fmt.Println(vv.Start, vv.End)
  174. // }
  175. // }
  176. // fmt.Println(msg.CustomRewardID)
  177. // fmt.Println(msg.User.ID)
  178. // fmt.Println(msg.User.Name)
  179. // fmt.Println(lowerUser)
  180. // fmt.Println(msg.Channel, msg.Message)
  181. // fmt.Println("---------------------")
  182. U, err := FindOrUpsertUser(&msg.User)
  183. if err != nil {
  184. if U.DisplayName == "" {
  185. U.DisplayName = msg.User.DisplayName
  186. }
  187. TWITCH_CLIENT.Reply(U.DisplayName, "hey eve, can you say hi to "+U.DisplayName)
  188. go chatTalksToBot(*U, tirc.PrivateMessage{
  189. Message: "!eve Please say hello to " + U.DisplayName + ". They just joined keybind's twitch stream and sent their first message. Please address them directly.",
  190. })
  191. U.DisplayName = msg.User.DisplayName
  192. U.Name = msg.User.Name
  193. U.ID = msg.User.ID
  194. U.Color = msg.User.Color
  195. U.Badges = msg.User.Badges
  196. err = IncrementUserPoints(U, 500)
  197. if err == nil {
  198. U.Points = 100
  199. }
  200. } else {
  201. _ = IncrementUserPoints(U, 5)
  202. }
  203. log.Println("CUSTOM REWARD ID:", msg.CustomRewardID)
  204. returnText, ok := TextCommands[msg.Message]
  205. if ok {
  206. TWITCH_CLIENT.Reply(returnText, "")
  207. return
  208. }
  209. if CheckCustomReward(U, msg) {
  210. return
  211. }
  212. mp3, ok := MP3Map[msg.CustomRewardID]
  213. if ok {
  214. go PlaceSoundEventInQueue("mp3", mp3)
  215. // go PlayMP3(mp3)
  216. return
  217. }
  218. if strings.Contains(msg.Message, "!time") {
  219. TWITCH_CLIENT.Reply(time.Now().Format(time.RFC3339), "")
  220. return
  221. }
  222. // banword := ""
  223. // isBanned := false
  224. // if strings.Contains(msg.Message, " tailwind ") || strings.HasPrefix(msg.Message, "tailwind") {
  225. // banword = "tailwind"
  226. // isBanned = true
  227. // }
  228. // if strings.Contains(msg.Message, " rust ") || strings.HasPrefix(msg.Message, "rust") {
  229. // banword = "rust"
  230. // isBanned = true
  231. // }
  232. // if isBanned {
  233. // TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "You said a naughty word: "+banword+" -1000 points for you.", "")
  234. // _ = IncrementUserPoints(U, -1000)
  235. // return
  236. // }
  237. if strings.Contains(msg.Message, "!eve") {
  238. BOTBUSY = true
  239. go chatTalksToBot(*U, msg)
  240. fmt.Println(msg.User.DisplayName)
  241. if msg.User.DisplayName == "KEYB1ND_" {
  242. // BOTBUSY = true
  243. // go chatTalksToBot(*U, msg)
  244. } else {
  245. // fmt.Println("BADGES")
  246. // isAllowed := false
  247. // for i, v := range msg.User.Badges {
  248. // fmt.Println("BADGE:", i, v)
  249. // if i == "vip" && v == 1 {
  250. // fmt.Println("FOUND IT:", i, v)
  251. // isAllowed = true
  252. // break
  253. // }
  254. // if i == "subscriber" {
  255. // fmt.Println("FOUND IT:", i, v)
  256. // isAllowed = true
  257. // break
  258. // }
  259. // if i == "moderator" {
  260. // fmt.Println("FOUND IT:", i, v)
  261. // isAllowed = true
  262. // break
  263. // }
  264. // }
  265. // if BOTBUSY {
  266. // TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "You need to wait until Eve has spoken", "")
  267. // return
  268. // }
  269. // if isAllowed {
  270. // BOTBUSY = true
  271. // go chatTalksToBot(*U, msg)
  272. // } else {
  273. // TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "You are not worthy of speaking to Eve, only VIP, Subs and Mods can speak you peasant", "")
  274. // }
  275. // return
  276. }
  277. }
  278. if strings.Contains(msg.Message, "!tts") {
  279. go CustomTTS(*U, msg)
  280. return
  281. }
  282. if strings.Contains(msg.Message, "!quote help") || msg.Message == "!quote" {
  283. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "To make a quote use 'Quote:' before your sentence...... example: 'Quote: This is a quote!' ", "")
  284. return
  285. }
  286. if strings.Contains(msg.Message, "!points") {
  287. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you have >> "+strconv.Itoa(U.Points)+" Points", "")
  288. return
  289. }
  290. if strings.Contains(msg.Message, "!quote") {
  291. RandQuote(&msg)
  292. return
  293. }
  294. if strings.Contains(msg.Message, "!top10") {
  295. Top10Command()
  296. return
  297. }
  298. if strings.Contains(msg.Message, "!roll") {
  299. err := UserRollCommand(U, &msg)
  300. if err != nil {
  301. log.Println("Error While Rolling: ", err)
  302. return
  303. }
  304. return
  305. }
  306. _ = SaveMessage(&msg)
  307. // log.Println("USER FROM DB: ", U)
  308. }
  309. var (
  310. RollTimeout = make(map[string]time.Time)
  311. RollLock sync.Mutex
  312. )
  313. func RandQuote(msg *tirc.PrivateMessage) {
  314. splitMatch := strings.Split(msg.Message, " ")
  315. if len(splitMatch) < 2 || len(splitMatch) > 2 {
  316. return
  317. }
  318. userToLower := strings.ToLower(splitMatch[1])
  319. userToLower = strings.Replace(userToLower, "@", "", -1)
  320. msgs, err := FindUserMessagesFromMatch(userToLower, "Quote:")
  321. if err != nil {
  322. return
  323. }
  324. if len(msgs) == 0 {
  325. TWITCH_CLIENT.Reply("No qoutes found for "+userToLower, "")
  326. return
  327. }
  328. random := rand.Intn(len(msgs))
  329. selectedMSG := msgs[random]
  330. outMSG := strings.Replace(selectedMSG.Message, "Quote:", "", -1)
  331. go PlayTTS(outMSG)
  332. TWITCH_CLIENT.Reply(selectedMSG.User.DisplayName+" '' "+outMSG+" '' - "+selectedMSG.Time.Format("Mon 02 Jan 15:04:05 MST 2006"), "")
  333. }
  334. func PlayRewardMP3(tag string) {
  335. f, err := os.Open("./mp3/" + tag + ".mp3")
  336. if err != nil {
  337. log.Fatal(err)
  338. }
  339. streamer, format, err := mp3.Decode(f)
  340. if err != nil {
  341. log.Fatal(err)
  342. }
  343. defer streamer.Close()
  344. speaker.Init(format.SampleRate, format.SampleRate.N(time.Second))
  345. done := make(chan bool)
  346. speaker.Play(beep.Seq(streamer, beep.Callback(func() {
  347. done <- true
  348. })))
  349. <-done
  350. }
  351. func Top10Command() {
  352. userList := GetTop10()
  353. outMsg := ""
  354. rank := 1
  355. for _, v := range userList {
  356. if v.Name == USERNAME {
  357. continue
  358. }
  359. outMsg += strconv.Itoa(rank) + "#" + v.DisplayName + "(" + strconv.Itoa(v.Points) + ") ......."
  360. rank++
  361. }
  362. TWITCH_CLIENT.Reply(outMsg, "")
  363. }
  364. func UserRollCommand(user *User, msg *tirc.PrivateMessage) (err error) {
  365. rollSplit := strings.Split(msg.Message, " ")
  366. if len(rollSplit) < 2 {
  367. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  368. return
  369. }
  370. rollAmount, err := strconv.Atoi(rollSplit[1])
  371. if err != nil {
  372. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  373. return
  374. }
  375. if user.Points < rollAmount || rollAmount < 0 {
  376. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you do not have enough points to gamba", "")
  377. return
  378. }
  379. lastRoll, ok := RollTimeout[msg.User.ID]
  380. if ok {
  381. seconds := time.Since(lastRoll).Seconds()
  382. if seconds < 20 {
  383. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, strconv.Itoa(int(20-seconds))+" seconds until you can roll again", "")
  384. return
  385. }
  386. }
  387. RollTimeout[msg.User.ID] = time.Now()
  388. random := rand.Intn(100) + 1
  389. if random < 30 {
  390. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  391. _ = IncrementUserPoints(user, -rollAmount)
  392. } else if random%2 == 0 {
  393. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins "+strconv.Itoa(rollAmount), "")
  394. _ = IncrementUserPoints(user, rollAmount)
  395. } else {
  396. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  397. _ = IncrementUserPoints(user, -rollAmount)
  398. }
  399. return
  400. }
  401. func CreateAPIClient() {
  402. twitchkey := os.Getenv("TWITCH_KEY")
  403. twitchkey = strings.Split(twitchkey, ":")[1]
  404. var err error
  405. HELIX_CLIENT, err = helix.NewClient(&helix.Options{
  406. AppAccessToken: twitchkey,
  407. ClientSecret: os.Getenv("CLIENT_SECRET"),
  408. ClientID: os.Getenv("CLIENT_ID"),
  409. })
  410. if err != nil {
  411. log.Println("UNABLE TO CREATE API CLIENT: ", err)
  412. }
  413. }
  414. func GetGlobalEmotes() {
  415. resp, err := HELIX_CLIENT.GetGlobalEmotes()
  416. if err != nil {
  417. log.Println("ERROR GETTING GLOBAL EMOTES:", err)
  418. return
  419. }
  420. for _, v := range resp.Data.Emotes {
  421. EmoteMap[v.ID] = v
  422. }
  423. }
  424. func GetChannelEmotes(BroadCasterID string) {
  425. resp, err := HELIX_CLIENT.GetChannelEmotes(&helix.GetChannelEmotesParams{
  426. BroadcasterID: BroadCasterID,
  427. })
  428. if err != nil {
  429. log.Println("ERROR GETTING CHANNEL EMOTES:", err)
  430. return
  431. }
  432. for _, v := range resp.Data.Emotes {
  433. EmoteMap[v.ID] = v
  434. }
  435. }
  436. // https://github.com/coqui-ai/TTS
  437. // https://github.com/coqui-ai/TTS
  438. // python3 TTS/server/server.py --vocoder_name vocoder_models/en/ljspeech/hifigan_v2
  439. // jenny is good too
  440. func CustomTTS(user User, msg tirc.PrivateMessage) {
  441. if user.Points < 50 {
  442. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "You need 50 points for TTS", "")
  443. return
  444. }
  445. splitTTS := strings.Split(msg.Message, "tts")
  446. if len(splitTTS) < 2 {
  447. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "TTS was badly formatted.. try: !tts [MSG]", "")
  448. return
  449. }
  450. err := IncrementUserPoints(&user, -50)
  451. if err != nil {
  452. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "We could not play your sound clip!", "")
  453. return
  454. }
  455. PlayTTS(splitTTS[1])
  456. }
  457. func PlayTTS(msg string) {
  458. defer func() {
  459. r := recover()
  460. if r != nil {
  461. log.Println(r, string(debug.Stack()))
  462. }
  463. }()
  464. // http://127.0.0.1:5002/api/tts?text=whats%20up&speaker_id=&style_wav=&language_id=
  465. params := url.Values{}
  466. params.Add("text", msg)
  467. httpClient := new(http.Client)
  468. // urlmsg := url.QueryEscape(msg)
  469. req, err := http.NewRequest("GET", "http://127.0.0.1:5002/api/tts?"+params.Encode(), nil)
  470. if err != nil {
  471. log.Println(err)
  472. return
  473. }
  474. resp, err := httpClient.Do(req)
  475. if err != nil {
  476. log.Println(err)
  477. return
  478. }
  479. bytes, err := io.ReadAll(resp.Body)
  480. if err != nil {
  481. log.Println(err)
  482. return
  483. }
  484. fileName := uuid.NewString() + "-" + strconv.Itoa(int(time.Now().UnixNano()))
  485. f, err := os.Create("./tts/" + fileName + ".wav")
  486. if err != nil {
  487. log.Println(err)
  488. return
  489. }
  490. defer f.Close()
  491. f2, err := os.Create("./tts/" + fileName + ".txt")
  492. if err != nil {
  493. log.Println(err)
  494. return
  495. }
  496. defer f2.Close()
  497. _, err = f.Write(bytes)
  498. if err != nil {
  499. log.Println(err)
  500. return
  501. }
  502. _, err = f2.Write([]byte(msg))
  503. if err != nil {
  504. log.Println(err)
  505. return
  506. }
  507. log.Println("BYTES:", len(bytes))
  508. PlayTTSFile(f.Name())
  509. }
  510. func PlayTTSFile(tag string) {
  511. f, err := os.Open(tag)
  512. if err != nil {
  513. log.Println("error opening tts file:", err)
  514. return
  515. }
  516. streamer, format, err := wav.Decode(f)
  517. if err != nil {
  518. log.Fatal(err)
  519. }
  520. defer streamer.Close()
  521. speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
  522. done := make(chan bool)
  523. speaker.Play(beep.Seq(streamer, beep.Callback(func() {
  524. done <- true
  525. })))
  526. <-done
  527. }