twitch-client.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  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("BOT ONLINE!", "")
  109. }()
  110. err := C.Client.Connect()
  111. if err != nil {
  112. log.Println(err)
  113. }
  114. }
  115. func PlaceEventOnSoundQueue(t string, data string) {
  116. select {
  117. case SoundQueue <- SoundEvent{
  118. T: t,
  119. Data: data,
  120. }:
  121. default:
  122. fmt.Println("SOUND QUEUE FULL")
  123. fmt.Println("SOUND QUEUE FULL")
  124. fmt.Println("SOUND QUEUE FULL")
  125. fmt.Println("SOUND QUEUE FULL")
  126. }
  127. return
  128. }
  129. func (C *IRC_CLIENT) JoinChannels() {
  130. for _, v := range C.ChannelMap {
  131. log.Println("JOINING CHANNEL: ", v)
  132. C.Client.Join(v.Name)
  133. GetChannelEmotes(v.BroadCasterID)
  134. }
  135. }
  136. func NewMessage(msg tirc.PrivateMessage) {
  137. ProcessMessage(msg)
  138. }
  139. func ProcessMessage(msg tirc.PrivateMessage) {
  140. defer func() {
  141. if r := recover(); r != nil {
  142. log.Println(r)
  143. log.Println(string(debug.Stack()))
  144. }
  145. }()
  146. // lowerUser := strings.ToLower(msg.User.DisplayName)
  147. // fmt.Println("---------------------")
  148. // fmt.Println(msg.FirstMessage)
  149. // fmt.Println(msg.Action)
  150. // fmt.Println(msg.Bits)
  151. // fmt.Println(msg.Tags)
  152. // fmt.Println(msg.Type)
  153. // for _, v := range msg.Emotes {
  154. // fmt.Println(v.ID, v.Name, v.Count)
  155. // for _, vv := range v.Positions {
  156. // fmt.Println(vv.Start, vv.End)
  157. // }
  158. // }
  159. // fmt.Println(msg.CustomRewardID)
  160. // fmt.Println(msg.User.ID)
  161. // fmt.Println(msg.User.Name)
  162. // fmt.Println(lowerUser)
  163. // fmt.Println(msg.Channel, msg.Message)
  164. // fmt.Println("---------------------")
  165. U, err := FindOrUpsertUser(&msg.User)
  166. if err != nil {
  167. U.DisplayName = msg.User.DisplayName
  168. U.Name = msg.User.Name
  169. U.ID = msg.User.ID
  170. U.Color = msg.User.Color
  171. U.Badges = msg.User.Badges
  172. err = IncrementUserPoints(U, 500)
  173. if err == nil {
  174. U.Points = 100
  175. }
  176. } else {
  177. _ = IncrementUserPoints(U, 5)
  178. }
  179. log.Println("CUSTOM REWARD ID:", msg.CustomRewardID)
  180. returnText, ok := TextCommands[msg.Message]
  181. if ok {
  182. TWITCH_CLIENT.Reply(returnText, "")
  183. return
  184. }
  185. if CheckCustomReward(U, msg) {
  186. return
  187. }
  188. mp3, ok := MP3Map[msg.CustomRewardID]
  189. if ok {
  190. go PlaceEventOnSoundQueue("mp3", mp3)
  191. // go PlayMP3(mp3)
  192. return
  193. }
  194. if strings.Contains(msg.Message, "!time") {
  195. TWITCH_CLIENT.Reply(time.Now().Format(time.RFC3339), "")
  196. return
  197. }
  198. if strings.Contains(msg.Message, "!bot") {
  199. if msg.User.DisplayName == "KEYB1ND_" {
  200. go askTheBot(msg.Message)
  201. }
  202. return
  203. }
  204. // banword := ""
  205. // isBanned := false
  206. // if strings.Contains(msg.Message, " tailwind ") || strings.HasPrefix(msg.Message, "tailwind") {
  207. // banword = "tailwind"
  208. // isBanned = true
  209. // }
  210. // if strings.Contains(msg.Message, " rust ") || strings.HasPrefix(msg.Message, "rust") {
  211. // banword = "rust"
  212. // isBanned = true
  213. // }
  214. // if isBanned {
  215. // TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "You said a naughty word: "+banword+" -1000 points for you.", "")
  216. // _ = IncrementUserPoints(U, -1000)
  217. // return
  218. // }
  219. // if strings.Contains(msg.Message, "!tts") {
  220. // go CustomTTS(*U, msg)
  221. // return
  222. // }
  223. if strings.Contains(msg.Message, "!quote help") || msg.Message == "!quote" {
  224. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "To make a quote use 'Quote:' before your sentence...... example: 'Quote: This is a quote!' ", "")
  225. return
  226. }
  227. if strings.Contains(msg.Message, "!points") {
  228. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you have >> "+strconv.Itoa(U.Points)+" Points", "")
  229. return
  230. }
  231. if strings.Contains(msg.Message, "!quote") {
  232. RandQuote(&msg)
  233. return
  234. }
  235. if strings.Contains(msg.Message, "!top10") {
  236. Top10Command()
  237. return
  238. }
  239. if strings.Contains(msg.Message, "!roll") {
  240. err := UserRollCommand(U, &msg)
  241. if err != nil {
  242. log.Println("Error While Rolling: ", err)
  243. return
  244. }
  245. return
  246. }
  247. _ = SaveMessage(&msg)
  248. // log.Println("USER FROM DB: ", U)
  249. }
  250. var (
  251. RollTimeout = make(map[string]time.Time)
  252. RollLock sync.Mutex
  253. )
  254. func RandQuote(msg *tirc.PrivateMessage) {
  255. splitMatch := strings.Split(msg.Message, " ")
  256. if len(splitMatch) < 2 || len(splitMatch) > 2 {
  257. return
  258. }
  259. userToLower := strings.ToLower(splitMatch[1])
  260. userToLower = strings.Replace(userToLower, "@", "", -1)
  261. msgs, err := FindUserMessagesFromMatch(userToLower, "Quote:")
  262. if err != nil {
  263. return
  264. }
  265. if len(msgs) == 0 {
  266. TWITCH_CLIENT.Reply("No qoutes found for "+userToLower, "")
  267. return
  268. }
  269. random := rand.Intn(len(msgs))
  270. selectedMSG := msgs[random]
  271. outMSG := strings.Replace(selectedMSG.Message, "Quote:", "", -1)
  272. go PlayTTS(outMSG)
  273. TWITCH_CLIENT.Reply(selectedMSG.User.DisplayName+" '' "+outMSG+" '' - "+selectedMSG.Time.Format("Mon 02 Jan 15:04:05 MST 2006"), "")
  274. }
  275. func PlayMP3(tag string) {
  276. // cmd := exec.Command("ffplay", "-v", "0", "-nodisp", "-autoexit", "./mp3/"+tag+".mp3")
  277. // out, err := cmd.CombinedOutput()
  278. // if err != nil {
  279. // log.Println(err, string(out))
  280. // }
  281. f, err := os.Open("./mp3/" + tag + ".mp3")
  282. if err != nil {
  283. log.Fatal(err)
  284. }
  285. streamer, format, err := mp3.Decode(f)
  286. if err != nil {
  287. log.Fatal(err)
  288. }
  289. defer streamer.Close()
  290. speaker.Init(format.SampleRate, format.SampleRate.N(1000))
  291. done := make(chan bool)
  292. speaker.Play(beep.Seq(streamer, beep.Callback(func() {
  293. done <- true
  294. })))
  295. <-done
  296. }
  297. func Top10Command() {
  298. userList := GetTop10()
  299. outMsg := ""
  300. rank := 1
  301. for _, v := range userList {
  302. if v.Name == USERNAME {
  303. continue
  304. }
  305. outMsg += strconv.Itoa(rank) + "#" + v.DisplayName + "(" + strconv.Itoa(v.Points) + ") ......."
  306. rank++
  307. }
  308. TWITCH_CLIENT.Reply(outMsg, "")
  309. }
  310. func UserRollCommand(user *User, msg *tirc.PrivateMessage) (err error) {
  311. rollSplit := strings.Split(msg.Message, " ")
  312. if len(rollSplit) < 2 {
  313. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  314. return
  315. }
  316. rollAmount, err := strconv.Atoi(rollSplit[1])
  317. if err != nil {
  318. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  319. return
  320. }
  321. if user.Points < rollAmount || rollAmount < 0 {
  322. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you do not have enough points to gamba", "")
  323. return
  324. }
  325. lastRoll, ok := RollTimeout[msg.User.ID]
  326. if ok {
  327. seconds := time.Since(lastRoll).Seconds()
  328. if seconds < 20 {
  329. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, strconv.Itoa(int(20-seconds))+" seconds until you can roll again", "")
  330. return
  331. }
  332. }
  333. RollTimeout[msg.User.ID] = time.Now()
  334. random := rand.Intn(100) + 1
  335. if random < 30 {
  336. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  337. _ = IncrementUserPoints(user, -rollAmount)
  338. } else if random%2 == 0 {
  339. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins "+strconv.Itoa(rollAmount), "")
  340. _ = IncrementUserPoints(user, rollAmount)
  341. } else {
  342. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  343. _ = IncrementUserPoints(user, -rollAmount)
  344. }
  345. return
  346. }
  347. func CreateAPIClient() {
  348. twitchkey := os.Getenv("TWITCH_KEY")
  349. twitchkey = strings.Split(twitchkey, ":")[1]
  350. var err error
  351. HELIX_CLIENT, err = helix.NewClient(&helix.Options{
  352. AppAccessToken: twitchkey,
  353. ClientSecret: os.Getenv("CLIENT_SECRET"),
  354. ClientID: os.Getenv("CLIENT_ID"),
  355. })
  356. if err != nil {
  357. log.Println("UNABLE TO CREATE API CLIENT: ", err)
  358. }
  359. }
  360. func GetGlobalEmotes() {
  361. resp, err := HELIX_CLIENT.GetGlobalEmotes()
  362. if err != nil {
  363. log.Println("ERROR GETTING GLOBAL EMOTES:", err)
  364. return
  365. }
  366. for _, v := range resp.Data.Emotes {
  367. EmoteMap[v.ID] = v
  368. }
  369. }
  370. func GetChannelEmotes(BroadCasterID string) {
  371. resp, err := HELIX_CLIENT.GetChannelEmotes(&helix.GetChannelEmotesParams{
  372. BroadcasterID: BroadCasterID,
  373. })
  374. if err != nil {
  375. log.Println("ERROR GETTING CHANNEL EMOTES:", err)
  376. return
  377. }
  378. for _, v := range resp.Data.Emotes {
  379. EmoteMap[v.ID] = v
  380. }
  381. }
  382. // https://github.com/coqui-ai/TTS
  383. // https://github.com/coqui-ai/TTS
  384. // python3 TTS/server/server.py --vocoder_name vocoder_models/en/ljspeech/hifigan_v2
  385. // jenny is good too
  386. func CustomTTS(user User, msg tirc.PrivateMessage) {
  387. if user.Points < 50 {
  388. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "You need 200 points for TTS", "")
  389. return
  390. }
  391. splitTTS := strings.Split(msg.Message, "tts")
  392. if len(splitTTS) < 2 {
  393. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "TTS was badly formatted.. try: !tts [MSG]", "")
  394. return
  395. }
  396. err := IncrementUserPoints(&user, -50)
  397. if err != nil {
  398. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "We could not play your sound clip!", "")
  399. return
  400. }
  401. PlayTTS(splitTTS[1])
  402. }
  403. func PlayTTS(msg string) {
  404. defer func() {
  405. r := recover()
  406. if r != nil {
  407. log.Println(r, string(debug.Stack()))
  408. }
  409. }()
  410. // http://127.0.0.1:5002/api/tts?text=whats%20up&speaker_id=&style_wav=&language_id=
  411. params := url.Values{}
  412. params.Add("text", msg)
  413. httpClient := new(http.Client)
  414. // urlmsg := url.QueryEscape(msg)
  415. req, err := http.NewRequest("GET", "http://127.0.0.1:5002/api/tts?"+params.Encode(), nil)
  416. if err != nil {
  417. log.Println(err)
  418. return
  419. }
  420. resp, err := httpClient.Do(req)
  421. if err != nil {
  422. log.Println(err)
  423. return
  424. }
  425. bytes, err := io.ReadAll(resp.Body)
  426. if err != nil {
  427. log.Println(err)
  428. return
  429. }
  430. fileName := uuid.NewString() + "-" + strconv.Itoa(int(time.Now().UnixNano()))
  431. f, err := os.Create("./tts/" + fileName + ".wav")
  432. if err != nil {
  433. log.Println(err)
  434. return
  435. }
  436. defer f.Close()
  437. f2, err := os.Create("./tts/" + fileName + ".txt")
  438. if err != nil {
  439. log.Println(err)
  440. return
  441. }
  442. defer f2.Close()
  443. _, err = f.Write(bytes)
  444. if err != nil {
  445. log.Println(err)
  446. return
  447. }
  448. _, err = f2.Write([]byte(msg))
  449. if err != nil {
  450. log.Println(err)
  451. return
  452. }
  453. log.Println("BYTES:", len(bytes))
  454. PlayTTSFile(f.Name())
  455. }
  456. func PlayTTSFile(tag string) {
  457. f, err := os.Open(tag)
  458. if err != nil {
  459. log.Println("error opening tts file:", err)
  460. return
  461. }
  462. streamer, format, err := wav.Decode(f)
  463. if err != nil {
  464. log.Fatal(err)
  465. }
  466. defer streamer.Close()
  467. speaker.Init(format.SampleRate, format.SampleRate.N(time.Second))
  468. done := make(chan bool)
  469. speaker.Play(beep.Seq(streamer, beep.Callback(func() {
  470. done <- true
  471. })))
  472. <-done
  473. }