# VPS Tracker — blocking launcher for RouterOS 7.22+
# First run: :global "vt-srcIp" "1.2.3.4"; /tool fetch url="__VT_API_URL__/cc.rsc" dst-path=vt-cc.rsc; /import file-name=vt-cc.rsc
# HTTPS GET only (no DPI/SNI). Ingest: POST /api/integrations/censorcheck/runs

:local vtApi "__VT_API_URL__"
:local vtToken "__VT_INGEST_TOKEN__"
:local vtDaily "__VT_DAILY__"
:local vtRemove "__VT_REMOVE_DAILY__"
:local launcherVer "ros-7"
:local schedName "vt-cc"
:local dstFile "vt-cc.rsc"

:local ver [/system resource get version]
:local dot [:find $ver "."]
:if ([:typeof $dot] = "nil") do={
  :error ("Need RouterOS 7.22+ (got " . $ver . ")")
}
:local major [:tonum [:pick $ver 0 $dot]]
:local rest [:pick $ver ($dot + 1) [:len $ver]]
:local cut [:len $rest]
:local d2 [:find $rest "."]
:local sp [:find $rest " "]
:if ([:typeof $d2] != "nil") do={ :set cut $d2 }
:if (([:typeof $sp] != "nil") and (($cut = [:len $rest]) or ($sp < $cut))) do={ :set cut $sp }
:local minor [:tonum [:pick $rest 0 $cut]]
:if (($major < 7) or (($major = 7) and ($minor < 22))) do={
  :error ("Need RouterOS 7.22+ (got " . $ver . ")")
}

