1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
module Fig.Monitor.Twitch.Chatbot
( twitchChatbot
) where
import Fig.Prelude
import qualified Data.Text as Text
import qualified Data.Map.Strict as Map
import qualified Wuss as WS
import qualified Network.WebSockets.Connection as WS
import Fig.Bus.Binary.Client
import Fig.Monitor.Twitch.Utils
twitchChatbot :: Config -> (Text, Text) -> IO ()
twitchChatbot cfg busAddr = do
log "Starting chatbot"
case headMay cfg.monitor of
Nothing -> pure ()
Just chan -> WS.runSecureClient "irc-ws.chat.twitch.tv" 443 "/" \conn -> do
WS.sendTextData conn $ "PASS oauth:" <> cfg.userToken
WS.sendTextData conn ("NICK lcolonq" :: Text)
WS.sendTextData conn ("CAP REQ :twitch.tv/commands twitch.tv/tags" :: Text)
WS.sendTextData conn $ "JOIN #" <> chan
-- WS.sendTextData conn ("PRIVMSG #lcolonq :test the other direction" :: Text)
busClient busAddr
(\cmds -> do
cmds.subscribe "fig monitor twitch chat outgoing"
forever do
resp <- WS.receiveData conn
forM (Text.lines resp) $ \line -> do
let msg = parseIRCMessage line
case msg.command of
"PING" -> do
log "Received PING, sending PONG"
WS.sendTextData conn $ "PONG :" <> mconcat msg.params
"CLEARCHAT" -> do
log "Received CLEARCHAT"
cmds.publish "fig monitor twitch chat clear-chat" . encodeUtf8 $ Text.unwords msg.params
"NOTICE" -> do
log "Received NOTICE"
cmds.publish "fig monitor twitch chat notice" . encodeUtf8 $ Text.unwords msg.params
"USERNOTICE" -> do
log "Received USERNOTICE"
cmds.publish "fig monitor twitch chat user-notice" . encodeUtf8 $ Text.unwords msg.params
"PRIVMSG"
| Just displaynm <- Map.lookup "display-name" msg.tags
, Just pfx <- msg.prefix
, Nothing <- Map.lookup "custom-reward-id" msg.tags -> do
log $ "Received chat message from: " <> displaynm
cmds.publish "fig monitor twitch chat incoming" . encodeUtf8 . Text.unwords $
[ mconcat [displaynm, "\t", fst $ Text.breakOn "!" pfx]
, Text.intercalate "\n" $ (\(key, v) -> key <> "\t" <> v) <$> Map.toList msg.tags
] <> drop 1 msg.params
_ -> pure ()
)
(\_cmds ev d -> do
case ev of
"fig monitor twitch chat outgoing" -> do
let msg = decodeUtf8 d
log $ "Sending chat message: " <> msg
WS.sendTextData conn $ mconcat
[ "PRIVMSG #"
, chan
, " :"
, msg
]
_else -> log $ "Invalid incoming event: " <> tshow ev
)
(pure ())
data IRCMessage = IRCMessage
{ tags :: !(Map.Map Text Text)
, prefix :: !(Maybe Text)
, command :: !Text
, params :: ![Text]
} deriving (Show, Eq, Ord)
parseIRCMessage :: Text -> IRCMessage
parseIRCMessage (Text.strip -> fullrest) =
let
(tags, tagsrest) =
if Text.head fullrest == '@'
then
let (tstr, rest) = Text.breakOn " " fullrest
in ( Map.fromList $ second (Text.drop 1) . Text.breakOn "=" <$> Text.splitOn ";" (Text.drop 1 tstr)
, Text.strip rest
)
else (Map.empty, fullrest)
(prefix, prefixrest) =
if Text.head tagsrest == ':'
then
let (pstr, rest) = Text.breakOn " " tagsrest
in ( Just $ Text.drop 1 pstr
, Text.strip rest
)
else (Nothing, tagsrest)
(command, cmdrest) = Text.breakOn " " prefixrest
params = case Text.breakOn ":" $ Text.strip cmdrest of
(Text.strip -> "", rest) -> [rest]
(ps, rest) -> Text.splitOn " " (Text.strip ps) <> [Text.drop 1 rest]
in IRCMessage{..}
|