commit d504ccf9928a94d7e6682a84e3193be4e1c54b5a Author: Gregory Bednov Date: Mon Mar 16 00:58:03 2026 +0300 i made this script-like code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b45255 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.hi +*.o +input diff --git a/README.md b/README.md new file mode 100644 index 0000000..fae455a --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +VERY dumb and simple attempt to catch some computers online if they're connected to you through the Yggdrasil and you're the peer to them. + +Use with command + +``` +sudo journalctl -xeu yggdrasil --no-pager -o json-pretty | jq ".MESSAGE" -r | cut -f3- -d' ' | sed 's/inbound:/Inbound/; s/outbound:/Outbound/' | cut -f1 -d@ | sed 's/$/"/;' | ./yggscraper +``` \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..7e73050 --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = [ + pkgs.jq + (pkgs.haskellPackages.ghcWithPackages (p: with p; [ + ip + haskell-language-server + ])) + ]; +} + diff --git a/yggscraper b/yggscraper new file mode 100755 index 0000000..c1557f5 Binary files /dev/null and b/yggscraper differ diff --git a/yggscraper.hs b/yggscraper.hs new file mode 100644 index 0000000..a5cd23e --- /dev/null +++ b/yggscraper.hs @@ -0,0 +1,101 @@ +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 \ No newline at end of file