kevin 0.8.2 → 0.9.0
raw patch · 17 files changed
+598/−797 lines, 17 files
Files
- Kevin/Base.hs +15/−19
- Kevin/Chatrooms.hs +11/−20
- Kevin/Damn/Packet.hs +23/−32
- Kevin/Damn/Protocol.hs +130/−178
- Kevin/Damn/Protocol/Send.hs +50/−69
- Kevin/IRC/Packet.hs +20/−26
- Kevin/IRC/Protocol.hs +88/−123
- Kevin/IRC/Protocol/Send.hs +64/−106
- Kevin/Protocol.hs +33/−41
- Kevin/Settings.hs +3/−3
- Kevin/Types.hs +25/−26
- Kevin/Util/Entity.hs +22/−31
- Kevin/Util/Logger.hs +1/−2
- Kevin/Util/Tablump.hs +46/−52
- Kevin/Util/Token.hs +53/−46
- Main.hs +10/−19
- kevin.cabal +4/−4
Kevin/Base.hs view
@@ -36,16 +36,15 @@ import System.IO.Error runPrinter :: Chan T.Text -> Handle -> IO ()-runPrinter ch h = void . forkIO . forever $ readChan ch >>=- T.hPutStr h . T.encodeUtf8+runPrinter ch h = void . forkIO . forever $ readChan ch >>= T.hPutStr h . T.encodeUtf8 io :: MonadIO m => IO a -> m a io = liftIO class KevinServer a where- readClient, readServer :: a -> IO T.Text- writeServer :: a -> T.Text -> IO ()- writeClient :: a -> T.Text -> IO ()+ readClient, readServer :: a -> IO T.Text+ writeServer :: a -> T.Text -> IO ()+ writeClient :: a -> T.Text -> IO () closeClient, closeServer :: a -> IO () data KevinException = ParseFailure@@ -59,24 +58,21 @@ -- actions hGetCharTimeout :: Handle -> Int -> IO Char-hGetCharTimeout h t = do- hSetBuffering h NoBuffering- ready <- hWaitForInput h t- if ready- then hGetChar h- else throwIO $ mkIOError eofErrorType "read timeout" (Just h) Nothing+hGetCharTimeout h t = do hSetBuffering h NoBuffering+ ready <- hWaitForInput h t+ if ready+ then hGetChar h+ else throwIO $ mkIOError eofErrorType "read timeout" (Just h) Nothing hGetSep :: Char -> Handle -> IO String-hGetSep sep h = fix (\f -> do- ch <- hGetCharTimeout h 180000- if ch == sep- then return ""- else (ch:) <$> f)+hGetSep sep h = fix (\f -> do ch <- hGetCharTimeout h 180000+ if ch == sep+ then return ""+ else (ch:) <$> f) instance KevinServer Kevin where- readClient k = do- line <- T.decodeUtf8 <$> T.hGetLine (irc k)- return $ T.init line+ readClient k = do line <- T.decodeUtf8 <$> T.hGetLine (irc k)+ return $ T.init line readServer k = T.pack <$> hGetSep '\NUL' (damn k) writeClient k = writeChan (iChan k)
Kevin/Chatrooms.hs view
@@ -25,7 +25,7 @@ deleteBy' :: (a -> Bool) -> [a] -> [a] deleteBy' f (x:xs) = if f x then xs else x:deleteBy' f xs-deleteBy' _ [] = []+deleteBy' _ [] = [] removeRoom :: Chatroom -> KevinIO () removeRoom c = kevin $ privclasses.at c .= Nothing >> users.at c .= Nothing@@ -34,12 +34,9 @@ addUser ch us = kevin $ users.ix ch %= (us:) numUsers :: Chatroom -> T.Text -> KevinIO Int-numUsers ch us = do- st <- gets_ $ view users- case st^.at ch of- Just usrs -> return . length- $ findIndices (\u -> us == username u) usrs- Nothing -> return 0+numUsers ch us = do st <- gets_ $ view users+ case st^.at ch of Just usrs -> return . length $ findIndices (\u -> us == username u) usrs+ Nothing -> return 0 removeUser :: Chatroom -> T.Text -> KevinIO () removeUser ch us = kevin $ users.ix ch %= deleteBy' ((== us) . username)@@ -54,23 +51,17 @@ setPrivclasses room ps = kevin $ privclasses.at room ?= M.fromList ps getPrivclass :: Chatroom -> T.Text -> KevinIO (Maybe T.Text)-getPrivclass room user = do- st <- gets_ $ view users- case st^.at room of- Just qs -> return $ privclass- <$> listToMaybe (filter ((== user) . username) qs)- Nothing -> return Nothing+getPrivclass room user = do st <- gets_ $ view users+ case st^.at room of Just qs -> return $ privclass <$> listToMaybe (filter ((== user) . username) qs)+ Nothing -> return Nothing getPrivclassLevel :: Chatroom -> T.Text -> KevinIO Int-getPrivclassLevel room pc = do- st <- gets_ $ view privclasses- return . fromMaybe 0 $ st^.at room >>= (^.at pc)+getPrivclassLevel room pc = do st <- gets_ $ view privclasses+ return . fromMaybe 0 $ st^.at room >>= (^.at pc) setUserPrivclass :: Chatroom -> T.Text -> T.Text -> KevinIO ()-setUserPrivclass room user pc = do- pclevel <- getPrivclassLevel room pc- kevin $ users.ix room.traverse.filtered ((user ==) . username)- %= (\u -> u {privclass = pc, privclassLevel = pclevel})+setUserPrivclass room user pc = do pclevel <- getPrivclassLevel room pc+ kevin $ users.ix room.traverse.filtered ((user ==) . username) %= (\u -> u {privclass = pc, privclassLevel = pclevel}) setTitle :: Chatroom -> T.Text -> KevinIO () setTitle ch t = kevin $ titles.at ch ?= t
Kevin/Damn/Packet.hs view
@@ -45,41 +45,35 @@ parseCommand = takeWhile1 (not . isSpace) parseParam :: Parser (Maybe T.Text)-parseParam = do- char ' '- parm <- takeWhile1 (not . isSpace)- return $ Just parm+parseParam = do char ' '+ parm <- takeWhile1 (not . isSpace)+ return $ Just parm parseArgs :: Parser (M.Map T.Text T.Text)-parseArgs = (M.fromList <$>) . many $ do- char '\n'- c <- takeTill (=='=')- char '='- r <- takeTill (=='\n')- return (c,r)+parseArgs = (M.fromList <$>) . many $ do char '\n'+ c <- takeTill (=='=')+ char '='+ r <- takeTill (=='\n')+ return (c,r) parseHead :: Parser Packet-parseHead = do- c <- parseCommand- p <- option Nothing parseParam- a <- parseArgs- return $ Packet c p a Nothing+parseHead = do c <- parseCommand+ p <- option Nothing parseParam+ a <- parseArgs+ return $ Packet c p a Nothing parsePacket :: T.Text -> Packet-parsePacket pack = case parseOnly parseHead top of- Left _ -> throw ParseFailure- Right res -> res & body .~ (T.drop 2 <$> toMaybe (not . T.null) b)- where- (top, b) = T.breakOn "\n\n" pack+parsePacket pack = case parseOnly parseHead top of Left _ -> throw ParseFailure+ Right res -> res & body .~ (T.drop 2 <$> toMaybe (not . T.null) b)+ where (top, b) = T.breakOn "\n\n" pack getResult :: Either String a -> a getResult x = let Right e = x in e fixLoginPacket :: Packet -> Packet fixLoginPacket pkt = if pkt^.command == "login"- then pkt & args %~ (<> getResult- (parseOnly parseArgs . T.cons '\n' . fromJust $ pkt^.body))- else pkt+ then pkt & args %~ (<> getResult (parseOnly parseArgs . T.cons '\n' . fromJust $ pkt^.body))+ else pkt subPacket :: Packet -> Maybe Packet subPacket = (parsePacket <$>) . view body@@ -88,18 +82,15 @@ okay (Packet _ _ a _) = let e = a ^. at "e" in isNothing e || e == Just "ok" parsePrivclasses :: T.Text -> [Privclass]-parsePrivclasses = map (liftM2 (,) (!! 1) (read . T.unpack . head)- . T.splitOn ":") +parsePrivclasses = map (liftM2 (,) (!! 1) (read . T.unpack . head) . T.splitOn ":") . filter (not . T.null) . T.splitOn "\n" readable :: Packet -> T.Text-readable (Packet cmd param arg bod) = cmd- <> maybe "" (' ' `T.cons`) param- <> formattedArgs (M.toList arg)- <> maybe "" ("\n\n" <>) bod- <> "\n\0"+readable (Packet cmd param arg bod) = cmd <> maybe "" (' ' `T.cons`) param+ <> formattedArgs (M.toList arg)+ <> maybe "" ("\n\n" <>) bod+ <> "\n\0" where formattedArgs [] = ""- formattedArgs q = ("\n" <>) . T.intercalate "\n"- . map (uncurry (\x y -> x <> "=" <> y)) $ q+ formattedArgs q = ("\n" <>) . T.intercalate "\n" . map (uncurry (\x y -> x <> "=" <> y)) $ q
Kevin/Damn/Protocol.hs view
@@ -28,197 +28,151 @@ cleanup = klog Blue "cleanup server" listen :: KevinIO ()-listen = fix (\f -> flip catches errHandlers $ do- k <- get_- pkt <- io $ parsePacket <$> readServer k- respond pkt (view command pkt)- f)+listen = fix (\f -> flip catches errHandlers $ do k <- get_+ pkt <- io $ parsePacket <$> readServer k+ respond pkt (view command pkt)+ f) -- main responder respond :: Packet -> T.Text -> KevinIO ()-respond _ "dAmnServer" = do- s <- use_ settings- sendLogin (s^.name) (s^.authtoken)+respond _ "dAmnServer" = do s <- use_ settings+ sendLogin (s^.name) (s^.authtoken) respond pkt "login" = if okay pkt- then do- j <- kevin $ do- loggedIn .= True- use joining- mapM_ sendJoin j- else I.sendNotice $ "Login failed: " <> pkt ^. args.ix "e"+ then do j <- kevin $ do loggedIn .= True+ use joining+ mapM_ sendJoin j+ else I.sendNotice $ "Login failed: " <> pkt ^. args.ix "e" -respond pkt "join" = do- roomname <- deformatRoom $ pkt^.parameter._Just- if okay pkt- then do- kevin $ joining %= (roomname:)- uname <- use_ name- I.sendJoin uname roomname- else I.sendNotice $ T.concat- ["Couldn't join ", roomname, ": ", pkt^.args.ix "e"]+respond pkt "join" = do roomname <- deformatRoom $ pkt^.parameter._Just+ if okay pkt+ then do kevin $ joining %= (roomname:)+ uname <- use_ name+ I.sendJoin uname roomname+ else I.sendNotice $ T.concat ["Couldn't join ", roomname, ": ", pkt^.args.ix "e"] -respond pkt "part" = do- roomname <- deformatRoom $ pkt^.parameter._Just- if okay pkt- then do- uname <- use_ name- removeRoom roomname- I.sendPart uname roomname Nothing- else I.sendNotice $ T.concat- ["Couldn't part ", roomname, ": ", pkt^.args.ix "e"]+respond pkt "part" = do roomname <- deformatRoom $ pkt^.parameter._Just+ if okay pkt+ then do uname <- use_ name+ removeRoom roomname+ I.sendPart uname roomname Nothing+ else I.sendNotice $ T.concat ["Couldn't part ", roomname, ": ", pkt^.args.ix "e"] -respond pkt "property" = do- roomname <- deformatRoom (pkt^.parameter._Just)- case pkt^.args.ix "p" of- "privclasses" -> setPrivclasses roomname . parsePrivclasses- $ pkt^.body._Just+respond pkt "property" = do roomname <- deformatRoom (pkt^.parameter._Just)+ case pkt^.args.ix "p" of "privclasses" -> setPrivclasses roomname . parsePrivclasses $ pkt^.body._Just - "topic" -> do- uname <- use_ name- I.sendTopic uname roomname- (fromMaybe uname $ pkt^.args.at "by")- (T.replace "\n" " - " . entityDecode- . tablumpDecode $ pkt^.body._Just)- (pkt^.args.ix "ts")+ "topic" -> do uname <- use_ name+ I.sendTopic uname roomname (fromMaybe uname $ pkt^.args.at "by")+ (T.replace "\n" " - " . entityDecode . tablumpDecode $ pkt^.body._Just)+ (pkt^.args.ix "ts") - "title" -> setTitle roomname (T.replace "\n" " - " . entityDecode- . tablumpDecode $ pkt^.body._Just)+ "title" -> setTitle roomname (T.replace "\n" " - " . entityDecode . tablumpDecode $ pkt^.body._Just) - "members" -> do- k <- get_- let members = map (mkUser roomname (k^.privclasses) . parsePacket)- . init . T.splitOn "\n\n"- $ pkt^.body._Just- pc = privclass . head . filter (\x -> username x == k^.name)- $ members- n = nub members- setUsers roomname members- when (roomname `elem` k^.joining) $ do- I.sendUserList (k^.name) n roomname- pclevel <- getPrivclassLevel roomname pc- I.sendSetUserMode (k^.name) roomname pclevel- kevin $ joining %= delete roomname+ "members" -> do k <- get_+ let members = map (mkUser roomname (k^.privclasses) . parsePacket)+ . init . T.splitOn "\n\n"+ $ pkt^.body._Just+ pc = privclass . head . filter (\x -> username x == k^.name) $ members+ n = nub members+ setUsers roomname members+ when (roomname `elem` k^.joining) $ do I.sendUserList (k^.name) n roomname+ pclevel <- getPrivclassLevel roomname pc+ I.sendSetUserMode (k^.name) roomname pclevel+ kevin $ joining %= delete roomname - "info" -> do- us <- use_ name- curtime <- io $ floor <$> getPOSIXTime- let fixedPacket = parsePacket . T.init- . T.replace "\n\nusericon" "\nusericon"- . readable $ pkt- uname = T.drop 6 $ pkt^.parameter._Just- rn = fixedPacket^.args.ix "realname"- conns = map (\pk ->- let x = parsePacket $ "conn" <> pk- in ( read (T.unpack $ x^.args.ix "online") :: Int- , read (T.unpack $ x^.args.ix "idle") :: Int- , map (T.drop 8) . filter (not . T.null)- . T.splitOn "\n\n" $ x^.body._Just- )- ) . tail . T.splitOn "conn"- $ fixedPacket^.body._Just- allRooms = nub $ conns >>= (\(_,_,c) -> c)- (onlinespan,idle,_) = minimumBy (comparing (view _1)) conns- signon = curtime - onlinespan- I.sendWhoisReply us uname (entityDecode rn) allRooms idle signon+ "info" -> do us <- use_ name+ curtime <- io $ floor <$> getPOSIXTime+ let fixedPacket = parsePacket . T.init+ . T.replace "\n\nusericon" "\nusericon"+ . readable $ pkt+ uname = T.drop 6 $ pkt^.parameter._Just+ rn = fixedPacket^.args.ix "realname"+ conns = map (\pk -> let x = parsePacket $ "conn" <> pk+ in ( read (T.unpack $ x^.args.ix "online") :: Int+ , read (T.unpack $ x^.args.ix "idle" ) :: Int+ , map (T.drop 8) . filter (not . T.null) . T.splitOn "\n\n" $ x^.body._Just+ )) . tail . T.splitOn "conn" $ fixedPacket^.body._Just+ allRooms = nub $ conns >>= (\(_,_,c) -> c)+ (onlinespan,idle,_) = minimumBy (comparing (view _1)) conns+ signon = curtime - onlinespan+ I.sendWhoisReply us uname (entityDecode rn) allRooms idle signon - q -> klogError $ "Unrecognized property " ++ T.unpack q+ q -> klogError $ "Unrecognized property " ++ T.unpack q -respond spk "recv" = deformatRoom (spk^.parameter._Just) >>= \roomname ->- case pkt^.command of- "join" -> do- let usname = pkt^.parameter._Just- pcs <- gets_ $ view privclasses- countUser <- numUsers roomname usname- let us = mkUser roomname pcs modifiedPkt- addUser roomname us- if countUser == 0- then do- I.sendJoin usname roomname- pclevel <- getPrivclassLevel roomname- $ modifiedPkt^.args.ix "pc"- I.sendSetUserMode usname roomname pclevel- else I.sendNoticeClone (username us) (succ countUser) roomname+respond spk "recv" = deformatRoom (spk^.parameter._Just) >>=+ \roomname -> case pkt^.command of "join" -> do let usname = pkt^.parameter._Just+ pcs <- gets_ $ view privclasses+ countUser <- numUsers roomname usname+ let us = mkUser roomname pcs modifiedPkt+ addUser roomname us+ if countUser == 0+ then do I.sendJoin usname roomname+ pclevel <- getPrivclassLevel roomname $ modifiedPkt^.args.ix "pc"+ I.sendSetUserMode usname roomname pclevel+ else I.sendNoticeClone (username us) (succ countUser) roomname - "part" -> do- let uname = pkt^.parameter._Just- removeUser roomname uname- countUser <- numUsers roomname uname- if countUser < 1- then I.sendPart uname roomname $ pkt^.args.at "r"- else I.sendNoticeUnclone uname countUser roomname+ "part" -> do let uname = pkt^.parameter._Just+ removeUser roomname uname+ countUser <- numUsers roomname uname+ if countUser < 1+ then I.sendPart uname roomname $ pkt^.args.at "r"+ else I.sendNoticeUnclone uname countUser roomname - "msg" -> do- let uname = arg "from"- msg = pkt^.body._Just- un <- use_ name- unless (un == uname) $ I.sendChanMsg uname roomname- (entityDecode $ tablumpDecode msg)+ "msg" -> do let uname = arg "from"+ msg = pkt^.body._Just+ un <- use_ name+ unless (un == uname) $ I.sendChanMsg uname roomname (entityDecode $ tablumpDecode msg) - "action" -> do- let uname = arg "from"- msg = pkt^.body._Just- un <- use_ name- unless (un == uname) $ I.sendChanAction uname roomname- (entityDecode $ tablumpDecode msg)+ "action" -> do let uname = arg "from"+ msg = pkt^.body._Just+ un <- use_ name+ unless (un == uname) $ I.sendChanAction uname roomname (entityDecode $ tablumpDecode msg) - "privchg" -> do- let user = pkt^.parameter._Just- by = arg "by"- newPc = arg "pc"- oldPc <- getPrivclass roomname user- oldPcLevel <- getPrivclassLevel roomname (fromMaybe "" oldPc)- newPcLevel <- getPrivclassLevel roomname newPc- setUserPrivclass roomname user newPc- I.sendRoomNotice roomname $ T.concat- [user, " has been moved"- , maybe "" (" from " <>) oldPc- , " to ", newPc, " by ", by- ]- I.sendChangeUserMode user roomname oldPcLevel newPcLevel+ "privchg" -> do let user = pkt^.parameter._Just+ by = arg "by"+ newPc = arg "pc"+ oldPc <- getPrivclass roomname user+ oldPcLevel <- getPrivclassLevel roomname (fromMaybe "" oldPc)+ newPcLevel <- getPrivclassLevel roomname newPc+ setUserPrivclass roomname user newPc+ I.sendRoomNotice roomname $ T.concat [ user, " has been moved"+ , maybe "" (" from " <>) oldPc+ , " to ", newPc, " by ", by+ ]+ I.sendChangeUserMode user roomname oldPcLevel newPcLevel - "kicked" -> do- let uname = pkt^.parameter._Just- removeUserAll roomname uname- I.sendKick uname (arg "by") roomname $ pkt^.body.traverse ^? notNull_+ "kicked" -> do let uname = pkt^.parameter._Just+ removeUserAll roomname uname+ I.sendKick uname (arg "by") roomname $ pkt^.body.traverse ^? notNull_ - "admin" -> case pkt^.parameter._Just of- "create" -> I.sendRoomNotice roomname $- T.concat ["Privclass ", arg "name"- , " created by ", arg "by"- , " with: ", arg "privs"]- "update" -> I.sendRoomNotice roomname $- T.concat ["Privclass ", arg "name"- , " updated by ", arg "by"- , " with: ", arg "privs"]- "rename" -> I.sendRoomNotice roomname $- T.concat ["Privclass ", arg "prev"- , " renamed to ", arg "name"- , " by ", arg "by"]- "move" -> I.sendRoomNotice roomname $- T.concat [ arg "n", " users in privclass "- , arg "prev", " moved to "- , arg "name", " by ", arg "by"]- "remove" -> I.sendRoomNotice roomname $- T.concat ["Privclass", arg "name", " removed by ", arg "by"]- "show" -> mapM_ (I.sendRoomNotice roomname) . T.splitOn "\n"- $ pkt^.body._Just- "privclass" -> I.sendRoomNotice roomname $ "Admin error: " <> arg "e"- q -> klogError $ "Unknown admin packet type " ++ show q+ "admin" -> case pkt^.parameter._Just of "create" -> I.sendRoomNotice roomname $ T.concat [ "Privclass ", arg "name"+ , " created by ", arg "by"+ , " with: ", arg "privs" ]+ "update" -> I.sendRoomNotice roomname $ T.concat [ "Privclass ", arg "name"+ , " updated by ", arg "by"+ , " with: ", arg "privs" ]+ "rename" -> I.sendRoomNotice roomname $ T.concat [ "Privclass ", arg "prev"+ , " renamed to ", arg "name"+ , " by ", arg "by" ]+ "move" -> I.sendRoomNotice roomname $ T.concat [ arg "n", " users in privclass "+ , arg "prev", " moved to "+ , arg "name", " by ", arg "by" ]+ "remove" -> I.sendRoomNotice roomname $ T.concat [ "Privclass", arg "name"+ , " removed by ", arg "by" ]+ "show" -> mapM_ (I.sendRoomNotice roomname) . T.splitOn "\n" $ pkt^.body._Just+ "privclass" -> I.sendRoomNotice roomname $ "Admin error: " <> arg "e"+ q -> klogError $ "Unknown admin packet type " ++ show q - x -> klogError $ "Unknown packet type " ++ show x+ x -> klogError $ "Unknown packet type " ++ show x - where- pkt = fromJust $ subPacket spk- modifiedPkt = parsePacket (T.replace "\n\npc" "\npc" $ readable pkt)- arg s = pkt^.args.ix s+ where pkt = fromJust $ subPacket spk+ modifiedPkt = parsePacket (T.replace "\n\npc" "\npc" $ readable pkt)+ arg s = pkt^.args.ix s -respond pkt "kicked" = do- roomname <- deformatRoom $ pkt^.parameter._Just- uname <- use_ name- removeRoom roomname- I.sendKick uname (pkt^.args.ix "by") roomname- $ pkt^.body.traverse ^? notNull_+respond pkt "kicked" = do roomname <- deformatRoom $ pkt^.parameter._Just+ uname <- use_ name+ removeRoom roomname+ I.sendKick uname (pkt^.args.ix "by") roomname $ pkt^.body.traverse ^? notNull_ respond pkt "send" = I.sendNotice $ "Send error: " <> pkt^.args.ix "e" @@ -229,16 +183,14 @@ mkUser :: Chatroom -> PrivclassStore -> Packet -> User mkUser room st p = User (p^.parameter._Just)- (g "pc")- (fromMaybe 0 $ st^.at room >>= (^.at (g "pc")))- (g "symbol")- (entityDecode $ g "realname")- (g "typename")- (g "gpc")- where- g s = p^.args.ix s+ (g "pc")+ (fromMaybe 0 $ st^.at room >>= (^.at (g "pc")))+ (g "symbol")+ (entityDecode $ g "realname")+ (g "typename")+ (g "gpc")+ where g s = p^.args.ix s errHandlers :: [Handler KevinIO ()]-errHandlers = [ handler_ _KevinException- $ klogError "Malformed communication from server"+errHandlers = [ handler_ _KevinException $ klogError "Malformed communication from server" , handler _IOException (\e -> klogError $ "server: " ++ show e) ]
Kevin/Damn/Protocol/Send.hs view
@@ -36,99 +36,80 @@ sendPacket p = get_ >>= \k -> io . writeServer k . T.snoc p $ '\0' formatRoom :: T.Text -> KevinIO T.Text-formatRoom b =- case T.splitAt 1 b of- ("#",s) -> return $ "chat:" <> s- ("&",s) -> do- uname <- use_ name- return . ("pchat:" <>) . T.intercalate ":"- . sort . map (T.map toLower) $ [uname, s]- r -> return $ "chat" <> uncurry (<>) r+formatRoom b = case T.splitAt 1 b of ("#",s) -> return $ "chat:" <> s+ ("&",s) -> do uname <- use_ name+ return . ("pchat:" <>) . T.intercalate ":" . sort . map (T.map toLower) $ [uname, s]+ r -> return $ "chat" <> uncurry (<>) r deformatRoom :: T.Text -> KevinIO T.Text deformatRoom room = if "chat:" `T.isPrefixOf` room- then return $ '#' `T.cons` T.drop 5 room- else do- uname <- use_ name- return $ T.cons '&' (head (filter (/= uname) . T.splitOn ":"- . T.drop 6 $ room))+ then return $ '#' `T.cons` T.drop 5 room+ else do uname <- use_ name+ return $ T.cons '&' (head (filter (/= uname) . T.splitOn ":" . T.drop 6 $ room)) -type Str = T.Text -- just make it shorter-type Room = Str+type Str = T.Text -- just make it shorter+type Room = Str type Username = Str-type Pc = Str+type Pc = Str -- * Communication to the server-sendHandshake :: KevinIO ()-sendLogin :: Username -> Str -> KevinIO ()-sendJoin, sendPart :: Room -> KevinIO ()-sendMsg, sendAction, sendNpMsg :: Room -> Str -> KevinIO ()-sendPromote, sendDemote :: Room -> Username -> Maybe Pc -> KevinIO ()-sendBan, sendUnban :: Room -> Username -> KevinIO ()-sendKick :: Room -> Username -> Maybe Str -> KevinIO ()-sendGet :: Room -> Str -> KevinIO ()-sendWhois :: Username -> KevinIO ()-sendSet :: Room -> Str -> Str -> KevinIO ()-sendAdmin :: Room -> Str -> KevinIO ()-sendKill :: Username -> Str -> KevinIO ()+sendHandshake :: KevinIO ()+sendLogin :: Username -> Str -> KevinIO ()+sendJoin, sendPart :: Room -> KevinIO ()+sendMsg, sendAction, sendNpMsg :: Room -> Str -> KevinIO ()+sendPromote, sendDemote :: Room -> Username -> Maybe Pc -> KevinIO ()+sendBan, sendUnban :: Room -> Username -> KevinIO ()+sendKick :: Room -> Username -> Maybe Str -> KevinIO ()+sendGet :: Room -> Str -> KevinIO ()+sendWhois :: Username -> KevinIO ()+sendSet :: Room -> Str -> Str -> KevinIO ()+sendAdmin :: Room -> Str -> KevinIO ()+sendKill :: Username -> Str -> KevinIO () -sendHandshake = sendPacket $ printf "dAmnClient 0.3\nagent=kevin%s\n"- [versionStr]+sendHandshake = sendPacket $ printf "dAmnClient 0.3\nagent=kevin%s\n" [versionStr] sendLogin u token = sendPacket $ printf "login %s\npk=%s\n" [u, token] -sendJoin room = do- roomname <- formatRoom room- sendPacket $ printf "join %s\n" [roomname]+sendJoin room = do roomname <- formatRoom room+ sendPacket $ printf "join %s\n" [roomname] -sendPart room = do- roomname <- formatRoom room- sendPacket $ printf "part %s\n" [roomname]+sendPart room = do roomname <- formatRoom room+ sendPacket $ printf "part %s\n" [roomname] -sendMsg room msg = do- roomname <- formatRoom room- sendPacket $ printf "send %s\n\nmsg main\n\n%s" [roomname, msg]+sendMsg = sendNpMsg -sendAction room msg = do- roomname <- formatRoom room- sendPacket $ printf "send %s\n\naction main\n\n%s" [roomname, msg]+sendAction room msg = do roomname <- formatRoom room+ sendPacket $ printf "send %s\n\naction main\n\n%s" [roomname, msg] -sendNpMsg = undefined+sendNpMsg room msg = do roomname <- formatRoom room+ sendPacket $ printf "send %s\n\nnpmsg main\n\n%s" [roomname, msg] -sendPromote room us pc = do- roomname <- formatRoom room- sendPacket $ printf "send %s\n\npromote %s%s" [roomname, us, maybeBody pc]+sendPromote room us pc = do roomname <- formatRoom room+ sendPacket $ printf "send %s\n\npromote %s%s" [roomname, us, maybeBody pc] -sendDemote room us pc = do- roomname <- formatRoom room- sendPacket $ printf "send %s\n\ndemote %s%s" [roomname, us, maybeBody pc]+sendDemote room us pc = do roomname <- formatRoom room+ sendPacket $ printf "send %s\n\ndemote %s%s" [roomname, us, maybeBody pc] -sendBan room us = do- roomname <- formatRoom room- sendPacket $ printf "send %s\n\nban %s\n\n" [roomname, us]+sendBan room us = do roomname <- formatRoom room+ sendPacket $ printf "send %s\n\nban %s\n\n" [roomname, us] -sendUnban room us = do- roomname <- formatRoom room- sendPacket $ printf "send %s\n\nunban %s\n\n" [roomname, us]+sendUnban room us = do roomname <- formatRoom room+ sendPacket $ printf "send %s\n\nunban %s\n\n" [roomname, us] -sendKick room us reason = do- roomname <- formatRoom room- sendPacket $ printf "kick %s\nu=%s%s\n" [roomname, us, maybeBody reason]+sendKick room us reason = do roomname <- formatRoom room+ sendPacket $ printf "kick %s\nu=%s%s\n" [roomname, us, maybeBody reason] -sendGet room prop = do- guard $ prop `elem` ["title", "topic", "privclasses", "members"]- roomname <- formatRoom room- sendPacket $ printf "get %s\np=%s\n" [roomname, prop]+sendGet room prop = do guard $ prop `elem` ["title", "topic", "privclasses", "members"]+ roomname <- formatRoom room+ sendPacket $ printf "get %s\np=%s\n" [roomname, prop] sendWhois us = sendPacket $ printf "get login:%s\np=info\n" [us] -sendSet room prop val = do- guard (prop == "topic" || prop == "title")- roomname <- formatRoom room- sendPacket $ printf "set %s\np=%s\n\n%s\n" [roomname, prop, val]+sendSet room prop val = do guard (prop == "topic" || prop == "title")+ roomname <- formatRoom room+ sendPacket $ printf "set %s\np=%s\n\n%s\n" [roomname, prop, val] -sendAdmin room cmd = do- roomname <- formatRoom room- sendPacket $ printf "send %s\n\nadmin\n\n%s" [roomname, cmd]+sendAdmin room cmd = do roomname <- formatRoom room+ sendPacket $ printf "send %s\n\nadmin\n\n%s" [roomname, cmd] sendKill = undefined
Kevin/IRC/Packet.hs view
@@ -15,9 +15,9 @@ import qualified Data.Text as T import Prelude hiding (takeWhile) -data Packet = Packet { _prefix :: Maybe T.Text+data Packet = Packet { _prefix :: Maybe T.Text , _command :: T.Text- , _params :: [T.Text]+ , _params :: [T.Text] } | BadPacket deriving (Show) @@ -33,11 +33,10 @@ servername = takeWhile1 (inClass "a-z0-9.-") username :: Parser T.Text-username = do- n <- nick- u <- option "" (T.cons <$> char '!' <*> user)- h <- option "" (T.cons <$> char '@' <*> servername)- return $ T.concat [n, u, h]+username = do n <- nick+ u <- option "" (T.cons <$> char '!' <*> user)+ h <- option "" (T.cons <$> char '@' <*> servername)+ return $ T.concat [n, u, h] nick :: Parser T.Text nick = T.cons <$> letter <*> takeWhile (inClass "a-zA-Z0-9[]\\`^{}-")@@ -49,9 +48,7 @@ parsePrefix = username <|> servername parseCommand :: Parser T.Text-parseCommand = takeWhile1 isAlpha- <|> liftM3 (\a b c -> T.pack [a,b,c])- digit digit digit+parseCommand = takeWhile1 isAlpha <|> liftM3 (\a b c -> T.pack [a,b,c]) digit digit digit parseParams :: Parser [T.Text] parseParams = (colonParam <|> nonColonParam) `sepBy` spaces@@ -69,26 +66,23 @@ messageBegin = Just <$> (char ':' *> parsePrefix <* spaces) packetParser :: Parser Packet-packetParser = do- pr <- option Nothing messageBegin- cmd <- T.map toUpper <$> parseCommand- spaces- par <- filter (not . T.null) <$> parseParams- option "" crlf- return $ Packet pr cmd par+packetParser = do pr <- option Nothing messageBegin+ cmd <- T.map toUpper <$> parseCommand+ spaces+ par <- filter (not . T.null) <$> parseParams+ option "" crlf+ return $ Packet pr cmd par parsePacket :: T.Text -> Packet-parsePacket str = case parseOnly packetParser str of- Left _ -> BadPacket- Right p -> p+parsePacket str = case parseOnly packetParser str of Left _ -> BadPacket+ Right p -> p showParams :: [T.Text] -> T.Text showParams = T.unwords . map (\str -> if " " `T.isInfixOf` str- then T.cons ':' str- else str)+ then T.cons ':' str+ else str) readable :: Packet -> T.Text-readable (Packet (Just str) cmd pms) = (<> "\r\n")- $ T.unwords [T.cons ':' str, cmd, showParams pms]-readable (Packet Nothing c p) = (<> "\r\n") $ T.unwords [c, showParams p]-readable _ = ""+readable (Packet (Just str) cmd pms) = (<> "\r\n") $ T.unwords [T.cons ':' str, cmd, showParams pms]+readable (Packet Nothing c p) = (<> "\r\n") $ T.unwords [c, showParams p]+readable _ = ""
Kevin/IRC/Protocol.hs view
@@ -30,71 +30,61 @@ cleanup = klog Green "cleanup client" listen :: KevinIO ()-listen = fix (\f -> flip catches errHandlers $ do- k <- get_- pkt <- io $ parsePacket <$> readClient k- respond pkt (view command pkt)- f)+listen = fix (\f -> flip catches errHandlers $ do k <- get_+ pkt <- io $ parsePacket <$> readClient k+ respond pkt (view command pkt)+ f) respond :: Packet -> T.Text -> KevinIO () respond BadPacket _ = sendNotice "Bad packet, try again."-respond pkt "JOIN" = do- l <- gets_ (view loggedIn)- if l- then mapM_ D.sendJoin rooms- else kevin $ joining %= (rooms ++)- where- rooms = T.splitOn "," $ pkt^.params._head+respond pkt "JOIN" = do l <- gets_ (view loggedIn)+ if l+ then mapM_ D.sendJoin rooms+ else kevin $ joining %= (rooms ++)+ where rooms = T.splitOn "," $ pkt^.params._head respond pkt "PART" = mapM_ D.sendPart . T.splitOn "," $ pkt^.params._head -respond pkt "PRIVMSG" = do- let (room:msg:_) = pkt^.params- if "\1ACTION" `T.isPrefixOf` msg- then do- let newMsg = T.drop 8 $ T.init msg- D.sendAction room $ entityEncode newMsg- else D.sendMsg room $ entityEncode msg+respond pkt "PRIVMSG" = do let (room:msg:_) = pkt^.params+ if "\1ACTION" `T.isPrefixOf` msg+ then do let newMsg = T.drop 8 $ T.init msg+ D.sendAction room $ entityEncode newMsg+ else D.sendMsg room $ entityEncode msg respond pkt "MODE" = if length (pkt^.params) > 1- then do- let (toggle,mode) = first (=="+") . T.splitAt 1 $ pkt^.params.ix 1- case mode of- "b" -> if' toggle D.sendBan D.sendUnban- (pkt^.params._head)- (fromMaybe "random unparseable garbage" . unmask- $ pkt^.params._last)- "o" -> if' toggle D.sendPromote D.sendDemote- (pkt^.params._head)- (pkt^.params._last)- Nothing- _ -> sendRoomNotice (pkt^.params._head)- $ "Unsupported mode " <> mode- else do- uname <- use_ name- sendChanMode uname (pkt^.params._head)+ then do let (toggle,mode) = first (=="+") . T.splitAt 1 $ pkt^.params.ix 1+ case mode of "b" -> if' toggle+ D.sendBan+ D.sendUnban+ (pkt^.params._head)+ (fromMaybe "random unparseable garbage" . unmask $ pkt^.params._last)+ "o" -> if' toggle+ D.sendPromote+ D.sendDemote+ (pkt^.params._head)+ (pkt^.params._last)+ Nothing+ _ -> sendRoomNotice (pkt^.params._head) $ "Unsupported mode " <> mode+ else do uname <- use_ name+ sendChanMode uname (pkt^.params._head) -respond pkt "TOPIC" = case pkt^.params of- [] -> sendNotice "Malformed packet"- [room] -> D.sendGet room "topic"- (room:topic:_) -> D.sendSet room "topic" topic+respond pkt "TOPIC" = case pkt^.params of [] -> sendNotice "Malformed packet"+ [room] -> D.sendGet room "topic"+ (room:topic:_) -> D.sendSet room "topic" topic -respond pkt "TITLE" = case pkt^.params of- [] -> sendNotice "Malformed packet"- [room] -> do- title <- gets_ . view $ titles.ix room- let p = T.concat ["Title for ", room, ": "]- mapM_ (sendRoomNotice room . (p <>)) (T.splitOn "\n" title)- (room:title) -> D.sendSet room "title" $ T.unwords title+respond pkt "TITLE" = case pkt^.params of [] -> sendNotice "Malformed packet"+ [room] -> do title <- gets_ . view $ titles.ix room+ let p = T.concat ["Title for ", room, ": "]+ mapM_ (sendRoomNotice room . (p <>)) (T.splitOn "\n" title)+ (room:title) -> D.sendSet room "title" $ T.unwords title respond pkt "PING" = sendPong $ pkt^.params._head respond pkt "WHOIS" = D.sendWhois $ pkt^.params._head -respond pkt "NAMES" = do- let room = pkt^.params._head- k <- get_- sendUserList (k^.name) (nubBy ((==) `on` username) (k^.users.ix room)) room+respond pkt "NAMES" = do let room = pkt^.params._head+ k <- get_+ sendUserList (k^.name) (nubBy ((==) `on` username) (k^.users.ix room)) room respond pkt "KICK" = let p = pkt^.params in D.sendKick (head p)@@ -108,92 +98,67 @@ respond pkt "ADMIN" = D.sendAdmin p $ T.intercalate " " ps where (p:ps) = pkt^.params -respond pkt "PROMOTE" = case pkt^.params of- (room:user:group:_) -> D.sendPromote room user $ Just group- (room:_) -> sendRoomNotice room "Usage: /promote #room username group"- _ -> sendNotice "Usage: /promote #room username group"+respond pkt "PROMOTE" = case pkt^.params of (room:user:group:_) -> D.sendPromote room user $ Just group+ (room:_) -> sendRoomNotice room "Usage: /promote #room username group"+ _ -> sendNotice "Usage: /promote #room username group" respond _ str = klogError $ T.unpack str unmask :: T.Text -> Maybe T.Text-unmask y = case T.split (`elem` "@!") y of- [s] -> Just s- xs -> listToMaybe $ filter (not . T.isInfixOf "*") xs+unmask y = case T.split (`elem` "@!") y of [s] -> Just s+ xs -> listToMaybe $ filter (not . T.isInfixOf "*") xs errHandlers :: [Handler KevinIO ()]-errHandlers = [ handler_ _KevinException- $ klogError "Bad communication from client"- , handler _IOException (\e -> klogError $ "client: " ++ show e)]+errHandlers = [ handler_ _KevinException $ klogError "Bad communication from client"+ , handler _IOException (\e -> klogError $ "client: " ++ show e) ] -- * Authentication-getting function notice :: Handle -> T.Text -> IO ()-notice h str = do- klogNow Blue ("client -> " ++ T.unpack asStr)- T.hPutStr h (asStr <> "\r\n")- where- asStr = printf "NOTICE AUTH :%s" [str]+notice h str = do klogNow Blue ("client -> " ++ T.unpack asStr)+ T.hPutStr h (asStr <> "\r\n")+ where asStr = printf "NOTICE AUTH :%s" [str] getAuthInfo :: Handle -> Bool -> KevinState ()-getAuthInfo handle = fix (\f authRetry -> do- pkt <- io $ parsePacket <$> T.hGetLine handle- io $ klogNow Yellow $ "client <- " ++ T.unpack (readable pkt)- case pkt^.command of- "PASS" -> do- password .= pkt^.params._head- passed .= True- "NICK" -> do- name .= pkt^.params._head- nicked .= True- "USER" -> usered .= True- _ -> io $ klogNow Red $ "invalid packet: " ++ show pkt- if authRetry- then checkToken handle- else do- p <- use passed- n <- use nicked- u <- use usered- if p && n && u- then welcome handle- else f False- )+getAuthInfo handle = fix (\f authRetry -> do pkt <- io $ parsePacket <$> T.hGetLine handle+ io $ klogNow Yellow $ "client <- " ++ T.unpack (readable pkt)+ case pkt^.command of "PASS" -> do password .= pkt^.params._head+ passed .= True+ "NICK" -> do name .= pkt^.params._head+ nicked .= True+ "USER" -> usered .= True+ _ -> io $ klogNow Red $ "invalid packet: " ++ show pkt+ if authRetry+ then checkToken handle+ else do p <- use passed+ n <- use nicked+ u <- use usered+ if p && n && u+ then welcome handle+ else f False) welcome :: Handle -> KevinState ()-welcome handle = do- nick <- use name- mapM_ (\x -> io $ do- klogNow Blue ("client -> " ++ T.unpack x)- T.hPutStr handle (x <> "\r\n")) [- printf ":%s 001 %s :Welcome to dAmnServer %s!%s@chat.deviantart.com"- [hostname, nick, nick, nick],- printf ":%s 002 %s :Your host is chat.deviantart.com\- \, running dAmnServer 0.3" [hostname, nick],- printf ":%s 003 %s :This server was created Thu Apr 28 1994\- \ at 05:30:00 EDT" [hostname, nick],- printf ":%s 004 %s chat.deviantart.com dAmnServer0.3 qov i"- [hostname, nick],- printf ":%s 005 %s PREFIX=(qov)~@+" [hostname, nick],- printf ":%s 375 %s :- chat.deviantart.com Message of the day -" - [hostname, nick],- printf ":%s 372 %s :- deviantART chat on IRC brought to you\- \ by kevin %s, created" [hostname, nick, versionStr],- printf ":%s 372 %s :- and maintained by Joel Taylor <http://otte.rs>"- [hostname, nick],- printf ":%s 376 %s :End of MOTD command" [hostname, nick]]- checkToken handle- where- hostname = "chat.deviantart.com"+welcome handle = do nick <- use name+ mapM_ (\x -> io $ do+ klogNow Blue ("client -> " ++ T.unpack x)+ T.hPutStr handle (x <> "\r\n")) [+ printf ":%s 001 %s :Welcome to dAmnServer %s!%s@chat.deviantart.com" [hostname, nick, nick, nick],+ printf ":%s 002 %s :Your host is chat.deviantart.com, running dAmnServer 0.3" [hostname, nick],+ printf ":%s 003 %s :This server was created Thu Apr 28 1994 at 05:30:00 EDT" [hostname, nick],+ printf ":%s 004 %s chat.deviantart.com dAmnServer0.3 qov i" [hostname, nick],+ printf ":%s 005 %s PREFIX=(qov)~@+" [hostname, nick],+ printf ":%s 375 %s :- chat.deviantart.com Message of the day -" [hostname, nick],+ printf ":%s 372 %s :- deviantART chat on IRC brought to you by kevin %s, created" [hostname, nick, versionStr],+ printf ":%s 372 %s :- and maintained by Joel Taylor <http://otte.rs>" [hostname, nick],+ printf ":%s 376 %s :End of MOTD command" [hostname, nick]]+ checkToken handle+ where hostname = "chat.deviantart.com" checkToken :: Handle -> KevinState ()-checkToken handle = do- s <- get- io $ notice handle "Fetching token..."- tok <- io $ getToken (s^.name) (s^.password)- case tok of- Just t -> do- authtoken .= t- io $ notice handle "Successfully authenticated."- Nothing -> do- io $ notice handle "Bad password, try again.\- \ (/quote pass yourpassword)"- getAuthInfo handle True+checkToken handle = do s <- get+ io $ notice handle "Fetching token..."+ tok <- io $ getToken (s^.name) (s^.password)+ case tok of Just t -> do authtoken .= t+ io $ notice handle "Successfully authenticated."+ Nothing -> do io $ notice handle "Bad password, try again. (/quote pass yourpassword)"+ getAuthInfo handle True
Kevin/IRC/Protocol/Send.hs view
@@ -37,123 +37,82 @@ type Room = Str type Username = Str -sendJoin :: Username -> Room -> KevinIO ()-sendPart :: Username -> Room -> Maybe Str -> KevinIO ()-sendSetUserMode :: Username -> Room -> Int -> KevinIO ()-sendChangeUserMode :: Username -> Room -> Int -> Int -> KevinIO ()-sendNotice :: Str -> KevinIO ()-sendRoomNotice :: Room -> Str -> KevinIO ()-sendChanMsg, sendChanAction :: Username -> Room -> Str -> KevinIO ()-sendKick :: Username -> Username -> Room -> Maybe Str -> KevinIO ()-sendTopic :: Username -> Room -> Username -> Str -> Str -> KevinIO ()-sendChanMode :: Username -> Room -> KevinIO ()-sendUserList :: Username -> [User] -> Room -> KevinIO ()-sendPong :: T.Text -> KevinIO ()-sendNoticeClone :: Username -> Int -> Room -> KevinIO ()-sendNoticeUnclone :: Username -> Int -> Room -> KevinIO ()-sendWhoisReply :: Username- -> Username- -> Username- -> [Room]- -> Int- -> Int- -> KevinIO ()+sendJoin :: Username -> Room -> KevinIO ()+sendPart :: Username -> Room -> Maybe Str -> KevinIO ()+sendSetUserMode :: Username -> Room -> Int -> KevinIO ()+sendChangeUserMode :: Username -> Room -> Int -> Int -> KevinIO ()+sendNotice :: Str -> KevinIO ()+sendRoomNotice :: Room -> Str -> KevinIO ()+sendChanMsg, sendChanAction :: Username -> Room -> Str -> KevinIO ()+sendKick :: Username -> Username -> Room -> Maybe Str -> KevinIO ()+sendTopic :: Username -> Room -> Username -> Str -> Str -> KevinIO ()+sendChanMode :: Username -> Room -> KevinIO ()+sendUserList :: Username -> [User] -> Room -> KevinIO ()+sendPong :: T.Text -> KevinIO ()+sendNoticeClone :: Username -> Int -> Room -> KevinIO ()+sendNoticeUnclone :: Username -> Int -> Room -> KevinIO ()+sendWhoisReply :: Username -> Username -> Username -> [Room] -> Int -> Int -> KevinIO () -sendJoin us rm =- sendPacket $ printf "%s JOIN :%s" [getHost us, rm]+sendJoin us rm = sendPacket $ printf "%s JOIN :%s" [getHost us, rm] -sendPart us rm msg =- sendPacket $ printf "%s PART %s%s" [getHost us, rm, maybeBody msg]+sendPart us rm msg = sendPacket $ printf "%s PART %s%s" [getHost us, rm, maybeBody msg] -sendSetUserMode us rm m = unless (T.null mode) $- sendPacket $ printf "%s MODE %s +%s %s" [hostname, rm, mode, us]- where mode = levelToMode m+sendSetUserMode us rm m = unless (T.null mode) $ sendPacket $ printf "%s MODE %s +%s %s" [hostname, rm, mode, us]+ where mode = levelToMode m -sendChangeUserMode us rm old new = unless (oldMode == newMode) $- sendPacket $ printf "%s MODE %s %s" [hostname, rm, modesAndUser]- where- oldMode = levelToMode old- newMode = levelToMode new- modesAndUser = case (oldMode, newMode) of- ("", "") -> T.concat ["-v ", us]- ("", _) -> T.concat ["+", newMode, " ", us]- (_, "") -> T.concat ["-", oldMode, " ", us]- (a, b) -> T.concat ["-", a, "+", b, " ", us, " ", us]+sendChangeUserMode us rm old new = unless (oldMode == newMode) $ sendPacket $ printf "%s MODE %s %s" [hostname, rm, modesAndUser]+ where oldMode = levelToMode old+ newMode = levelToMode new+ modesAndUser = case (oldMode, newMode) of ("", "") -> T.concat ["-v ", us]+ ("", _) -> T.concat ["+", newMode, " ", us]+ (_, "") -> T.concat ["-", oldMode, " ", us]+ (a, b) -> T.concat ["-", a, "+", b, " ", us, " ", us] -sendNotice =- sendPacket . printf "NOTICE AUTH :%s" . return+sendNotice = sendPacket . printf "NOTICE AUTH :%s" . return -sendRoomNotice room n =- sendPacket $ printf "%s NOTICE %s :%s" [hostname, room, n]+sendRoomNotice room n = sendPacket $ printf "%s NOTICE %s :%s" [hostname, room, n] -sendChanMsg sender room msg = mapM_ (\x ->- sendPacket $ printf "%s PRIVMSG %s :%s" [getHost sender, room, x]- ) . T.splitOn "\n" $ msg+sendChanMsg sender room msg = mapM_ (\x -> sendPacket $ printf "%s PRIVMSG %s :%s" [getHost sender, room, x]) . T.splitOn "\n" $ msg -sendChanAction sender room msg = mapM_ (\x ->- sendPacket $ printf "%s PRIVMSG %s :\1ACTION %s\1"- [getHost sender, room, x]- ) . T.splitOn "\n" $ msg+sendChanAction sender room msg = mapM_ (\x -> sendPacket $ printf "%s PRIVMSG %s :\1ACTION %s\1" [getHost sender, room, x]) . T.splitOn "\n" $ msg -sendKick kickee kicker room msg =- sendPacket $ printf "%s KICK %s %s%s"- [getHost kicker, room, kickee, maybeBody msg]+sendKick kickee kicker room msg = sendPacket $ printf "%s KICK %s %s%s" [getHost kicker, room, kickee, maybeBody msg] -sendTopic us rm maker top startdate = do- sendPacket $ printf "%s 332 %s %s :%s" [hostname, us, rm, top]- sendPacket $ printf "%s 333 %s %s %s %s"- [hostname, us, rm, maker, startdate]+sendTopic us rm maker top startdate = do sendPacket $ printf "%s 332 %s %s :%s" [hostname, us, rm, top]+ sendPacket $ printf "%s 333 %s %s %s %s" [hostname, us, rm, maker, startdate] -sendChanMode us rm = do- sendPacket $ printf "%s 324 %s %s +t" [hostname, us, rm]- sendPacket $ printf "%s 329 %s %s 767529000" [hostname, us, rm]+sendChanMode us rm = do sendPacket $ printf "%s 324 %s %s +t" [hostname, us, rm]+ sendPacket $ printf "%s 329 %s %s 767529000" [hostname, us, rm] -sendUserList us uss rm = do- mapM_ (\nms ->- sendPacket $ printf "%s 353 %s = %s :%s"- [hostname, us, rm, T.unwords nms]) chunkedNames- sendPacket $ printf "%s 366 %s %s :End of /NAMES list." [hostname, us, rm]- where- names = map (\u ->- T.concat [levelToSym $ privclassLevel u, username u]) uss- chunkedNames = reverse . map reverse . subchunk' 432 names $ [[]]- subchunk' n = fix (\f x y ->- let hy = head y- hx = head x- ty = tail y- tx = tail x- in if null x- then y- else f tx $ if sum (map T.length hy) + T.length hx <= n- then (hx:hy):ty- else [hx]:y)+sendUserList us uss rm = do mapM_ (\nms -> sendPacket $ printf "%s 353 %s = %s :%s" [hostname, us, rm, T.unwords nms]) chunkedNames+ sendPacket $ printf "%s 366 %s %s :End of /NAMES list." [hostname, us, rm]+ where names = map (\u -> T.concat [levelToSym $ privclassLevel u, username u]) uss+ chunkedNames = reverse . map reverse . subchunk' 432 names $ [[]]+ subchunk' n = fix (\f x y -> let hy = head y+ hx = head x+ ty = tail y+ tx = tail x+ in if null x+ then y+ else f tx $ if sum (map T.length hy) + T.length hx <= n+ then (hx:hy):ty+ else [hx]:y) -sendPong p =- sendPacket $ printf "%s PONG chat.deviantart.com :%s" [hostname, p]+sendPong p = sendPacket $ printf "%s PONG chat.deviantart.com :%s" [hostname, p] -sendNoticeClone uname i rm =- sendPacket $ printf "%s NOTICE %s :%s has joined again\- \ (now joined %s times)" [hostname, rm, uname, T.pack $ show i]+sendNoticeClone uname i rm = sendPacket $ printf "%s NOTICE %s :%s has joined again (now joined %s times)" [hostname, rm, uname, T.pack $ show i] -sendNoticeUnclone uname i rm =- sendPacket $ printf "%s NOTICE %s :%s has parted (now joined %s)"- [hostname, rm, uname, times]- where- times | i == 1 = "once"- | otherwise = T.pack (show i) <> " times"+sendNoticeUnclone uname i rm = sendPacket $ printf "%s NOTICE %s :%s has parted (now joined %s)" [hostname, rm, uname, times]+ where times | i == 1 = "once"+ | otherwise = T.pack (show i) <> " times" -sendWhoisReply me us rn rooms idle signon = do- sendPacket $ printf "%s 311 %s %s %s chat.deviantart.com * :%s"- [hostname, me, us, us, rn]- sendPacket $ printf "%s 307 %s %s :is a registered nick" [hostname, me, us]- sendPacket $ printf "%s 319 %s %s :%s"- [hostname, me, us, T.intercalate " " . map (T.cons '#') $ rooms]- sendPacket $ printf "%s 312 %s %s chat.deviantart.com :dAmn"- [hostname, me, us]- sendPacket $ printf "%s 317 %s %s %s %s :seconds idle, signon time"- [hostname, me, us, T.pack $ show idle, T.pack $ show signon]- sendPacket $ printf "%s 318 %s %s :End of /WHOIS list." [hostname, me, us]+sendWhoisReply me us rn rooms idle signon = do sendPacket $ printf "%s 311 %s %s %s chat.deviantart.com * :%s" [hostname, me, us, us, rn]+ sendPacket $ printf "%s 307 %s %s :is a registered nick" [hostname, me, us]+ sendPacket $ printf "%s 319 %s %s :%s" [hostname, me, us, T.intercalate " " . map (T.cons '#') $ rooms]+ sendPacket $ printf "%s 312 %s %s chat.deviantart.com :dAmn" [hostname, me, us]+ sendPacket $ printf "%s 317 %s %s %s %s :seconds idle, signon time" [hostname, me, us, T.pack $ show idle, T.pack $ show signon]+ sendPacket $ printf "%s 318 %s %s :End of /WHOIS list." [hostname, me, us] levelToSym :: Int -> T.Text levelToSym x | x > 0 && x <= 35 = ""@@ -163,9 +122,8 @@ | otherwise = "" levelToMode :: Int -> T.Text-levelToMode x = case levelToSym x of- "" -> ""- "+" -> "v"- "@" -> "o"- "~" -> "q"- _ -> error "levelToSym, what are you doing"+levelToMode x = case levelToSym x of "" -> ""+ "+" -> "v"+ "@" -> "o"+ "~" -> "q"+ _ -> error "levelToSym, what are you doing"
Kevin/Protocol.hs view
@@ -15,51 +15,43 @@ , handler_ id (return Nothing) ] mkKevin :: Socket -> IO (Maybe Kevin)-mkKevin sock = flip catches watchInterrupt . withSocketsDo $ do- (client, _, _) <- accept sock- hSetBuffering client NoBuffering- klogNow Blue "received a client"- s <- execStateT (C.getAuthInfo client False) def- damnSock <- connectTo "chat.deviantart.com" $ PortNumber 3900- hSetBuffering damnSock NoBuffering- logChan <- newChan- damnChan <- newChan- ircChan <- newChan- return . Just $ Kevin damnSock- client- damnChan- ircChan- s- mempty- mempty- mempty- mempty- False- logChan+mkKevin sock = flip catches watchInterrupt . withSocketsDo+ $ do (client, _, _) <- accept sock+ hSetBuffering client NoBuffering+ klogNow Blue "received a client"+ s <- execStateT (C.getAuthInfo client False) def+ damnSock <- connectTo "chat.deviantart.com" $ PortNumber 3900+ hSetBuffering damnSock NoBuffering+ logChan <- newChan+ damnChan <- newChan+ ircChan <- newChan+ return . Just $ Kevin damnSock+ client+ damnChan+ ircChan+ s+ mempty+ mempty+ mempty+ mempty+ False+ logChan mkListener :: Int -> IO Socket mkListener = listenOn . PortNumber . fromIntegral kevinServer :: Int -> IO ()-kevinServer n = do- sock <- mkListener n- putStrLn $ "Listening on port " ++ show n- forever $ do- kev <- mkKevin sock- case kev of- Just k -> listen k- Nothing -> return ()+kevinServer n = do sock <- mkListener n+ putStrLn $ "Listening on port " ++ show n+ forever $ do kev <- mkKevin sock+ case kev of Just k -> listen k+ Nothing -> return () listen :: Kevin -> IO ()-listen k = do- mvar <- newTVarIO k- runLogger (logger k)- runPrinter (dChan k) (damn k)- runPrinter (iChan k) (irc k)- forkIO . void $ runReaderT (bracket_ S.initialize- (S.cleanup >> io (closeClient k))- S.listen) mvar- forkIO . void $ runReaderT (bracket_ (return ())- (C.cleanup >> io (closeServer k))- C.listen) mvar- return ()+listen k = do mvar <- newTVarIO k+ runLogger (logger k)+ runPrinter (dChan k) (damn k)+ runPrinter (iChan k) (irc k)+ forkIO . void $ runReaderT (bracket_ S.initialize (S.cleanup >> io (closeClient k)) S.listen) mvar+ forkIO . void $ runReaderT (bracket_ (return ()) (C.cleanup >> io (closeServer k)) C.listen) mvar+ return ()
Kevin/Settings.hs view
@@ -7,9 +7,9 @@ data Settings = Settings { _name :: Text , _password :: Text , _authtoken :: Text- , _passed :: Bool- , _nicked :: Bool- , _usered :: Bool+ , _passed :: Bool+ , _nicked :: Bool+ , _usered :: Bool } deriving (Show) makeClassy ''Settings
Kevin/Types.hs view
@@ -39,35 +39,35 @@ type Chatroom = T.Text -data User = User { username :: T.Text- , privclass :: T.Text+data User = User { username :: T.Text+ , privclass :: T.Text , privclassLevel :: Int- , symbol :: T.Text- , realname :: T.Text- , typename :: T.Text- , gpc :: T.Text+ , symbol :: T.Text+ , realname :: T.Text+ , typename :: T.Text+ , gpc :: T.Text } deriving (Eq, Show) -type UserStore = M.Map Chatroom [User]+type UserStore = M.Map Chatroom [User] -type Privclasses = M.Map T.Text Int+type Privclasses = M.Map T.Text Int type PrivclassStore = M.Map Chatroom Privclasses-type Privclass = (T.Text, Int)+type Privclass = (T.Text, Int) -type Title = T.Text-type TitleStore = M.Map Chatroom Title+type Title = T.Text+type TitleStore = M.Map Chatroom Title -data Kevin = Kevin { damn :: Handle- , irc :: Handle- , dChan :: Chan T.Text- , iChan :: Chan T.Text+data Kevin = Kevin { damn :: Handle+ , irc :: Handle+ , dChan :: Chan T.Text+ , iChan :: Chan T.Text , _kevinSettings :: Settings- , _users :: UserStore- , _privclasses :: PrivclassStore- , _titles :: TitleStore- , _joining :: [T.Text]- , _loggedIn :: Bool- , logger :: Chan String+ , _users :: UserStore+ , _privclasses :: PrivclassStore+ , _titles :: TitleStore+ , _joining :: [T.Text]+ , _loggedIn :: Bool+ , logger :: Chan String } makeLenses ''Kevin@@ -78,11 +78,10 @@ type KevinS = StateT Kevin STM kevin :: KevinS a -> KevinIO a-kevin m = ask >>= \v -> liftIO $ atomically $ do- s <- readTVar v- (a, t) <- runStateT m s- writeTVar v t- return a+kevin m = ask >>= \v -> liftIO $ atomically $ do s <- readTVar v+ (a, t) <- runStateT m s+ writeTVar v t+ return a type KevinIO = ReaderT (TVar Kevin) IO
Kevin/Util/Entity.hs view
@@ -18,57 +18,48 @@ decodeCharacter = entityNumeric <|> entityNamed <|> take 1 entityNumeric :: Parser T.Text-entityNumeric = do- string "&#"- entity <- (<>) <$> option "" (string "x") <*> takeWhile1 isHexDigit- char ';'- return . fromMaybe (T.concat ["&#", entity, ";"])- $ (if "x" `T.isPrefixOf` entity - then lookupHexEntity- else lookupNumericEntity) entity+entityNumeric = do string "&#"+ entity <- (<>) <$> option "" (string "x") <*> takeWhile1 isHexDigit+ char ';'+ return . fromMaybe (T.concat ["&#", entity, ";"]) $ (if "x" `T.isPrefixOf` entity + then lookupHexEntity+ else lookupNumericEntity) entity entityNamed :: Parser T.Text-entityNamed = do- char '&'- entity <- T.cons <$> letter <*> takeWhile1 isAlphaNum- char ';'- return . fromMaybe (T.concat ["&", entity, ";"])- . lookupNamedEntity $ entity+entityNamed = do char '&'+ entity <- T.cons <$> letter <*> takeWhile1 isAlphaNum+ char ';'+ return . fromMaybe (T.concat ["&", entity, ";"]) . lookupNamedEntity $ entity decodeParser :: Parser T.Text decodeParser = T.concat <$> many1 decodeCharacter entityDecode :: T.Text -> T.Text entityDecode "" = ""-entityDecode str = case parseOnly decodeParser str of- Left err -> error $ "entityDecode: " ++ err- Right s -> s+entityDecode str = case parseOnly decodeParser str of Left err -> error $ "entityDecode: " ++ err+ Right s -> s entityEncode :: T.Text -> T.Text entityEncode = T.pack . concat . entityEncodeS . T.unpack entityEncodeS :: String -> [String]-entityEncodeS = fix (\f str -> case str of- [] -> []- (x:xs) -> if x < '\127' then [x]:f xs- else ("&#" ++ show (ord x) ++ ";"):f xs)+entityEncodeS = fix (\f str -> case str of [] -> []+ (x:xs) -> if x < '\127'+ then [x]:f xs+ else ("&#" ++ show (ord x) ++ ";"):f xs) lookupNamedEntity :: T.Text -> Maybe T.Text lookupNamedEntity ent = (T.singleton . chr) <$> lookup ent namedEntities lookupHexEntity :: T.Text -> Maybe T.Text-lookupHexEntity e = case R.hexadecimal $ T.cons '0' e of- Right (n,_) -> do- guard $ n < ord maxBound- return . T.singleton . chr $ n- Left _ -> Nothing+lookupHexEntity e = case R.hexadecimal $ T.cons '0' e of Right (n,_) -> do guard $ n < ord maxBound+ return . T.singleton . chr $ n+ Left _ -> Nothing lookupNumericEntity :: T.Text -> Maybe T.Text-lookupNumericEntity e = case R.decimal e of- Right (n,_) -> do- guard $ n < ord maxBound- return . T.singleton . chr $ n- Left _ -> Nothing+lookupNumericEntity e = case R.decimal e of Right (n,_) -> do guard $ n < ord maxBound+ return . T.singleton . chr $ n+ Left _ -> Nothing namedEntities :: [(T.Text, Int)] namedEntities = [ ("quot", 34), ("amp", 38), ("apos", 39), ("lt", 60)
Kevin/Util/Logger.hs view
@@ -42,8 +42,7 @@ ++ "m" ++ rtrimmed ++ "\027[0m" where- rtrimmed = reverse . dropWhile (\x -> isSpace x || x == '\x0')- . reverse $ str+ rtrimmed = reverse . dropWhile (\x -> isSpace x || x == '\x0') . reverse $ str klog_ :: Chan String -> Color -> String -> IO () klog_ ch col str = writeChan ch $ render col str
Kevin/Util/Tablump.hs view
@@ -13,15 +13,14 @@ import Text.Regex.PCRE.String fromRight :: (Show a) => Either a b -> b-fromRight (Left x) = error $ "fromRight on Left " ++ show x+fromRight (Left x) = error $ "fromRight on Left " ++ show x fromRight (Right a) = a {-# NOINLINE regexReplace #-} regexReplace :: Regex -> ([String] -> String) -> String -> String-regexReplace find replace = fix (\f str -> case fromRight . unsafePerformIO- $ regexec find str of- Just (bef, _, af, matches) -> concat [bef, replace matches, f af]- Nothing -> str)+regexReplace find replace = fix (\f str ->+ case fromRight . unsafePerformIO $ regexec find str of Just (bef, _, af, matches) -> concat [bef, replace matches, f af]+ Nothing -> str) {-# NOINLINE regexen #-} regexen :: [(Regex, [String] -> String)]@@ -29,53 +28,48 @@ first (fromRight . unsafePerformIO . compile defaultCompOpt defaultExecOpt) ) . reverse $ [- "&b\t" $$ const "\2",- "&/b\t" $$ const "\15",- "&i\t" $$ const "\22",- "&/i\t" $$ const "\15",- "&u\t" $$ const "\31",- "&/u\t" $$ const "\15",- "&s\t" $$ const "<s>",- "&/s\t" $$ const "</s>",- "&sup\t" $$ const "",- "&/sup\t" $$ const "",- "&sub\t" $$ const "",- "&/sub\t" $$ const "",- "&code\t" $$ const "",- "&/code\t" $$ const "",- "&br\t" $$ const "\n",- "&ul\t" $$ const "",- "&/ul\t" $$ const "",- "&ol\t" $$ const "",- "&/ol\t" $$ const "",- "&li\t" $$ const "- ",- "&/li\t" $$ const "\n",- "&bcode\t" $$ const "",- "&/bcode\t" $$ const "",- "&/a\t" $$ const ")",- "&/acro\t" $$ const "</acronym>",- "&/abbr\t" $$ const "</abbr>",- "&p\t" $$ const "",- "&/p\t" $$ const "\n",- "&emote\t(.+?)\t.+?\t.+?\t.+?\t.+?\t" $$ head,- "&a\t(.+?)\t.*?\t" $$ \(x:_) -> printf "%s (" x,- "&link\t(.+?)\t&\t" $$ head,- "&link\t(.+?)\t(.+?)\t&\t" $$ \(x:y:_) -> printf "%s (%s)" x y,- "&dev\t.+?\t(.+?)\t" $$ head,- "&avatar\t(.+?)\t.+?\t" $$ \(x:_) -> printf ":icon%s:" x,- "&thumb\t(.+?)\t.+?\t.+?\t.+?\t.+?\t.+?\t.+?\t" $$ \(x:_) ->- printf ":thumb%s:" x,- "&img\t(.+?)\t(.*?)\t(.*?)\t" $$ \(x:y:z:_) ->- printf "<img src='%s' alt='%s' title='%s' />" x y z,- "&iframe\t(.+?)\t(.*?)\t(.*?)\t" $$ \(x:y:z:_) ->- printf "<iframe src='%s' width='%s' height='%s' />" x y z,- "&acro\t(.+?)\t" $$ \(x:_) -> printf "<acronym title='%s'>" x,- "&abbr\t(.+?)\t" $$ \(x:_) -> printf "<abbr title='%s'>" x,- " ?<abbr title='colors:[0-9A-Fa-f]{6}:[0-9A-Fa-f]{6}'></abbr>"- $$ const "",- "^<abbr title='(.+?)'>.+?</abbr>:" $$ \(x:_) -> printf "%s:" x,- "^[a-zA-Z0-9\\-_]+<abbr title='(.+?)'></abbr>:" $$ \(x:_) ->- printf "%s:" x+ "&b\t" $$ const "\2",+ "&/b\t" $$ const "\15",+ "&i\t" $$ const "\22",+ "&/i\t" $$ const "\15",+ "&u\t" $$ const "\31",+ "&/u\t" $$ const "\15",+ "&s\t" $$ const "<s>",+ "&/s\t" $$ const "</s>",+ "&sup\t" $$ const "",+ "&/sup\t" $$ const "",+ "&sub\t" $$ const "",+ "&/sub\t" $$ const "",+ "&code\t" $$ const "",+ "&/code\t" $$ const "",+ "&br\t" $$ const "\n",+ "&ul\t" $$ const "",+ "&/ul\t" $$ const "",+ "&ol\t" $$ const "",+ "&/ol\t" $$ const "",+ "&li\t" $$ const "- ",+ "&/li\t" $$ const "\n",+ "&bcode\t" $$ const "",+ "&/bcode\t" $$ const "",+ "&/a\t" $$ const ")",+ "&/acro\t" $$ const "</acronym>",+ "&/abbr\t" $$ const "</abbr>",+ "&p\t" $$ const "",+ "&/p\t" $$ const "\n",+ "&emote\t(.+?)\t.+?\t.+?\t.+?\t.+?\t" $$ head,+ "&a\t(.+?)\t.*?\t" $$ \(x:_) -> printf "%s (" x,+ "&link\t(.+?)\t&\t" $$ head,+ "&link\t(.+?)\t(.+?)\t&\t" $$ \(x:y:_) -> printf "%s (%s)" x y,+ "&dev\t.+?\t(.+?)\t" $$ head,+ "&avatar\t(.+?)\t.+?\t" $$ \(x:_) -> printf ":icon%s:" x,+ "&thumb\t.+?\t(.+?)\t.+?\t.+?\t.+?\t.+?\t" $$ \(x:_) -> printf "[thumb: %s]" x,+ "&img\t(.+?)\t(.*?)\t(.*?)\t" $$ \(x:y:z:_) -> printf "<img src='%s' alt='%s' title='%s' />" x y z,+ "&iframe\t(.+?)\t(.*?)\t(.*?)\t" $$ \(x:y:z:_) -> printf "<iframe src='%s' width='%s' height='%s' />" x y z,+ "&acro\t(.+?)\t" $$ \(x:_) -> printf "<acronym title='%s'>" x,+ "&abbr\t(.+?)\t" $$ \(x:_) -> printf "<abbr title='%s'>" x,+ " ?<abbr title='colors:[0-9A-Fa-f]{6}:[0-9A-Fa-f]{6}'></abbr>" $$ const "",+ "^<abbr title='(.+?)'>.+?</abbr>:" $$ \(x:_) -> printf "%s:" x,+ "^[a-zA-Z0-9\\-_]+<abbr title='(.+?)'></abbr>:" $$ \(x:_) -> printf "%s:" x ] tablumpDecode :: T.Text -> T.Text
Kevin/Util/Token.hs view
@@ -16,54 +16,61 @@ import Text.Printf recvUntil :: TLSCtx -> B.ByteString -> IO B.ByteString-recvUntil ctx str = do- line <- recvData ctx- if str `B.isInfixOf` line- then return line- else fmap (line <>) $ recvUntil ctx str+recvUntil ctx str = do line <- recvData ctx+ if str `B.isInfixOf` line+ then return line+ else fmap (line <>) $ recvUntil ctx str concatHeaders :: [(String,String)] -> String concatHeaders = intercalate "\r\n" . map (\(x,y) -> x ++ ": " ++ y) +scrapeFormValue :: String -> B.ByteString -> B.ByteString+scrapeFormValue key bs = B.takeWhile (/='"') . B.drop 7 . head+ . filter (\l -> "value=" `B.isPrefixOf` l) . B.words . head+ . filter (\l -> B.pack key `B.isInfixOf` l) . B.lines $ bs++scrapeCookies :: B.ByteString -> B.ByteString+scrapeCookies bs = B.intercalate ";" . map snd+ . filter ((== "Set-Cookie") . fst)+ . map (second (B.drop 2 . B.takeWhile (/=';'))+ . B.breakSubstring ": ")+ . B.lines $ bs+ getToken :: T.Text -> T.Text -> IO (Maybe T.Text)-getToken uname pass = do- let params = defaultParams { pCiphers = ciphersuite_all- , onCertificatesRecv =- certificateChecks [return- . certificateVerifyDomain- "chat.deviantart.com"]- }- headers :: [(String, String)]- headers = [("Connection", "closed"),- ("Content-Type", "application/x-www-form-urlencoded")]- gen <- makeSystem- ctx <- connectionClient "www.deviantart.com" "443" params gen- let payload = urlEncodeVars [ ("username", T.unpack uname)- , ("password", T.unpack pass)- , ("remember_me","1")- ]- handshake ctx- sendData ctx . LB.pack- $ printf "POST /users/login HTTP/1.1\r\n%s\r\n\- \Content-Length: %d\r\n\r\n%s"- (concatHeaders $ ("Host", "www.deviantart.com"):headers)- (length payload)- payload- bs <- recvData ctx- if "wrong-password" `B.isInfixOf` bs- then return Nothing- else do- let cookie = B.intercalate ";" . map snd- . filter ((== "Set-Cookie") . fst)- . map (second (B.drop 2 . B.takeWhile (/=';'))- . B.breakSubstring ": ")- . B.lines $ bs- s = printf "GET /chat/Botdom HTTP/1.1\r\n%s\r\n\- \cookie: %s\r\n\r\n"- (concatHeaders [("Host", "chat.deviantart.com")])- (B.unpack cookie)- sendData ctx $ LB.pack s- bq <- recvUntil ctx "dAmnChat_Init"- return . (Just . decodeUtf8 . B.take 32 . B.tail- . B.dropWhile (/='"') . B.dropWhile (/=',') . snd)- . B.breakSubstring "dAmn_Login" $ bq+getToken uname pass = do let params = defaultParams { pCiphers = ciphersuite_all+ , onCertificatesRecv = certificateChecks [return . certificateVerifyDomain "chat.deviantart.com"] }+ headers :: [(String, String)]+ headers = [("Connection", "closed"), ("Content-Type", "application/x-www-form-urlencoded")]+ gen <- makeSystem+ ctv <- connectionClient "www.deviantart.com" "443" params gen+ handshake ctv+ sendData ctv . LB.pack $ "GET /users/login HTTP/1.1\r\nHost: www.deviantart.com\r\n\r\n"+ bl <- recvUntil ctv "validate_key"+ bye ctv++ let payload = urlEncodeVars [ ("username", T.unpack uname)+ , ("password", T.unpack pass)+ , ("validate_token", B.unpack (scrapeFormValue "validate_token" bl))+ , ("validate_key", B.unpack (scrapeFormValue "validate_key" bl))+ , ("remember_me","1")+ ]+ ctx <- connectionClient "www.deviantart.com" "443" params gen+ handshake ctx+ sendData ctx . LB.pack $ printf "POST /users/login HTTP/1.1\r\n%s\r\n\+ \cookie: %s\r\nContent-Length: %d\r\n\r\n%s"+ (concatHeaders $ ("Host", "www.deviantart.com"):headers)+ (B.unpack (scrapeCookies bl))+ (length payload)+ payload+ bs <- recvData ctx+ if "wrong-password" `B.isInfixOf` bs+ then return Nothing+ else do let s = printf "GET /chat/Botdom HTTP/1.1\r\n%s\r\n\+ \cookie: %s\r\n\r\n"+ (concatHeaders [("Host", "chat.deviantart.com")])+ (B.unpack (scrapeCookies bs))+ sendData ctx $ LB.pack s+ bq <- recvUntil ctx "dAmnChat_Init"+ return . (Just . decodeUtf8 . B.take 32 . B.tail+ . B.dropWhile (/='"') . B.dropWhile (/=',') . snd)+ . B.breakSubstring "dAmn_Login" $ bq
Main.hs view
@@ -9,30 +9,21 @@ data Flag = Port Int | Version | Help deriving (Eq) opts :: [OptDescr Flag]-opts = [- Option "p" ["port"] (ReqArg (Port . read) "number")- $ "local port to run the server on (defaults to "- ++ show defaultPort- ++ ")",- Option "h" ["help"] (NoArg Help) "print this message",- Option "v" ["version"] (NoArg Version) "show kevin's version number"- ]+opts = [ Option "p" ["port"] (ReqArg (Port . read) "number") $ "local port to run the server on (defaults to " ++ show defaultPort ++ ")"+ , Option "h" ["help"] (NoArg Help) "print this message"+ , Option "v" ["version"] (NoArg Version) "show kevin's version number" ] header :: String header = "Usage: kevin [options...]" getPort :: [Flag] -> Int getPort (Port x:_) = x-getPort (_:xs) = getPort xs-getPort [] = defaultPort+getPort (_:xs) = getPort xs+getPort [] = defaultPort main :: IO ()-main = do- args <- getArgs- case getOpt Permute opts args of- (flags, _, []) -> case flags of- f | Help `elem` f -> putStrLn $ usageInfo header opts- | Version `elem` f -> putStrLn - $ "kevin version " ++ showVersion version- | otherwise -> kevinServer $ getPort flags- (_, _, msgs@(_:_)) -> putStrLn $ concat msgs ++ usageInfo header opts+main = do args <- getArgs+ case getOpt Permute opts args of (flags, _, []) -> case flags of f | Help `elem` f -> putStrLn $ usageInfo header opts+ | Version `elem` f -> putStrLn $ "kevin version " ++ showVersion version+ | otherwise -> kevinServer $ getPort flags+ (_, _, msgs@(_:_)) -> putStrLn $ concat msgs ++ usageInfo header opts
kevin.cabal view
@@ -1,5 +1,5 @@ Name: kevin-Version: 0.8.2+Version: 0.9.0 Synopsis: a dAmn ↔ IRC proxy Description: a dAmn ↔ IRC proxy License: GPL@@ -18,7 +18,7 @@ Main-is: Main.hs Default-language: Haskell2010- + Build-Depends: attoparsec, base == 4.*, bytestring,@@ -36,7 +36,7 @@ time, tls, tls-extra- + Other-Modules: Kevin, Kevin.Base, Kevin.Chatrooms,@@ -54,6 +54,6 @@ Kevin.Util.Tablump, Kevin.Util.Token, Kevin.Version- + default-extensions: DeriveDataTypeable, ExistentialQuantification, FlexibleContexts, OverloadedStrings, ScopedTypeVariables, TemplateHaskell ghc-options: -Wall -fno-warn-unused-do-bind -threaded