Skip to content

Instantly share code, notes, and snippets.

@jsCommander
Created January 2, 2026 10:02
Show Gist options
  • Select an option

  • Save jsCommander/2078a56b9c449ec08967d6a58b31f0f3 to your computer and use it in GitHub Desktop.

Select an option

Save jsCommander/2078a56b9c449ec08967d6a58b31f0f3 to your computer and use it in GitHub Desktop.
gos uslugi slot checker
import { exec } from 'child_process';
import { appendFileSync, mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
// --- CONFIGURATION ---
const CONFIG = {
COOKIES_FILE: 'data/cookies.json',
SCHEDULE: {
TICK_INTERVAL_SEC: 5, // Pulse of the runner
MIN_INTERVAL_SEC: 10, // Global floor between requests
NORMAL_INTERVAL_SEC: 120, // 2 minutes
SESSION_CHECK_INTERVAL_SEC: 300, // 5 minutes
IMPORTANT_RANGES: [
{ start: '00:00', end: '00:10', intervalSec: 20 },
{ start: '07:00', end: '09:30', intervalSec: 60 },
]
},
CHECK_SESSION_URL: 'https://www.gosuslugi.ru/auth-provider/check-session',
URL: 'https://www.gosuslugi.ru/api/lk/v1/equeue/agg/slots',
LOGS_DIR: 'logs',
STATIC_HEADERS: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/146.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.5',
'X-ORDER-ID': 'undefined',
'X-FORM-ID': '600300/1',
'Content-Type': 'application/json',
},
PAYLOAD: {
organizationId: ['10003871712'],
serviceId: ['10001971213'],
eserviceId: '10000000300',
attributes: [],
filter: null,
},
};
let COOKIES: Record<string, string> = {};
// --- PERSISTENCE ---
function saveCookies() {
try {
const dir = dirname(CONFIG.COOKIES_FILE);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(CONFIG.COOKIES_FILE, JSON.stringify(COOKIES, null, 2));
} catch (error) {
console.error(`Failed to save cookies: ${error}`);
}
}
function loadCookies() {
try {
if (existsSync(CONFIG.COOKIES_FILE)) {
const data = readFileSync(CONFIG.COOKIES_FILE, 'utf-8');
COOKIES = JSON.parse(data);
} else {
logger('ERROR', `Cookies file not found at ${CONFIG.COOKIES_FILE}. Please create it.`);
process.exit(1);
}
} catch (error) {
logger('ERROR', `Failed to load cookies: ${error}`);
process.exit(1);
}
}
loadCookies();
const stringifyCookies = (cookiesObj: Record<string, string>) =>
Object.entries(cookiesObj)
.map(([key, value]) => `${key}=${value}`)
.join('; ');
const parseSetCookie = (setCookieHeaders: string[] | string | null): Record<string, string> => {
if (!setCookieHeaders) return {};
const cookies: Record<string, string> = {};
const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
headers.forEach(header => {
const parts = header.split(';')[0].split('=');
if (parts.length >= 2) {
const key = parts[0].trim();
const value = parts.slice(1).join('=').trim();
cookies[key] = value;
}
});
return cookies;
};
function updateCookies(setCookieHeader: string[] | string | null) {
if (!setCookieHeader) return;
const newCookies = parseSetCookie(setCookieHeader);
const diff: Record<string, { old: string, new: string }> = {};
Object.entries(newCookies).forEach(([key, value]) => {
if (COOKIES[key] !== value) {
diff[key] = { old: COOKIES[key] || '(none)', new: value };
COOKIES[key] = value; // Merge/Update
}
});
if (Object.keys(diff).length > 0) {
logger('INFO', 'Cookies Updated', diff);
saveCookies();
} else {
logger('INFO', 'No cookies were updated.');
}
}
// Ensure logs directory exists
if (!existsSync(CONFIG.LOGS_DIR)) {
mkdirSync(CONFIG.LOGS_DIR);
}
// --- STATE ---
let lastCheckTimestamp = 0;
let lastSessionCheckTimestamp = 0;
let isChecking = false;
// --- LOGGING ---
function formatLocalISO(date: Date): string {
const z = date.getTimezoneOffset() * 60000;
return new Date(date.getTime() - z).toISOString().slice(0, -1);
}
function logger(level: 'INFO' | 'ERROR' | 'FOUND' | 'TICK', message: string, data?: any) {
const now = new Date();
const timestamp = formatLocalISO(now);
const dataStr = data ? ` | ${JSON.stringify(data)}` : '';
const logEntry = `[${timestamp}] [${level}] ${message}${dataStr}`;
// 1. Console Output
if (level === 'ERROR') {
console.error(logEntry);
} else {
console.log(logEntry);
}
// 2. File Output
const date = timestamp.split('T')[0];
const fileEntry = logEntry + '\n';
// Main log file (per date)
const logFile = join(CONFIG.LOGS_DIR, `${date}.log`);
appendFileSync(logFile, fileEntry);
// Special logs
if (level === 'FOUND') {
appendFileSync(join(CONFIG.LOGS_DIR, 'success.log'), fileEntry);
}
if (level === 'ERROR') {
appendFileSync(join(CONFIG.LOGS_DIR, 'error.log'), fileEntry);
}
}
// --- MONITORING LOGIC ---
function getActiveRange() {
const now = new Date();
const currentTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
return CONFIG.SCHEDULE.IMPORTANT_RANGES.find(range => {
if (range.start <= range.end) {
return currentTime >= range.start && currentTime <= range.end;
} else {
// Handle ranges crossing midnight (e.g., 23:50 - 00:10)
return currentTime >= range.start || currentTime <= range.end;
}
});
}
function getRequiredIntervalMs(): number {
const range = getActiveRange();
const seconds = range ? range.intervalSec : CONFIG.SCHEDULE.NORMAL_INTERVAL_SEC;
const finalSeconds = Math.max(seconds, CONFIG.SCHEDULE.MIN_INTERVAL_SEC);
return finalSeconds * 1000;
}
async function checkSlots() {
try {
const response = await fetch(CONFIG.URL, {
method: 'POST',
headers: {
...CONFIG.STATIC_HEADERS,
'Cookie': stringifyCookies(COOKIES),
},
body: JSON.stringify(CONFIG.PAYLOAD),
});
const data = (await response.json()) as any;
// Process and merge response cookies
const newSetCookies = (response.headers as any).getSetCookie?.() || response.headers.get('set-cookie')
updateCookies(newSetCookies);
if (!response.ok) {
const msg = `API request failed (${response.status})`;
logger('ERROR', msg, data);
notify('Gosuslugi Error', `${msg}: ${JSON.stringify(data)}`);
process.exit(1);
}
const slots = Array.isArray(data) ? data : (data.slots || []);
const hasSlots = slots.length > 0;
if (hasSlots) {
const msg = `Found ${slots.length} available slots.`;
logger('FOUND', msg, data);
notify('Gosuslugi Slots Found!', msg);
} else {
logger('INFO', 'Слотов пока нет.', data);
}
} catch (error) {
const errorMsg = `Unexpected error: ${String(error)}`;
logger('ERROR', errorMsg);
notify('Gosuslugi Monitor Error', errorMsg);
process.exit(1);
}
}
function notify(title: string, message: string) {
// Экранируем кавычки и спецсимволы для безопасной передачи в shell
const escapedMessage = message.replace(/"/g, '\\"').replace(/`/g, '\\`').replace(/\$/g, '\\$');
const command = `notify-send "${title}" "${escapedMessage}" --urgency=critical`;
exec(command, (error) => {
if (error) {
logger('ERROR', `Failed to send notification: ${error.message}`);
}
});
}
// --- RUNNER ---
async function tick() {
if (isChecking) {
return
};
const now = Date.now();
const requiredIntervalMs = getRequiredIntervalMs();
const timeSinceLastCheck = now - lastCheckTimestamp;
if (timeSinceLastCheck < requiredIntervalMs) {
return
};
const activeRange = getActiveRange();
logger('TICK', `Triggering check (Mode: ${activeRange ? 'IMPORTANT' : 'NORMAL'}, Interval: ${requiredIntervalMs / 1000}s)`);
isChecking = true;
try {
await checkSlots();
lastCheckTimestamp = Date.now();
} finally {
isChecking = false;
}
}
setInterval(tick, CONFIG.SCHEDULE.TICK_INTERVAL_SEC * 1000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment