twitch-client.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. package main
  2. import (
  3. "io"
  4. "log"
  5. "math/rand"
  6. "net/http"
  7. "net/url"
  8. "os"
  9. "os/exec"
  10. "runtime/debug"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. tirc "github.com/gempir/go-twitch-irc/v4"
  16. "github.com/nicklaw5/helix"
  17. )
  18. type IRC_CLIENT struct {
  19. Name string
  20. Client *tirc.Client
  21. // 1 == main channel
  22. // 2 == sub channel
  23. ChannelMap map[string]*IRC_CHANNEL
  24. }
  25. type IRC_CHANNEL struct {
  26. Name string
  27. BroadCasterID string
  28. Type int
  29. }
  30. func (C *IRC_CLIENT) ReplyToUser(userName string, msg string, channel string) {
  31. if channel == "" {
  32. C.Client.Say(C.Name, userName+" >> "+msg)
  33. } else {
  34. C.Client.Say(channel, userName+" >> "+msg)
  35. }
  36. }
  37. func (C *IRC_CLIENT) Reply(msg string, channel string) {
  38. if channel == "" {
  39. C.Client.Say(C.Name, msg)
  40. } else {
  41. C.Client.Say(channel, msg)
  42. }
  43. }
  44. func (C *IRC_CLIENT) GetAllChannelEmotes() {
  45. for _, v := range C.ChannelMap {
  46. log.Println("GETTING EMOTED FOR CHANNEL: ", v.Name)
  47. GetChannelEmotes(v.BroadCasterID)
  48. }
  49. }
  50. func NewUserSubReSubRaidMessage(user tirc.UserNoticeMessage) {
  51. TWITCH_CLIENT.ReplyToUser(user.User.DisplayName, "thank you!", "")
  52. }
  53. func USER_TEST(user tirc.UserStateMessage) {
  54. log.Println("State:", user)
  55. }
  56. func (C *IRC_CLIENT) Connect() {
  57. defer func() {
  58. r := recover()
  59. if r != nil {
  60. log.Println(r, string(debug.Stack()))
  61. }
  62. monitor <- 10
  63. }()
  64. log.Println("KEY LENGTH: ", len(os.Getenv("TWITCH_KEY")))
  65. C.Client = tirc.NewClient(C.Name, os.Getenv("TWITCH_KEY"))
  66. C.Client.SendPings = true
  67. C.Client.IdlePingInterval = time.Duration(time.Second * 10)
  68. C.Client.PongTimeout = time.Duration(time.Second * 60)
  69. C.Client.OnPrivateMessage(NewMessage)
  70. C.Client.OnUserNoticeMessage(NewUserSubReSubRaidMessage)
  71. // C.Client.OnUserStateMessage(USER_TEST)
  72. go func() {
  73. time.Sleep(3 * time.Second)
  74. C.JoinChannels()
  75. }()
  76. err := C.Client.Connect()
  77. if err != nil {
  78. log.Println(err)
  79. }
  80. }
  81. func (C *IRC_CLIENT) JoinChannels() {
  82. for _, v := range C.ChannelMap {
  83. log.Println("JOINING CHANNEL: ", v)
  84. C.Client.Join(v.Name)
  85. GetChannelEmotes(v.BroadCasterID)
  86. }
  87. }
  88. func NewMessage(msg tirc.PrivateMessage) {
  89. ProcessMessage(msg)
  90. }
  91. func ProcessMessage(msg tirc.PrivateMessage) {
  92. defer func() {
  93. if r := recover(); r != nil {
  94. log.Println(r)
  95. log.Println(string(debug.Stack()))
  96. }
  97. }()
  98. // lowerUser := strings.ToLower(msg.User.DisplayName)
  99. // fmt.Println("---------------------")
  100. // fmt.Println(msg.FirstMessage)
  101. // fmt.Println(msg.Action)
  102. // fmt.Println(msg.Bits)
  103. // fmt.Println(msg.Tags)
  104. // fmt.Println(msg.Type)
  105. // for _, v := range msg.Emotes {
  106. // fmt.Println(v.ID, v.Name, v.Count)
  107. // for _, vv := range v.Positions {
  108. // fmt.Println(vv.Start, vv.End)
  109. // }
  110. // }
  111. // fmt.Println(msg.CustomRewardID)
  112. // fmt.Println(msg.User.ID)
  113. // fmt.Println(msg.User.Name)
  114. // fmt.Println(lowerUser)
  115. // fmt.Println(msg.Channel, msg.Message)
  116. // fmt.Println("---------------------")
  117. U, err := FindOrUpsertUser(&msg.User)
  118. if err != nil {
  119. U.DisplayName = msg.User.DisplayName
  120. U.Name = msg.User.Name
  121. U.ID = msg.User.ID
  122. U.Color = msg.User.Color
  123. U.Badges = msg.User.Badges
  124. err = IncrementUserPoints(U, 100)
  125. if err == nil {
  126. U.Points = 100
  127. }
  128. } else {
  129. _ = IncrementUserPoints(U, 1)
  130. }
  131. log.Println("CUSTOM REWARD ID:", msg.CustomRewardID)
  132. returnText, ok := TextCommands[msg.Message]
  133. if ok {
  134. TWITCH_CLIENT.Reply(returnText, "")
  135. return
  136. }
  137. if CheckCustomReward(U, msg) {
  138. return
  139. }
  140. mp3, ok := MP3Map[msg.CustomRewardID]
  141. if ok {
  142. go PlaySound(mp3)
  143. return
  144. }
  145. if strings.Contains(msg.Message, "!time") {
  146. TWITCH_CLIENT.Reply(time.Now().Format(time.RFC3339), "")
  147. return
  148. }
  149. // if strings.Contains(msg.Message, "!tts") {
  150. // go CustomTTS(*U, msg)
  151. // return
  152. // }
  153. if strings.Contains(msg.Message, "!quote help") || msg.Message == "!quote" {
  154. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "To make a quote use 'Quote:' before your sentence...... example: 'Quote: This is a quote!' ", "")
  155. return
  156. }
  157. if strings.Contains(msg.Message, "!points") {
  158. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you have >> "+strconv.Itoa(U.Points)+" Points", "")
  159. return
  160. }
  161. if strings.Contains(msg.Message, "!quote") {
  162. RandQuote(&msg)
  163. return
  164. }
  165. if strings.Contains(msg.Message, "!top10") {
  166. Top10Command()
  167. return
  168. }
  169. if strings.Contains(msg.Message, "!roll") {
  170. err := UserRollCommand(U, &msg)
  171. if err != nil {
  172. log.Println("Error While Rolling: ", err)
  173. return
  174. }
  175. return
  176. }
  177. _ = SaveMessage(&msg)
  178. // log.Println("USER FROM DB: ", U)
  179. }
  180. var (
  181. RollTimeout = make(map[string]time.Time)
  182. RollLock sync.Mutex
  183. )
  184. func RandQuote(msg *tirc.PrivateMessage) {
  185. splitMatch := strings.Split(msg.Message, " ")
  186. if len(splitMatch) < 2 || len(splitMatch) > 2 {
  187. return
  188. }
  189. userToLower := strings.ToLower(splitMatch[1])
  190. userToLower = strings.Replace(userToLower, "@", "", -1)
  191. msgs, err := FindUserMessagesFromMatch(userToLower, "Quote:")
  192. if err != nil {
  193. return
  194. }
  195. if len(msgs) == 0 {
  196. TWITCH_CLIENT.Reply("No qoutes found for "+userToLower, "")
  197. return
  198. }
  199. random := rand.Intn(len(msgs))
  200. selectedMSG := msgs[random]
  201. outMSG := strings.Replace(selectedMSG.Message, "Quote:", "", -1)
  202. go PlayTTS(outMSG)
  203. TWITCH_CLIENT.Reply(selectedMSG.User.DisplayName+" '' "+outMSG+" '' - "+selectedMSG.Time.Format("Mon 02 Jan 15:04:05 MST 2006"), "")
  204. }
  205. func PlaySound(tag string) {
  206. cmd := exec.Command("ffplay", "-v", "0", "-nodisp", "-autoexit", "./mp3/"+tag+".mp3")
  207. out, err := cmd.CombinedOutput()
  208. if err != nil {
  209. log.Println(err, string(out))
  210. }
  211. }
  212. func Top10Command() {
  213. userList := GetTop10()
  214. outMsg := ""
  215. rank := 1
  216. for _, v := range userList {
  217. if v.Name == USERNAME {
  218. continue
  219. }
  220. outMsg += strconv.Itoa(rank) + "#" + v.DisplayName + "(" + strconv.Itoa(v.Points) + ") ......."
  221. rank++
  222. }
  223. TWITCH_CLIENT.Reply(outMsg, "")
  224. }
  225. func UserRollCommand(user *User, msg *tirc.PrivateMessage) (err error) {
  226. rollSplit := strings.Split(msg.Message, " ")
  227. if len(rollSplit) < 2 {
  228. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  229. return
  230. }
  231. rollAmount, err := strconv.Atoi(rollSplit[1])
  232. if err != nil {
  233. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "Invalid roll format", "")
  234. return
  235. }
  236. if user.Points < rollAmount {
  237. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, "you do not have enough points to gamba", "")
  238. return
  239. }
  240. lastRoll, ok := RollTimeout[msg.User.ID]
  241. if ok {
  242. seconds := time.Since(lastRoll).Seconds()
  243. if seconds < 20 {
  244. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, strconv.Itoa(int(20-seconds))+" seconds until you can roll again", "")
  245. return
  246. }
  247. }
  248. RollTimeout[msg.User.ID] = time.Now()
  249. random := rand.Intn(100) + 1
  250. if random < 30 {
  251. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  252. _ = IncrementUserPoints(user, -rollAmount)
  253. } else if random%2 == 0 {
  254. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins "+strconv.Itoa(rollAmount), "")
  255. _ = IncrementUserPoints(user, rollAmount)
  256. } else {
  257. TWITCH_CLIENT.ReplyToUser(msg.User.DisplayName, " rolls "+strconv.Itoa(random)+" and wins nothing", "")
  258. _ = IncrementUserPoints(user, -rollAmount)
  259. }
  260. return
  261. }
  262. func CreateAPIClient() {
  263. twitchkey := os.Getenv("TWITCH_KEY")
  264. twitchkey = strings.Split(twitchkey, ":")[1]
  265. var err error
  266. HELIX_CLIENT, err = helix.NewClient(&helix.Options{
  267. AppAccessToken: twitchkey,
  268. ClientSecret: os.Getenv("CLIENT_SECRET"),
  269. ClientID: os.Getenv("CLIENT_ID"),
  270. })
  271. if err != nil {
  272. log.Println("UNABLE TO CREATE API CLIENT: ", err)
  273. }
  274. }
  275. func GetGlobalEmotes() {
  276. resp, err := HELIX_CLIENT.GetGlobalEmotes()
  277. if err != nil {
  278. log.Println("ERROR GETTING GLOBAL EMOTES:", err)
  279. return
  280. }
  281. for _, v := range resp.Data.Emotes {
  282. EmoteMap[v.ID] = v
  283. }
  284. }
  285. func GetChannelEmotes(BroadCasterID string) {
  286. resp, err := HELIX_CLIENT.GetChannelEmotes(&helix.GetChannelEmotesParams{
  287. BroadcasterID: BroadCasterID,
  288. })
  289. if err != nil {
  290. log.Println("ERROR GETTING CHANNEL EMOTES:", err)
  291. return
  292. }
  293. for _, v := range resp.Data.Emotes {
  294. EmoteMap[v.ID] = v
  295. }
  296. }
  297. // https://github.com/coqui-ai/TTS
  298. // https://github.com/coqui-ai/TTS
  299. // python3 TTS/server/server.py --vocoder_name vocoder_models/en/ljspeech/hifigan_v2
  300. // jenny is good too
  301. func CustomTTS(user User, msg tirc.PrivateMessage) {
  302. if user.Points < 80 {
  303. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "You need 200 points for TTS", "")
  304. return
  305. }
  306. splitTTS := strings.Split(msg.Message, "tts")
  307. if len(splitTTS) < 2 {
  308. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "TTS was badly formatted.. try: !tts [MSG]", "")
  309. return
  310. }
  311. err := IncrementUserPoints(&user, -80)
  312. if err != nil {
  313. TWITCH_CLIENT.ReplyToUser(user.DisplayName, "We could not play your sound clip!", "")
  314. return
  315. }
  316. PlayTTS(splitTTS[1])
  317. }
  318. func PlayTTS(msg string) {
  319. defer func() {
  320. r := recover()
  321. if r != nil {
  322. log.Println(r, string(debug.Stack()))
  323. }
  324. }()
  325. // http://127.0.0.1:5002/api/tts?text=whats%20up&speaker_id=&style_wav=&language_id=
  326. params := url.Values{}
  327. params.Add("text", msg)
  328. fileName := ""
  329. if len(msg) < 25 {
  330. fileName = msg
  331. } else {
  332. fileName = msg[0:20]
  333. }
  334. httpClient := new(http.Client)
  335. // urlmsg := url.QueryEscape(msg)
  336. req, err := http.NewRequest("GET", "http://127.0.0.1:5002/api/tts?"+params.Encode(), nil)
  337. if err != nil {
  338. log.Println(err)
  339. return
  340. }
  341. resp, err := httpClient.Do(req)
  342. if err != nil {
  343. log.Println(err)
  344. return
  345. }
  346. bytes, err := io.ReadAll(resp.Body)
  347. if err != nil {
  348. log.Println(err)
  349. return
  350. }
  351. f, err := os.Create("./tts/" + fileName + "-" + strconv.Itoa(int(time.Now().UnixNano())) + ".wav")
  352. if err != nil {
  353. log.Println(err)
  354. return
  355. }
  356. _, err = f.Write(bytes)
  357. if err != nil {
  358. log.Println(err)
  359. return
  360. }
  361. log.Println("BYTES:", len(bytes))
  362. cmd := exec.Command("ffplay", "-v", "0", "-nodisp", "-autoexit", f.Name())
  363. out, err := cmd.CombinedOutput()
  364. if err != nil {
  365. log.Println(err, string(out))
  366. }
  367. }