yggscraper/yggscraper.hs

101 lines
2.6 KiB
Haskell
Raw Permalink Normal View History

2026-03-16 00:58:03 +03:00
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