twitch-client.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "log"
  8. "math/rand"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "runtime/debug"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "time"
  17. tirc "github.com/gempir/go-twitch-irc/v4"
  18. "github.com/google/uuid"
  19. "github.com/gopxl/beep"
  20. "github.com/gopxl/beep/mp3"
  21. "github.com/gopxl/beep/speaker"
  22. "github.com/gopxl/beep/wav"
  23. "github.com/nicklaw5/helix"
  24. )
  25. type IRC_CLIENT struct {
  26. Name string
  27. Client *tirc.Client
  28. // 1 == main channel
  29. // 2 == sub channel
  30. ChannelMap map[string]*IRC_CHANNEL
  31. }
  32. type IRC_CHANNEL struct {
  33. Name string
  34. BroadCasterID string
  35. Type int
  36. }
  37. func (C *IRC_CLIENT) ReplyToUser(userName string, msg string, channel string) {
  38. if channel == "" {
  39. C.Client.Say(C.Name, userName+" >> "+msg)
  40. } else {
  41. C.Client.Say(channel, userName+" >> "+msg)
  42. }
  43. }
  44. func (C *IRC_CLIENT) Reply(msg string, channel string) {
  45. if channel == "" {
  46. C.Client.Say(C.Name, msg)
  47. } else {
  48. C.Client.Say(channel, msg)
  49. }
  50. }
  51. func (C *IRC_CLIENT) GetAllChannelEmotes() {
  52. for _, v := range C.ChannelMap {
  53. log.Println("GETTING EMOTED FOR CHANNEL: ", v.Name)
  54. GetChannelEmotes(v.BroadCasterID)
  55. }
  56. }
  57. func NewUserSubReSubRaidMessage(user tirc.UserNoticeMessage) {
  58. TWITCH_CLIENT.ReplyToUser(user.User.DisplayName, "thank you!", "")
  59. }
  60. func USER_TEST(user tirc.UserStateMessage) {
  61. log.Println("State:", user)
  62. }
  63. func (C *IRC_CLIENT) POST_INFO() {
  64. defer func() {
  65. r := recover()
  66. if r != nil {
  67. log.Println(r, string(debug.Stack()))
  68. }
  69. monitor <- 1337
  70. }()
  71. time.Sleep(1 * time.Hour)
  72. if C.Client != nil {
  73. returnText, ok := TextCommands["!x"]
  74. if ok {
  75. TWITCH_CLIENT.Reply(returnText, "")
  76. }
  77. time.Sleep(30 * time.Second)
  78. returnText, ok = TextCommands["!discord"]
  79. if ok {
  80. TWITCH_CLIENT.Reply(returnText, "")
  81. }
  82. time.Sleep(30 * time.Second)
  83. returnText, ok = TextCommands["!vpn"]
  84. if ok {
  85. TWITCH_CLIENT.Reply(returnText, "")
  86. }
  87. }
  88. }
  89. func (C *IRC_CLIENT) Connect() {
  90. defer func() {
  91. r := recover()
  92. if r != nil {
  93. log.Println(r, string(debug.Stack()))
  94. }
  95. monitor <- 10
  96. }()
  97. log.Println("KEY LENGTH: ", len(os.Getenv("TWITCH_KEY")))
  98. C.Client = tirc.NewClient(C.Name, os.Getenv("TWITCH_KEY"))
  99. C.Client.SendPings = true
  100. C.Client.IdlePingInterval = time.Duration(time.Second * 10)
  101. C.Client.PongTimeout = time.Duration(time.Second * 60)
  102. C.Client.OnPrivateMessage(NewMessage)
  103. C.Client.OnUserNoticeMessage(NewUserSubReSubRaidMessage)
  104. // C.Client.OnUserStateMessage(USER_TEST)
  105. go func() {
  106. time.Sleep(3 * time.Second)
  107. C.JoinChannels()
  108. time.Sleep(3 * time.Second)
  109. go TWITCH_CLIENT.POST_INFO()
  110. TWITCH_CLIENT.Reply("BOT ONLINE!", "")
  111. }()
  112. err := C.Client.Connect()
  113. if err != nil {
  114. log.Println(err)
  115. }
  116. }
  117. func PlaceEventOnSoundQueue(t string, data string) {
  118. select {
  119. case SoundQueue <- SoundEvent{
  120. T: t,
  121. Data: data,
  122. }:
  123. default:
  124. fmt.Println("SOUND QUEUE FULL")
  125. fmt.Println("SOUND QUEUE FULL")
  126. fmt.Println("SOUND QUEUE FULL")
  127. fmt.Println("SOUND QUEUE FULL")
  128. }
  129. return
  130. }
  131. func (C *IRC_CLIENT) JoinChannels() {
  132. for _, v := range C.ChannelMap {
  133. log.Println("JOINING CHANNEL: ", v)
  134. C.Client.Join(v.Name)
  135. GetChannelEmotes(v.BroadCasterID)
  136. }
  137. }
  138. func NewMessage(msg tirc.PrivateMessage) {
  139. ProcessMessage(msg)
  140. }
  141. func ProcessMessage(msg tirc.PrivateMessage) {
  142. defer func() {
  143. if r := recover(); r != nil {
  144. log.Println(r)
  145. log.Println(string(debug.Stack()))
  146. }
  147. }()
  148. // lowerUser := strings.ToLower(msg.User.DisplayName)
  149. // fmt.Println("---------------------")
  150. // fmt.Println(msg.FirstMessage)
  151. // fmt.Println(msg.Action)
  152. // fmt.Println(msg.Bits)
  153. // fmt.Println(msg.Tags)
  154. // fmt.Println(msg.Type)
  155. // for _, v := range msg.Emotes {
  156. // fmt.Println(v.ID, v.Name, v.Count)
  157. // for _, vv := range v.Positions {
  158. // fmt.Println(vv.Start, vv.End)
  159. // }
  160. // }
  161. // fmt.Println(msg.CustomRewardID)
  162. // fmt.Println(msg.User.ID)
  163. // fmt.Println(msg.User.Name)
  164. // fmt.Println(lowerUser)
  165. // fmt.Println(msg.Channel, msg.Message)
  166. // fmt.Println("---------------------")
  167. U, err := FindOrUpsertUser(&msg.User)
  168. if err != nil {
  169. U.DisplayName = msg.User.DisplayName
  170. U.Name = msg.User.Name
  171. U.ID = msg.User.ID
  172. U.Color = msg.User.Color
  173. U.Badges = msg.User.Badges
  174. err = IncrementUserPoints(U, 500)
  175. if err == nil {
  176. U.Points = 100
  177. }
  178. } else {
  179. _ = IncrementUserPoints(U, 5)
  180. }
  181. log.Println("CUSTOM REWARD ID:", msg.CustomRewardID)
  182. returnText, ok := TextCommands[msg.Message]
  183. if ok {
  184. TWITCH_CLIENT.Reply(returnText, "")
  185. return
  186. }
  187. if CheckCustomReward(U, msg) {
  188. return
  189. }
  190. mp3, ok := MP3Map[msg.CustomRewardID]
  191. if ok {
  192. go PlaceEventOnSoundQueue("mp3", mp3)
  193. // go PlayMP3(mp3)
  194. return
  195. }
  196. if strings.Contains(msg.Message, "!time") {
  197. TWITCH_CLIENT.Reply(time.Now().Format(time.RFC3339), "")
  198. return
  199. }
  200. if strings.Contains(msg.Message, "!bot") {
  201. if msg.User.DisplayName == "KEYB1ND_" {
  202. go askTheBot(msg.Message)
  203. }
  204. return
  205. }
  206. // banword := ""
  207. // isBanned := false
  208. // if strings.Contains(msg.Message, " tailwind ") || strings.HasPrefix(msg.Message, "tailwind") {
  209. // banword = "tailwind"
  210. // isBanned = true
  211. // }
  212. // if strings.Contains(msg.Message, " rust ") || strings.HasPrefix(msg.Message, "rust") {
  213. // banword = "rust"
  214. // isBanned = true
  215. // }
  216. // if isBanned {
  217. // TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "You said a naughty word: "+banword+" -1000 points for you.", "")
  218. // _ = IncrementUserPoints(U, -1000)
  219. // return
  220. // }
  221. // if strings.Contains(msg.Message, "!tts") {
  222. // go CustomTTS(*U, msg)
  223. // return
  224. // }
  225. if strings.Contains(msg.Message, "!quote help") || msg.Message == "!quote" {
  226. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "To make a quote use 'Quote:' before your sentence...... example: 'Quote: This is a quote!' ", "")
  227. return
  228. }
  229. if strings.Contains(msg.Message, "!points") {
  230. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you have >> "+strconv.Itoa(U.Points)+" Points", "")
  231. return
  232. }
  233. if strings.Contains(msg.Message, "!quote") {
  234. RandQuote(&msg)
  235. return
  236. }
  237. if strings.Contains(msg.Message, "!top10") {
  238. Top10Command()
  239. return
  240. }
  241. if strings.Contains(msg.Message, "!roll") {
  242. err := UserRollCommand(U, &msg)
  243. if err != nil {
  244. log.Println("Error While Rolling: ", err)
  245. return
  246. }
  247. return
  248. }
  249. _ = SaveMessage(&msg)
  250. // log.Println("USER FROM DB: ", U)
  251. }
  252. var (
  253. RollTimeout = make(map[string]time.Time)
  254. RollLock sync.Mutex
  255. )
  256. func RandQuote(msg *tirc.PrivateMessage) {
  257. splitMatch := strings.Split(msg.Message, " ")
  258. if len(splitMatch) < 2 || len(splitMatch) > 2 {
  259. return
  260. }
  261. userToLower := strings.ToLower(splitMatch[1])
  262. userToLower = strings.Replace(userToLower, "@", "", -1)
  263. msgs, err := FindUserMessagesFromMatch(userToLower, "Quote:")
  264. if err != nil {
  265. return
  266. }
  267. if len(msgs) == 0 {
  268. TWITCH_CLIENT.Reply("No qoutes found for "+userToLower, "")
  269. return
  270. }
  271. random := rand.Intn(len(msgs))
  272. selectedMSG := msgs[random]
  273. outMSG := strings.Replace(selectedMSG.Message, "Quote:", "", -1)
  274. go PlayTTS(outMSG)
  275. TWITCH_CLIENT.Reply(selectedMSG.User.DisplayName+" '' "+outMSG+" '' - "+selectedMSG.Time.Format("Mon 02 Jan 15:04:05 MST 2006"), "")
  276. }
  277. func PlayMP3(tag string) {
  278. // cmd := exec.Command("ffplay", "-v", "0", "-nodisp", "-autoexit", "./mp3/"+tag+".mp3")
  279. // out, err := cmd.CombinedOutput()
  280. // if err != nil {
  281. // log.Println(err, string(out))
  282. // }
  283. f, err := os.Open("./mp3/" + tag + ".mp3")
  284. if err != nil {
  285. log.Fatal(err)
  286. }
  287. streamer, format, err := mp3.Decode(f)
  288. if err != nil {
  289. log.Fatal(err)
  290. }
  291. defer streamer.Close()
  292. speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
  293. done := make(chan bool)
  294. speaker.Play(beep.Seq(streamer, beep.Callback(func() {
  295. done <- true
  296. })))
  297. <-done
  298. }
  299. func Top10Command() {
  300. userList := GetTop10()
  301. outMsg := ""
  302. rank := 1
  303. for _, v := range userList {
  304. if v.Name == USERNAME {
  305. continue
  306. }
  307. outMsg += strconv.Itoa(rank) + "#" + v.DisplayName + "(" + strconv.Itoa(v.Points) + ") ......."
  308. rank++
  309. }
  310. TWITCH_CLIENT.Reply(outMsg, "")
  311. }
  312. func UserRollCommand(user *User, msg *tirc.PrivateMessage) (err error) {
  313. rollSplit := strings.Split(msg.Message, " ")
  314. if len(rollSplit) < 2 {
  315. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  316. return
  317. }
  318. rollAmount, err := strconv.Atoi(rollSplit[1])
  319. if err != nil {
  320. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  321. return
  322. }
  323. if user.Points < rollAmount || rollAmount < 0 {
  324. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you do not have enough points to gamba", "")
  325. return
  326. }
  327. lastRoll, ok := RollTimeout[msg.User.ID]
  328. if ok {
  329. seconds := time.Since(lastRoll).Seconds()
  330. if seconds < 20 {
  331. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, strconv.Itoa(int(20-seconds))+" seconds until you can roll again", "")
  332. return
  333. }
  334. }
  335. RollTimeout[msg.User.ID] = time.Now()
  336. random := rand.Intn(100) + 1
  337. if random < 30 {
  338. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  339. _ = IncrementUserPoints(user, -rollAmount)
  340. } else if random%2 == 0 {
  341. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins "+strconv.Itoa(rollAmount), "")
  342. _ = IncrementUserPoints(user, rollAmount)
  343. } else {
  344. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  345. _ = IncrementUserPoints(user, -rollAmount)
  346. }
  347. return
  348. }
  349. func CreateAPIClient() {
  350. twitchkey := os.Getenv("TWITCH_KEY")
  351. twitchkey = strings.Split(twitchkey, ":")[1]
  352. var err error
  353. HELIX_CLIENT, err = helix.NewClient(&helix.Options{
  354. AppAccessToken: twitchkey,
  355. ClientSecret: os.Getenv("CLIENT_SECRET"),
  356. ClientID: os.Getenv("CLIENT_ID"),
  357. })
  358. if err != nil {
  359. log.Println("UNABLE TO CREATE API CLIENT: ", err)
  360. }
  361. }
  362. func GetGlobalEmotes() {
  363. resp, err := HELIX_CLIENT.GetGlobalEmotes()
  364. if err != nil {
  365. log.Println("ERROR GETTING GLOBAL EMOTES:", err)
  366. return
  367. }
  368. for _, v := range resp.Data.Emotes {
  369. EmoteMap[v.ID] = v
  370. }
  371. }
  372. func GetChannelEmotes(BroadCasterID string) {
  373. resp, err := HELIX_CLIENT.GetChannelEmotes(&helix.GetChannelEmotesParams{
  374. BroadcasterID: BroadCasterID,
  375. })
  376. if err != nil {
  377. log.Println("ERROR GETTING CHANNEL EMOTES:", err)
  378. return
  379. }
  380. for _, v := range resp.Data.Emotes {
  381. EmoteMap[v.ID] = v
  382. }
  383. }
  384. // https://github.com/coqui-ai/TTS
  385. // https://github.com/coqui-ai/TTS
  386. // python3 TTS/server/server.py --vocoder_name vocoder_models/en/ljspeech/hifigan_v2
  387. // jenny is good too
  388. func CustomTTS(user User, msg tirc.PrivateMessage) {
  389. if user.Points < 80 {
  390. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "You need 200 points for TTS", "")
  391. return
  392. }
  393. splitTTS := strings.Split(msg.Message, "tts")
  394. if len(splitTTS) < 2 {
  395. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "TTS was badly formatted.. try: !tts [MSG]", "")
  396. return
  397. }
  398. err := IncrementUserPoints(&user, -80)
  399. if err != nil {
  400. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "We could not play your sound clip!", "")
  401. return
  402. }
  403. PlayTTS(splitTTS[1])
  404. }
  405. func PlayTTS(msg string) {
  406. defer func() {
  407. r := recover()
  408. if r != nil {
  409. log.Println(r, string(debug.Stack()))
  410. }
  411. }()
  412. // http://127.0.0.1:5002/api/tts?text=whats%20up&speaker_id=&style_wav=&language_id=
  413. params := url.Values{}
  414. params.Add("text", msg)
  415. httpClient := new(http.Client)
  416. // urlmsg := url.QueryEscape(msg)
  417. req, err := http.NewRequest("GET", "http://127.0.0.1:5002/api/tts?"+params.Encode(), nil)
  418. if err != nil {
  419. log.Println(err)
  420. return
  421. }
  422. resp, err := httpClient.Do(req)
  423. if err != nil {
  424. log.Println(err)
  425. return
  426. }
  427. bytes, err := io.ReadAll(resp.Body)
  428. if err != nil {
  429. log.Println(err)
  430. return
  431. }
  432. fileName := uuid.NewString() + "-" + strconv.Itoa(int(time.Now().UnixNano()))
  433. f, err := os.Create("./tts/" + fileName + ".wav")
  434. if err != nil {
  435. log.Println(err)
  436. return
  437. }
  438. defer f.Close()
  439. f2, err := os.Create("./tts/" + fileName + ".txt")
  440. if err != nil {
  441. log.Println(err)
  442. return
  443. }
  444. defer f2.Close()
  445. _, err = f.Write(bytes)
  446. if err != nil {
  447. log.Println(err)
  448. return
  449. }
  450. _, err = f2.Write([]byte(msg))
  451. if err != nil {
  452. log.Println(err)
  453. return
  454. }
  455. log.Println("BYTES:", len(bytes))
  456. PlayTTSFile(f.Name())
  457. }
  458. func PlayTTSFile(tag string) {
  459. f, err := os.Open(tag)
  460. if err != nil {
  461. log.Println("error opening tts file:", err)
  462. return
  463. }
  464. streamer, format, err := wav.Decode(f)
  465. if err != nil {
  466. log.Fatal(err)
  467. }
  468. defer streamer.Close()
  469. speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
  470. done := make(chan bool)
  471. speaker.Play(beep.Seq(streamer, beep.Callback(func() {
  472. done <- true
  473. })))
  474. <-done
  475. }
  476. type BotModel struct {
  477. Model string `json:"model"`
  478. Prompt string `json:"prompt"`
  479. Stream bool `json:"stream"`
  480. Options *BotOpts `json:"options"`
  481. MaxTokens int `json:"max_tokens"`
  482. Messages []*BotMSG `json:"messages"`
  483. }
  484. type BotMSG struct {
  485. Role string `json:"role"`
  486. Content string `json:"content"`
  487. }
  488. type BotOpts struct {
  489. Temperature float64 `json:"temperature"`
  490. // TopP float64 `json:"top_p"`
  491. // TopK int `json:"top_k"`
  492. // MinP float64 `json:"min_p"`
  493. // NumPredict int `json:"num_predict"`
  494. }
  495. type BotResp struct {
  496. Model string `json:"model"`
  497. CreatedAt time.Time `json:"created_at"`
  498. Response string `json:"response"`
  499. Done bool `json:"done"`
  500. Context []int `json:"context"`
  501. TotalDuration int64 `json:"total_duration"`
  502. LoadDuration int `json:"load_duration"`
  503. PromptEvalCount int `json:"prompt_eval_count"`
  504. PromptEvalDuration int `json:"prompt_eval_duration"`
  505. EvalCount int `json:"eval_count"`
  506. EvalDuration int64 `json:"eval_duration"`
  507. }
  508. type BotRespChat struct {
  509. Message *BotMSG `json:"message"`
  510. }
  511. var BaseMSG = make([]*BotMSG, 0)
  512. func INIT_MSG() {
  513. BaseMSG = append(BaseMSG, &BotMSG{
  514. Role: "user",
  515. Content: "I want you to refer to me as Key from now on.",
  516. })
  517. BaseMSG = append(BaseMSG, &BotMSG{
  518. Role: "assistant",
  519. Content: "I will refer to you as key from now on.",
  520. })
  521. BaseMSG = append(BaseMSG, &BotMSG{
  522. Role: "user",
  523. Content: "I want you to pretend to be evil, be subtle about it but try to be a bit snarky with your replies. Also, try to make fun of me in subtle ways when possible, don't over do it though.",
  524. })
  525. BaseMSG = append(BaseMSG, &BotMSG{
  526. Role: "assistant",
  527. Content: "I will do that",
  528. })
  529. BaseMSG = append(BaseMSG, &BotMSG{
  530. Role: "user",
  531. Content: "Please try to keep your answers short.",
  532. })
  533. BaseMSG = append(BaseMSG, &BotMSG{
  534. Role: "assistant",
  535. Content: "I will do that",
  536. })
  537. }
  538. func askTheBot(msg string) {
  539. ms := strings.Split(msg, " ")
  540. m := strings.Join(ms[1:], " ")
  541. BaseMSG = append(BaseMSG, &BotMSG{
  542. Role: "user",
  543. Content: m,
  544. })
  545. BM := new(BotModel)
  546. BM.Model = "mannix/llama3.1-8b-abliterated"
  547. // BM.Prompt = m
  548. BM.Messages = BaseMSG
  549. BM.Stream = false
  550. BM.Options = new(BotOpts)
  551. BM.Options.Temperature = 0.8
  552. BM.MaxTokens = 50
  553. ob, err := json.Marshal(BM)
  554. httpClient := new(http.Client)
  555. buff := bytes.NewBuffer(ob)
  556. // urlmsg := url.QueryEscape(msg)
  557. // req, err := http.NewRequest("POST", "http://localhost:11434/api/generate", buff)
  558. req, err := http.NewRequest("POST", "http://localhost:11434/api/chat", buff)
  559. if err != nil {
  560. log.Println(err)
  561. return
  562. }
  563. resp, err := httpClient.Do(req)
  564. if err != nil {
  565. log.Println(err)
  566. return
  567. }
  568. bytes, err := io.ReadAll(resp.Body)
  569. if err != nil {
  570. log.Println(err)
  571. return
  572. }
  573. fmt.Println(string(bytes))
  574. BR := new(BotRespChat)
  575. err = json.Unmarshal(bytes, BR)
  576. if err != nil {
  577. fmt.Println("lama resp err:", err)
  578. return
  579. }
  580. BaseMSG = append(BaseMSG, BR.Message)
  581. fmt.Println(BR.Message.Content)
  582. PlayTTS(BR.Message.Content)
  583. }