:if ($vtRemove = "yes") do={
  :do { /system scheduler remove [find name=$schedName] } on-error={}
  :put ("Ежедневная проверка снята (" . $schedName . ")")
} else={

:put ("censorcheck launcher " . $launcherVer . " (RouterOS)")

:global "vt-srcIp"
:if (([:typeof $"vt-srcIp"] != "str") or ([:len $"vt-srcIp"] = 0)) do={
  :put "Задайте исходный IP и повторите импорт:"
  :put ":global \"vt-srcIp\" \"1.2.3.4\""
  :error "vt-srcIp не задан"
}
:local srcIp $"vt-srcIp"
:put ("vt-srcIp: " . $srcIp)
:local ingestOk false

:local r
:local publicIp ""
:do {
  :set r [/tool fetch url="https://api.ipify.org" src-address=$srcIp output=user as-value]
  :if (($r->"status") = "finished") do={
    :set publicIp ($r->"data")
  }
} on-error={}
:local cr [:find $publicIp "\r"]
:if ([:typeof $cr] != "nil") do={ :set publicIp [:pick $publicIp 0 $cr] }
:local lf [:find $publicIp "\n"]
:if ([:typeof $lf] != "nil") do={ :set publicIp [:pick $publicIp 0 $lf] }
:if ([:len $publicIp] = 0) do={
  :do {
    :set r [/tool fetch url="https://ifconfig.me/ip" src-address=$srcIp output=user as-value]
    :if (($r->"status") = "finished") do={
      :set publicIp ($r->"data")
    }
  } on-error={}
  :set cr [:find $publicIp "\r"]
  :if ([:typeof $cr] != "nil") do={ :set publicIp [:pick $publicIp 0 $cr] }
  :set lf [:find $publicIp "\n"]
  :if ([:typeof $lf] != "nil") do={ :set publicIp [:pick $publicIp 0 $lf] }
}
:if ([:len $publicIp] = 0) do={
  :error "Не удалось определить публичный IP"
}

:local hoster ""
:do {
  :set r [/tool fetch url=("https://ipwho.is/" . $publicIp) src-address=$srcIp output=user as-value]
  :if (($r->"status") = "finished") do={
    :local j [:deserialize from=json value=($r->"data")]
    :local isp ($j->"connection"->"isp")
    :if ([:typeof $isp] = "str") do={ :set hoster $isp }
    :if ([:len $hoster] = 0) do={
      :local org ($j->"org")
      :if ([:typeof $org] = "str") do={ :set hoster $org }
    }
  }
} on-error={}

:local runId ("mt-" . [:rndstr length=16])
:put ("probe IP: " . $publicIp)
:if ([:len $hoster] > 0) do={ :put ("хостер: " . $hoster) }
:put ("runId: " . $runId)
:put "Проверяю сайты (HTTPS GET, без DPI)..."

:local hosts {\
  "youtube.com";"redirector.googlevideo.com";"discord.com";"instagram.com";"facebook.com";\
  "x.com";"linkedin.com";"rutracker.org";"digitalocean.com";"amnezia.org";"getoutline.org";\
  "mailfence.com";"flibusta.is";"rezka.ag";"api.telegram.org";"play.google.com";\
  "spotify.com";"netflix.com";"patreon.com";"swagger.io";"snyk.io";"mongodb.com";\
  "autodesk.com";"graylog.org";"redis.io";"copilot.microsoft.com"\
}

:local resultJson ""
:local itemJson ""
:local code 0
:local data
:local line
:local nl
:local cr2
:local sp1
:local rest2
:local sp2
:local codeStr
:local n
:foreach host in=$hosts do={
  :set code 0
  :do {
    :set r [/tool fetch url=("https://" . $host . "/") src-address=$srcIp output=user-with-headers as-value]
    :if (($r->"status") != "finished") do={
      :set code -1
    } else={
      :set data ($r->"data")
      :set line $data
      :set nl [:find $data "\n"]
      :if ([:typeof $nl] != "nil") do={ :set line [:pick $data 0 $nl] }
      :set cr2 [:find $line "\r"]
      :if ([:typeof $cr2] != "nil") do={ :set line [:pick $line 0 $cr2] }
      :if ([:pick $line 0 4] = "HTTP") do={
        :set sp1 [:find $line " "]
        :if ([:typeof $sp1] != "nil") do={
          :set rest2 [:pick $line ($sp1 + 1) [:len $line]]
          :set sp2 [:find $rest2 " "]
          :set codeStr $rest2
          :if ([:typeof $sp2] != "nil") do={ :set codeStr [:pick $rest2 0 $sp2] }
          :set n [:tonum $codeStr]
          :if ([:typeof $n] = "num") do={ :set code $n } else={ :set code 200 }
        } else={ :set code 200 }
      } else={ :set code 200 }
    }
  } on-error={
    :set code 0
  }
  :put ($host . " " . $code)
  :set itemJson ("{\"service\":\"" . $host . "\",\"raw\":{\"https\":{\"ipv4\":{\"status\":" . $code . "}}}}")
  :if ([:len $resultJson] > 0) do={ :set resultJson ($resultJson . ",") }
  :set resultJson ($resultJson . $itemJson)
}

:local hosterJson ""
:if ([:len $hoster] > 0) do={
  :local esc ""
  :local hi 0
  :while ($hi < [:len $hoster]) do={
    :local ch [:pick $hoster $hi ($hi + 1)]
    :if ($ch = "\\") do={ :set esc ($esc . "\\\\") } else={
      :if ($ch = "\"") do={ :set esc ($esc . "\\\"") } else={ :set esc ($esc . $ch) }
    }
    :set hi ($hi + 1)
  }
  :set hosterJson (",\"hoster\":\"" . $esc . "\"")
}
:if ([:len $resultJson] = 0) do={ :error "ingest: results пустой" }
:local json ("{\"schemaVersion\":1,\"runId\":\"" . $runId . "\",\"probe\":{\"publicIp\":\"" . $publicIp . "\"" . $hosterJson . "},\"launcherVersion\":\"" . $launcherVer . "\",\"censorcheck\":{\"version\":\"ros\",\"mode\":\"https\"},\"results\":[" . $resultJson . "]}")
:local hdrs ("Content-Type: application/json,Authorization: Bearer " . $vtToken)
:put ("Отправляю ingest (" . [:len $json] . " B)...")
:set ingestOk false
:onerror err,attr in={
  :set r [/tool fetch url=($vtApi . "/api/integrations/censorcheck/runs") src-address=$srcIp http-method=post http-header-field=$hdrs http-data=$json output=user as-value]
  :put ($r->"data")
  :set ingestOk true
} do={
  :put ("ingest: " . $err)
  :if (([:typeof $attr] = "array") and ([:typeof ($attr->"code")] != "nil")) do={
    :put ("code: " . ($attr->"code"))
  }
  :if (([:typeof $attr] = "array") and ([:typeof ($attr->"data")] = "str") and ([:len ($attr->"data")] > 0)) do={
    :put ($attr->"data")
  }
  :if ([:len $json] > 120) do={ :put ([:pick $json 0 120] . "...") } else={ :put $json }
}
:if ($ingestOk = false) do={
  :put "повтор ingest без src-address..."
  :onerror err2,attr2 in={
    :set r [/tool fetch url=($vtApi . "/api/integrations/censorcheck/runs") http-method=post http-header-field=$hdrs http-data=$json output=user as-value]
    :put ($r->"data")
  } do={
    :put ("ingest retry: " . $err2)
    :if (([:typeof $attr2] = "array") and ([:typeof ($attr2->"data")] = "str") and ([:len ($attr2->"data")] > 0)) do={
      :put ($attr2->"data")
    }
    :if ([:len $json] > 120) do={ :put ([:pick $json 0 120] . "...") } else={ :put $json }
  }
}

:if ($vtDaily = "yes") do={
  :local ident [/system identity get name]
  :local h ([:len $ident] + [:len [/system resource get architecture-name]])
  :local hour (3 + ($h % 4))
  :local minute (($h * 17) % 60)
  :local hh $hour
  :local mm $minute
  :if ($hour < 10) do={ :set hh ("0" . $hour) }
  :if ($minute < 10) do={ :set mm ("0" . $minute) }
  :local startTime ($hh . ":" . $mm . ":00")
  :local ev (":global \"vt-srcIp\" \"" . $srcIp . "\"; /tool fetch url=" . $vtApi . "/cc.rsc dst-path=" . $dstFile . "; /import file-name=" . $dstFile)
  :do { /system scheduler remove [find name=$schedName] } on-error={}
  /system scheduler add name=$schedName interval=1d start-time=$startTime on-event=$ev policy=read,write,test,policy comment="vps-tracker vt-cc"
  :put ("Ежедневная проверка: каждый день в " . $startTime . " (" . $schedName . ")")
  :put ("Снять: /tool fetch url=" . $vtApi . "/cc.rsc?remove=daily dst-path=" . $dstFile . "; /import file-name=" . $dstFile)
}

}
