#!/bin/bash
perform_check_request() {
local access_id="$1"
local access_code="$2"
shift 2
local -a ip_list=("$@")
local timeout=30
# === Input Validation ===
if [[ -z "$access_id" ]]; then
echo "Error: access_id must be a non-empty string" >&2
return 1
fi
if [[ -z "$access_code" ]]; then
echo "Error: access_code must be a non-empty string" >&2
return 1
fi
if [[ ${#ip_list[@]} -eq 0 ]]; then
echo "Error: ip_list must be a non-empty list of strings" >&2
return 1
fi
local url="https://www.serversnug.com/api/public/v1/check/"
# === Build JSON payload safely using jq ===
local payload
payload=$(jq -n \
--arg access_id "$access_id" \
--arg access_code "$access_code" \
--argjson ip "$(printf '%s\n' "${ip_list[@]}" | jq -R . | jq -s .)" \
'{access_id: $access_id, access_code: $access_code, ip: $ip}') || {
echo "Error: Failed to construct JSON payload" >&2
return 1
}
# === Make the request ===
local tmpfile
tmpfile=$(mktemp) || {
echo "Error: Failed to create temporary file" >&2
return 1
}
local http_code
http_code=$(curl -sS --max-time "$timeout" \
-w "%{http_code}" \
-o "$tmpfile" \
-X POST \
-H "Content-Type: application/json" \
-d "$payload" \
"$url")
local curl_exit=$?
local response_body
response_body=$(<"$tmpfile")
rm -f "$tmpfile"
# Handle curl-level failures
if [[ $curl_exit -ne 0 ]]; then
if [[ $curl_exit -eq 28 ]]; then
echo "Error: Request timed out while contacting ServerSnug API" >&2
else
echo "Error: Failed to connect to ServerSnug API (network issue)" >&2
fi
return 1
fi
# === Validate JSON response ===
if ! echo "$response_body" | jq empty > /dev/null 2>&1; then
echo "Error: Invalid JSON response from server. Status code: $http_code" >&2
return 1
fi
# === Handle HTTP status codes ===
case "$http_code" in
403)
echo "Error: 403 Forbidden - Credentials not recognized" >&2
return 1
;;
500)
echo "Error: 500 Internal Server Error - ServerSnug service issue" >&2
return 1
;;
502)
echo "Error: 502 Bad Gateway - ServerSnug service issue" >&2
return 1
;;
503)
echo "Error: 503 Service Unavailable - ServerSnug under maintenance" >&2
return 1
;;
422)
local err
err=$(echo "$response_body" | jq -r '.error // "Unknown error"')
echo "Error: 422 Processing Error: $err" >&2
return 1
;;
esac
if [[ "$http_code" != "200" ]]; then
echo "Error: Unexpected HTTP status: $http_code" >&2
return 1
fi
# === Check for API-level errors (even on 200) ===
local api_error
api_error=$(echo "$response_body" | jq -r '.error // empty')
if [[ -n "$api_error" ]]; then
case "$api_error" in
"Allowance exceeded")
echo "Error: Allowance exceeded - fewer (or no) results returned" >&2
;;
"Malformed request")
echo "Error: Malformed request - check JSON format" >&2
;;
*)
echo "Error: API error: $api_error" >&2
;;
esac
return 1
fi
# === Validate response structure ===
if ! echo "$response_body" | jq -e 'has("count") and has("response")' > /dev/null; then
echo "Error: Response missing required fields ('count' or 'response')" >&2
return 1
fi
# Success → output JSON
echo "$response_body"
return 0
}
import requests
import json
from typing import List, Dict, Any
def perform_check_request(
access_id: str,
access_code: str,
ip_list: List[str],
timeout: int = 30
) -> Dict[str, Any]:
"""
Performs a ServerSnug 'check' request with full error handling.
Returns the parsed JSON response on success.
Raises detailed exceptions for any failure.
"""
# Input validation
if not access_id or not isinstance(access_id, str):
raise ValueError("access_id must be a non-empty string")
if not access_code or not isinstance(access_code, str):
raise ValueError("access_code must be a non-empty string")
if not isinstance(ip_list, list) or not ip_list:
raise ValueError("ip_list must be a non-empty list of strings")
if not all(isinstance(ip, str) for ip in ip_list):
raise ValueError("All elements in ip_list must be strings (IP addresses)")
url = "https://www.serversnug.com/api/public/v1/check/"
payload = {
"access_id": access_id,
"access_code": access_code,
"ip": ip_list
}
try:
response = requests.post(
url,
json=payload, # ensures proper JSON Content-Type
timeout=timeout,
allow_redirects=False
)
except requests.exceptions.Timeout:
raise ConnectionError("Request timed out while contacting ServerSnug API")
except requests.exceptions.ConnectionError:
raise ConnectionError("Failed to connect to ServerSnug API (network issue)")
except requests.exceptions.RequestException as e:
raise ConnectionError(f"Request failed: {e}")
# Always attempt to parse JSON even on error status codes
try:
data: Dict[str, Any] = response.json()
except json.JSONDecodeError:
raise ValueError(f"Invalid JSON response from server. Status code: {response.status_code}")
except Exception as e:
raise ValueError(f"Failed to parse response: {e}")
# Handle HTTP-level errors first
if response.status_code == 403:
raise PermissionError("403 Forbidden - Credentials not recognized")
if response.status_code == 500:
raise RuntimeError("500 Internal Server Error - ServerSnug service issue")
if response.status_code == 502:
raise RuntimeError("502 Internal Server Error - ServerSnug service issue")
if response.status_code == 503:
raise RuntimeError("503 Service Unavailable - ServerSnug under maintenance")
if response.status_code == 422:
error_msg = data.get("error") # Error is Missing ip
raise RuntimeError("422 Processing Error: {error_msg}")
if response.status_code != 200:
raise RuntimeError(f"Unexpected HTTP status: {response.status_code}")
# Check for API-level error message (returned with 200)
error_msg = data.get("error")
if error_msg:
if error_msg == "Allowance exceeded":
raise RuntimeError("Allowance exceeded - fewer (or no) results returned")
if error_msg == "Malformed request":
raise ValueError("Malformed request - check JSON format")
raise RuntimeError(f"API error: {error_msg}")
# Validate expected success structure
if "count" not in data or "response" not in data:
raise ValueError("Response missing required fields ('count' or 'response')")
return data
<?php
/**
* Performs a ServerSnug 'check' request with full error handling.
* Returns the parsed JSON response on success.
* Throws detailed exceptions for any failure.
*/
function perform_check_request(
string $access_id,
string $access_code,
array $ip_list,
int $timeout = 30
): array {
// =====================
// Input validation
// =====================
if (empty($access_id)) {
throw new InvalidArgumentException("access_id must be a non-empty string");
}
if (empty($access_code)) {
throw new InvalidArgumentException("access_code must be a non-empty string");
}
if (!is_array($ip_list) || empty($ip_list)) {
throw new InvalidArgumentException("ip_list must be a non-empty list of strings");
}
foreach ($ip_list as $ip) {
if (!is_string($ip)) {
throw new InvalidArgumentException("All elements in ip_list must be strings (IP addresses)");
}
}
$url = "https://www.serversnug.com/api/public/v1/check/";
$payload = [
"access_id" => $access_id,
"access_code" => $access_code,
"ip" => $ip_list
];
// =====================
// cURL request
// =====================
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$responseBody = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$curlErrno = curl_errno($ch);
curl_close($ch);
// =====================
// Network / cURL errors
// =====================
if ($curlErrno !== 0) {
if ($curlErrno === CURLE_OPERATION_TIMEDOUT) {
throw new RuntimeException("Request timed out while contacting ServerSnug API");
}
throw new RuntimeException("Failed to connect to ServerSnug API (network issue): " . $curlError);
}
// =====================
// Parse JSON response
// =====================
$data = json_decode($responseBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException(
"Invalid JSON response from server. Status code: {$httpCode} - " . json_last_error_msg()
);
}
// =====================
// HTTP status code handling
// =====================
if ($httpCode === 403) {
throw new RuntimeException("403 Forbidden - Credentials not recognized");
}
if ($httpCode === 500) {
throw new RuntimeException("500 Internal Server Error - ServerSnug service issue");
}
if ($httpCode === 502) {
throw new RuntimeException("502 Bad Gateway - ServerSnug service issue");
}
if ($httpCode === 503) {
throw new RuntimeException("503 Service Unavailable - ServerSnug under maintenance");
}
if ($httpCode === 422) {
$errorMsg = $data['error'] ?? 'Unknown processing error';
throw new RuntimeException("422 Processing Error: " . $errorMsg);
}
if ($httpCode !== 200) {
throw new RuntimeException("Unexpected HTTP status: {$httpCode}");
}
// =====================
// API-level error messages (can come with 200)
// =====================
if (!empty($data['error'])) {
$errorMsg = $data['error'];
if ($errorMsg === "Allowance exceeded") {
throw new RuntimeException("Allowance exceeded - fewer (or no) results returned");
}
if ($errorMsg === "Malformed request") {
throw new InvalidArgumentException("Malformed request - check JSON format");
}
throw new RuntimeException("API error: " . $errorMsg);
}
// =====================
// Validate expected response structure
// =====================
if (!isset($data['count']) || !isset($data['response'])) {
throw new RuntimeException("Response missing required fields ('count' or 'response')");
}
return $data;
}
?>
/**
* Performs a ServerSnug 'check' request with full error handling.
* Returns the parsed JSON response on success.
* Throws detailed Error objects for any failure.
*/
async function performCheckRequest(accessId, accessCode, ipList, timeout = 30) {
// =====================
// Input validation
// =====================
if (!accessId || typeof accessId !== 'string') {
throw new Error("access_id must be a non-empty string");
}
if (!accessCode || typeof accessCode !== 'string') {
throw new Error("access_code must be a non-empty string");
}
if (!Array.isArray(ipList) || ipList.length === 0) {
throw new Error("ip_list must be a non-empty list of strings");
}
if (!ipList.every(ip => typeof ip === 'string')) {
throw new Error("All elements in ip_list must be strings (IP addresses)");
}
const url = "https://www.serversnug.com/api/public/v1/check/";
const payload = {
access_id: accessId,
access_code: accessCode,
ip: ipList
};
// =====================
// Timeout handling with AbortController
// =====================
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout * 1000);
let response;
try {
response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
signal: controller.signal,
});
} catch (err) {
if (err.name === 'AbortError') {
throw new Error("Request timed out while contacting ServerSnug API");
}
throw new Error("Failed to connect to ServerSnug API (network issue): " + err.message);
} finally {
clearTimeout(timeoutId);
}
// =====================
// Parse JSON (even on error status codes)
// =====================
let data;
try {
data = await response.json();
} catch (err) {
throw new Error(`Invalid JSON response from server. Status code: ${response.status}`);
}
// =====================
// HTTP status code handling
// =====================
if (response.status === 403) {
throw new Error("403 Forbidden - Credentials not recognized");
}
if (response.status === 500) {
throw new Error("500 Internal Server Error - ServerSnug service issue");
}
if (response.status === 502) {
throw new Error("502 Bad Gateway - ServerSnug service issue");
}
if (response.status === 503) {
throw new Error("503 Service Unavailable - ServerSnug under maintenance");
}
if (response.status === 422) {
const errorMsg = data?.error || 'Unknown processing error';
throw new Error(`422 Processing Error: ${errorMsg}`);
}
if (response.status !== 200) {
throw new Error(`Unexpected HTTP status: ${response.status}`);
}
// =====================
// API-level error messages (can appear even with status 200)
// =====================
if (data?.error) {
const errorMsg = data.error;
if (errorMsg === "Allowance exceeded") {
throw new Error("Allowance exceeded - fewer (or no) results returned");
}
if (errorMsg === "Malformed request") {
throw new Error("Malformed request - check JSON format");
}
throw new Error(`API error: ${errorMsg}`);
}
// =====================
// Validate expected response structure
// =====================
if (!('count' in data) || !('response' in data)) {
throw new Error("Response missing required fields ('count' or 'response')");
}
return data;
}
export default performCheckRequest;