module Main where import Net.IP ( IP ) import qualified Net.IP as IP import qualified Net.IPv4 as NIPv4 import qualified Net.IPv6 as NIPv6 import qualified Data.Text as T import System.Directory ( doesFileExist, getXdgDirectory, XdgDirectory(XdgConfig) ) import System.FilePath (()) import Data.Set (empty, Set, insert, delete, toList) import Data.String ( IsString(fromString) ) import Data.Maybe (catMaybes) data Bound = Inbound | Outbound deriving (Show, Eq, Ord) data LogRecord = Connected Bound IP | Disconnected Bound IP deriving (Show, Eq) parseBound :: String -> Maybe Bound parseBound "Inbound" = Just Inbound parseBound "Outbound" = Just Outbound parseBound _ = Nothing parseLogRecord :: String -> Maybe LogRecord parseLogRecord s = case words s of ["Connected", b, ipTxt] -> do bound <- parseBound b ip <- IP.decode . fromString $ ipTxt pure (Connected bound ip) ["Disconnected", b, ipTxt] -> do bound <- parseBound b ip <- IP.decode . fromString $ ipTxt pure (Disconnected bound ip) _ -> Nothing type Connection = (Bound, IP) data SuperIP = Address IP | Range4 NIPv4.IPv4Range | Range6 NIPv6.IPv6Range inSuper :: SuperIP -> IP -> Bool inSuper (Address i0) i1 = i0 == i1 inSuper (Range4 r4) ip = IP.case_ (\ip4 -> NIPv4.member ip4 r4) (\_ -> False) ip inSuper (Range6 r6) ip = IP.case_ (\_ -> False) (\ip6 -> NIPv6.member ip6 r6) ip instance Read SuperIP where readsPrec :: Int -> ReadS SuperIP readsPrec _ c | Just i <- IP.decode s = [(Address i, "")] | Just r4 <- NIPv4.decodeRange s = [(Range4 r4, "")] | Just r6 <- NIPv6.decodeRange s = [(Range6 r6, "")] | otherwise = [] where s = fromString c instance Show SuperIP where show (Address ip) = show ip show (Range4 r) = show r show (Range6 r) = show r f :: Set Connection -> LogRecord -> Set Connection f s (Connected b ip) = insert (b, ip) s f s (Disconnected b ip) = delete (b, ip) s main :: IO () main = do dir <- getXdgDirectory XdgConfig "yggscraper" let path = dir "blacklist" exists <- doesFileExist path blacklist <- if exists then do blacklistFile <- readFile (dir "blacklist") return . map (\x -> read x :: SuperIP) . lines $ blacklistFile else return [] strs <- getContents let xs = catMaybes . map parseLogRecord . lines $ strs online = toList $ foldl' f empty xs finder (_, ip) = any (\x -> inSuper x ip) notBlacklisted = [x | x <- online, not $ finder x blacklist] putStrLn . T.unpack . T.unlines . map (IP.encode . snd) $ notBlacklisted