1+ --[[
2+ https://github.com/shockpast/twisaac
3+ Twitch, but inside of Isaac.
4+
5+ If you will use this module inside of other mod, please credit me :)
6+ ]]
7+
8+ --
9+ local socket = require (" socket" )
10+
11+ local helpers = include (" modules/helpers" )
12+
13+ --- @class Twitch
14+ local twitch = {}
15+ twitch .messages = {}
16+ twitch .badges = {}
17+
18+ local sfx = SFXManager ()
19+
20+ --- @param message string
21+ local function parse_message (message )
22+ local message_entity = {}
23+
24+ -- parse tags
25+ do
26+ local tags = {}
27+
28+ local tag_part , rest = message :match (" ^@(.-) (.+)" )
29+ if tag_part then
30+ for k , v in tag_part :gmatch (" ([^=;]+)=([^;]*)" ) do
31+ tags [k ] = v
32+ end
33+ end
34+
35+ message_entity .tags = tags
36+ message = rest or message
37+ end
38+
39+ -- parse badges
40+ do
41+ local badges = {}
42+
43+ for badge , version in message_entity .tags .badges :gmatch (" ([^/,]+)/([^/,]+)" ) do
44+ badges [badge ] = tonumber (version ) or version
45+ end
46+
47+ message_entity .badges = badges
48+ end
49+
50+ -- parse id
51+ do
52+ message_entity .id = message_entity .tags .id or " unknown"
53+ end
54+
55+ -- parse timestamp
56+ do
57+ local timestamp = tonumber (message_entity .tags [" tmi-sent-ts" ]) or 0
58+ message_entity .timestamp = os.date (" %H:%M:%S" , math.floor (timestamp / 1000 ))
59+ end
60+
61+ -- parse color
62+ do
63+ local color = message_entity .tags .color
64+ message_entity .color = color and helpers .hex_to_rgb (color ) or nil
65+ end
66+
67+ -- parse default data
68+ do
69+ local prefix , command , channel , text = message :match (" :(%S+) (%S+) (%S+) :(.*)" )
70+ message_entity .prefix = prefix
71+ message_entity .command = command
72+ message_entity .channel = channel
73+ message_entity .username = prefix :match (" ^(.-)!" )
74+ message_entity .text = text
75+ end
76+
77+ return message_entity
78+ end
79+
80+ --- @param channel string
81+ --- @param username string
82+ --- @param token string
83+ function twitch :connect (channel , username , token )
84+ self .socket = socket .tcp ()
85+ self .socket :settimeout (5 )
86+
87+ local ok , err = self .socket :connect (" irc.chat.twitch.tv" , 6667 )
88+ if not ok then
89+ print (string.format (" [twisaac] connection failed: %s" , tostring (err )))
90+ return nil
91+ end
92+
93+ self .channel = channel
94+ self .username = username
95+
96+ self .socket :send (string.format (" PASS oauth:%s\r\n " , token ))
97+ self .socket :send (string.format (" NICK %s\r\n " , username ))
98+ self .socket :send (string.format (" JOIN #%s\r\n " , channel ))
99+ self .socket :send (" CAP REQ :twitch.tv/membership twitch.tv/tags\r\n " ) -- chat metadata
100+
101+ self .messages [channel ] = {}
102+
103+ print (string.format (" [twisaac] connected to #%s" , channel ))
104+
105+ return twitch
106+ end
107+
108+ function twitch :receive ()
109+ while self .socket do
110+ self .socket :settimeout (0 )
111+ local message , err = self .socket :receive (" *l" )
112+
113+ if message ~= nil then
114+ if string.sub (message , 0 , # " PING" ) == " PING" then
115+ print (message )
116+ print (" [twisaac] ping-pong!" )
117+
118+ self .socket :send (" PONG :tmi.twitch.tv\r\n " )
119+ end
120+
121+ if string.sub (message , 0 , # " @badge-info" ) ~= " @badge-info" then return end
122+ if # self .messages > 5 then table.remove (self .messages , 1 ) end
123+
124+ sfx :Play (SoundEffect .SOUND_BOSS2_BUBBLES )
125+
126+ self .messages [# self .messages + 1 ] = parse_message (message )
127+ end
128+
129+ if err and err ~= " timeout" then
130+ print (" [twisaac] error: " .. tostring (err ))
131+
132+ self .socket :close ()
133+ self .socket = nil
134+ end
135+
136+ coroutine.yield ()
137+ end
138+ end
139+
140+ function twitch :reply (id , message )
141+ if id == nil or tonumber (id ) == nil then return end
142+ if # message <= 0 then return end
143+
144+ local message_entity = self .messages [tonumber (id )]
145+ if message_entity == nil then return end
146+
147+ self .socket :send (string.format (" @reply-parent-msg-id=%s PRIVMSG #%s :%s\r\n " , message_entity .id , self .channel , message ))
148+
149+ print (" [twisaac] > " .. message )
150+ end
151+
152+ function twitch :say (message )
153+ if # message <= 0 then return end
154+
155+ self .socket :send (string.format (" PRIVMSG #%s :%s\r\n " , self .channel , message ))
156+
157+ print (" [twisaac] > " .. message )
158+ end
159+
160+ --
161+ do
162+ local badge_names = { " broadcaster" , " moderator" , " turbo" , " verified" , " vip" }
163+
164+ for i = 16 , 5 * 16 , 16 do
165+ local sprite = Sprite ()
166+ sprite :Load (" gfx/ui/twisaac/badges.anm2" , true )
167+ sprite :Play (" Root" , true )
168+ sprite :SetFrame ((i / 16 ) - 1 )
169+
170+ twitch .badges [badge_names [i / 16 ]] = sprite
171+ end
172+ end
173+ --
174+
175+ return twitch
0 commit comments