File size: 4,121 Bytes
bf3488d
 
 
f3314f8
 
 
bf3488d
 
f3314f8
 
 
bf3488d
f3314f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf3488d
 
f3314f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
 * createClient.js
 * 
 * Tworzy klienta HTTP opartego na 'got-scraping'.
 * Zast臋puje Axios, aby lepiej udawa膰 przegl膮dark臋 (TLS Fingerprinting).
 * To kluczowe, aby unikn膮膰 blokad IP/Bot贸w na Railway.
 */

import { gotScraping } from 'got-scraping';
import { CookieJar } from 'tough-cookie';
import { HttpsProxyAgent } from 'https-proxy-agent';

export function createClient() {
    const cookieJar = new CookieJar();
    
    // Obs艂uga zewn臋trznego proxy (np. Residential Proxy)
    let agent = undefined;
    if (process.env.LIBRUS_PROXY_URL) {
        agent = {
            https: new HttpsProxyAgent(process.env.LIBRUS_PROXY_URL)
        };
    }

    // Instancja got-scraping z automatycznym zarz膮dzaniem nag艂贸wkami i TLS
    const instance = gotScraping.extend({
        cookieJar,
        timeout: { request: 60000 }, // 60s timeout
        retry: { limit: 2 },
        agent, 
        headers: {
            // Referer jest wymagany przez Librus
            "Referer": "https://portal.librus.pl/rodzina/synergia/loguj",
        },
        // Konfiguracja generatora nag艂贸wk贸w (udajemy Chrome/Firefox na Desktopie)
        headerGeneratorOptions: {
            browsers: [
                { name: 'chrome', minVersion: 110 },
                { name: 'firefox', minVersion: 110 }
            ],
            devices: ['desktop'],
            locales: ['pl-PL', 'en-US'],
            operatingSystems: ['windows', 'linux']
        }
    });

    // Wrapper, kt贸ry udaje API Axios'a (get, post) dla kompatybilno艣ci z reszt膮 kodu
    const axiosLikeWrapper = {
        get: async (url, config = {}) => {
            const options = prepareOptions(config);
            try {
                const response = await instance.get(url, options);
                return formatResponse(response);
            } catch (error) {
                throw formatError(error);
            }
        },
        post: async (url, data, config = {}) => {
            const options = prepareOptions(config);
            
            // Obs艂uga danych (URLSearchParams, JSON, string)
            if (data instanceof URLSearchParams) {
                options.body = data.toString();
                if (!options.headers) options.headers = {};
                options.headers['content-type'] = 'application/x-www-form-urlencoded';
            } else if (typeof data === 'object' && data !== null) {
                options.json = data;
            } else {
                options.body = data;
            }

            try {
                const response = await instance.post(url, options);
                return formatResponse(response);
            } catch (error) {
                throw formatError(error);
            }
        }
    };

    return axiosLikeWrapper;
}

// Pomocnicza funkcja do formatowania odpowiedzi jak w Axios
function formatResponse(response) {
    let data = response.body;
    // Pr贸bujemy parsowa膰 JSON je艣li to string
    try {
        if (typeof data === 'string' && (data.trim().startsWith('{') || data.trim().startsWith('['))) {
            data = JSON.parse(data);
        }
    } catch (e) { /* ignore */ }

    return {
        data,
        status: response.statusCode,
        statusText: response.statusMessage,
        headers: response.headers,
        request: response.request
    };
}

// Pomocnicza funkcja do formatowania b艂臋d贸w jak w Axios
function formatError(error) {
    if (error.response) {
        error.response.data = error.response.body;
        try { 
            if (typeof error.response.body === 'string') {
                error.response.data = JSON.parse(error.response.body); 
            }
        } catch(e){}
        error.response.status = error.response.statusCode;
    }
    return error;
}

// Pomocnicza funkcja do mapowania opcji Axios -> Got
function prepareOptions(config) {
    const options = { ...config };
    // Mapowanie timeoutu (axios: timeout -> got: timeout.request)
    if (options.timeout && typeof options.timeout === 'number') {
        options.timeout = { request: options.timeout };
    }
    return options;
}