mrfirdauss commited on
Commit
9704817
Β·
2 Parent(s): 5705468d033e7d

fix fgunction

Browse files
constant.py β†’ app/util/constant.py RENAMED
@@ -1,411 +1,411 @@
1
- COUNTRY_CODES = {
2
- 'Afghanistan': 'af',
3
- 'Albania': 'al',
4
- 'Algeria': 'dz',
5
- 'Andorra': 'ad',
6
- 'Angola': 'ao',
7
- 'Antigua and Barbuda': 'ag',
8
- 'Argentina': 'ar',
9
- 'Armenia': 'am',
10
- 'Australia': 'au',
11
- 'Austria': 'at',
12
- 'Azerbaijan': 'az',
13
- 'Bahamas': 'bs',
14
- 'Bahrain': 'bh',
15
- 'Bangladesh': 'bd',
16
- 'Barbados': 'bb',
17
- 'Belarus': 'by',
18
- 'Belgium': 'be',
19
- 'Belize': 'bz',
20
- 'Benin': 'bj',
21
- 'Bhutan': 'bt',
22
- 'Bolivia': 'bo',
23
- 'Bosnia and Herzegovina': 'ba',
24
- 'Botswana': 'bw',
25
- 'Brazil': 'br',
26
- 'Brunei': 'bn',
27
- 'Bulgaria': 'bg',
28
- 'Burkina Faso': 'bf',
29
- 'Burundi': 'bi',
30
- 'Cambodia': 'kh',
31
- 'Cameroon': 'cm',
32
- 'Canada': 'ca',
33
- 'Cape Verde': 'cv',
34
- 'Central African Republic': 'cf',
35
- 'Chad': 'td',
36
- 'Chile': 'cl',
37
- 'China': 'cn',
38
- 'Colombia': 'co',
39
- 'Comoros': 'km',
40
- 'Congo': 'cg',
41
- 'Congo (Dem. Rep.)': 'cd',
42
- 'Costa Rica': 'cr',
43
- 'Cote d\'Ivoire': 'ci',
44
- 'Croatia': 'hr',
45
- 'Cuba': 'cu',
46
- 'Cyprus': 'cy',
47
- 'Czech Republic': 'cz',
48
- 'Denmark': 'dk',
49
- 'Djibouti': 'dj',
50
- 'Dominica': 'dm',
51
- 'Dominican Republic': 'do',
52
- 'Ecuador': 'ec',
53
- 'Egypt': 'eg',
54
- 'El Salvador': 'sv',
55
- 'Equatorial Guinea': 'gq',
56
- 'Eritrea': 'er',
57
- 'Estonia': 'ee',
58
- 'Eswatini': 'sz',
59
- 'Ethiopia': 'et',
60
- 'Fiji': 'fj',
61
- 'Finland': 'fi',
62
- 'France': 'fr',
63
- 'Gabon': 'ga',
64
- 'Gambia': 'gm',
65
- 'Georgia': 'ge',
66
- 'Germany': 'de',
67
- 'Ghana': 'gh',
68
- 'Greece': 'gr',
69
- 'Grenada': 'gd',
70
- 'Guatemala': 'gt',
71
- 'Guinea': 'gn',
72
- 'Guinea-Bissau': 'gw',
73
- 'Guyana': 'gy',
74
- 'Haiti': 'ht',
75
- 'Honduras': 'hn',
76
- 'Hong Kong': 'hk',
77
- 'Hungary': 'hu',
78
- 'Iceland': 'is',
79
- 'India': 'in',
80
- 'Indonesia': 'id',
81
- 'Iran': 'ir',
82
- 'Iraq': 'iq',
83
- 'Ireland': 'ie',
84
- 'Israel': 'il',
85
- 'Italy': 'it',
86
- 'Jamaica': 'jm',
87
- 'Japan': 'jp',
88
- 'Jordan': 'jo',
89
- 'Kazakhstan': 'kz',
90
- 'Kenya': 'ke',
91
- 'Kiribati': 'ki',
92
- 'Kosovo': 'xk',
93
- 'Kuwait': 'kw',
94
- 'Kyrgyzstan': 'kg',
95
- 'Laos': 'la',
96
- 'Latvia': 'lv',
97
- 'Lebanon': 'lb',
98
- 'Lesotho': 'ls',
99
- 'Liberia': 'lr',
100
- 'Libya': 'ly',
101
- 'Liechtenstein': 'li',
102
- 'Lithuania': 'lt',
103
- 'Luxembourg': 'lu',
104
- 'Macao': 'mo',
105
- 'Macedonia': 'mk',
106
- 'Madagascar': 'mg',
107
- 'Malawi': 'mw',
108
- 'Malaysia': 'my',
109
- 'Maldives': 'mv',
110
- 'Mali': 'ml',
111
- 'Malta': 'mt',
112
- 'Marshall Islands': 'mh',
113
- 'Mauritania': 'mr',
114
- 'Mauritius': 'mu',
115
- 'Mexico': 'mx',
116
- 'Micronesia': 'fm',
117
- 'Moldova': 'md',
118
- 'Monaco': 'mc',
119
- 'Mongolia': 'mn',
120
- 'Montenegro': 'me',
121
- 'Morocco': 'ma',
122
- 'Mozambique': 'mz',
123
- 'Myanmar': 'mm',
124
- 'Namibia': 'na',
125
- 'Nauru': 'nr',
126
- 'Nepal': 'np',
127
- 'Netherlands': 'nl',
128
- 'New Zealand': 'nz',
129
- 'Nicaragua': 'ni',
130
- 'Niger': 'ne',
131
- 'Nigeria': 'ng',
132
- 'North Korea': 'kp',
133
- 'North Macedonia': 'mk',
134
- 'Norway': 'no',
135
- 'Oman': 'om',
136
- 'Pakistan': 'pk',
137
- 'Palau': 'pw',
138
- 'Palestinian Territories': 'ps',
139
- 'Panama': 'pa',
140
- 'Papua New Guinea': 'pg',
141
- 'Paraguay': 'py',
142
- 'Peru': 'pe',
143
- 'Philippines': 'ph',
144
- 'Poland': 'pl',
145
- 'Portugal': 'pt',
146
- 'Qatar': 'qa',
147
- 'Romania': 'ro',
148
- 'Russia': 'ru',
149
- 'Russian Federation': 'ru',
150
- 'Rwanda': 'rw',
151
- 'Saint Kitts and Nevis': 'kn',
152
- 'Saint Lucia': 'lc',
153
- 'Saint Vincent and the Grenadines': 'vc',
154
- 'Samoa': 'ws',
155
- 'San Marino': 'sm',
156
- 'Sao Tome and Principe': 'st',
157
- 'Saudi Arabia': 'sa',
158
- 'Senegal': 'sn',
159
- 'Serbia': 'rs',
160
- 'Seychelles': 'sc',
161
- 'Sierra Leone': 'sl',
162
- 'Singapore': 'sg',
163
- 'Slovakia': 'sk',
164
- 'Slovenia': 'si',
165
- 'Solomon Islands': 'sb',
166
- 'Somalia': 'so',
167
- 'South Africa': 'za',
168
- 'South Korea': 'kr',
169
- 'South Sudan': 'ss',
170
- 'Spain': 'es',
171
- 'Sri Lanka': 'lk',
172
- 'St. Vincent and the Grenadines': 'vc',
173
- 'Sudan': 'sd',
174
- 'Suriname': 'sr',
175
- 'Swaziland': 'sz',
176
- 'Sweden': 'se',
177
- 'Switzerland': 'ch',
178
- 'Syria': 'sy',
179
- 'Taiwan': 'tw',
180
- 'Tajikistan': 'tj',
181
- 'Tanzania': 'tz',
182
- 'Thailand': 'th',
183
- 'Timor-Leste': 'tl',
184
- 'Togo': 'tg',
185
- 'Tonga': 'to',
186
- 'Trinidad and Tobago': 'tt',
187
- 'Tunisia': 'tn',
188
- 'Turkey': 'tr',
189
- 'TΓΌrkiye': 'tr',
190
- 'Turkmenistan': 'tm',
191
- 'Tuvalu': 'tv',
192
- 'Uganda': 'ug',
193
- 'Ukraine': 'ua',
194
- 'United Arab Emirates': 'ae',
195
- 'United Kingdom': 'gb',
196
- 'United States': 'us',
197
- 'United States of America': 'us',
198
- 'Uruguay': 'uy',
199
- 'Uzbekistan': 'uz',
200
- 'Vanuatu': 'vu',
201
- 'Vatican City': 'va',
202
- 'Venezuela': 've',
203
- 'Viet Nam': 'vn',
204
- 'Vietnam': 'vn',
205
- 'Yemen': 'ye',
206
- 'Zambia': 'zm',
207
- 'Zimbabwe': 'zw'
208
- }
209
-
210
-
211
- REVERSE_COUNTRY_CODES = {
212
- "af": "Afghanistan",
213
- "al": "Albania",
214
- "dz": "Algeria",
215
- "ad": "Andorra",
216
- "ao": "Angola",
217
- "ag": "Antigua and Barbuda",
218
- "ar": "Argentina",
219
- "am": "Armenia",
220
- "au": "Australia",
221
- "at": "Austria",
222
- "az": "Azerbaijan",
223
- "bs": "Bahamas",
224
- "bh": "Bahrain",
225
- "bd": "Bangladesh",
226
- "bb": "Barbados",
227
- "by": "Belarus",
228
- "be": "Belgium",
229
- "bz": "Belize",
230
- "bj": "Benin",
231
- "bt": "Bhutan",
232
- "bo": "Bolivia",
233
- "ba": "Bosnia and Herzegovina",
234
- "bw": "Botswana",
235
- "br": "Brazil",
236
- "bn": "Brunei",
237
- "bg": "Bulgaria",
238
- "bf": "Burkina Faso",
239
- "bi": "Burundi",
240
- "kh": "Cambodia",
241
- "cm": "Cameroon",
242
- "ca": "Canada",
243
- "cv": "Cape Verde",
244
- "cf": "Central African Republic",
245
- "td": "Chad",
246
- "cl": "Chile",
247
- "cn": "China",
248
- "co": "Colombia",
249
- "km": "Comoros",
250
- "cg": "Congo",
251
- "cd": "Congo (Dem. Rep.)",
252
- "cr": "Costa Rica",
253
- "ci": "Cote d'Ivoire",
254
- "hr": "Croatia",
255
- "cu": "Cuba",
256
- "cy": "Cyprus",
257
- "cz": "Czech Republic",
258
- "dk": "Denmark",
259
- "dj": "Djibouti",
260
- "dm": "Dominica",
261
- "do": "Dominican Republic",
262
- "ec": "Ecuador",
263
- "eg": "Egypt",
264
- "sv": "El Salvador",
265
- "gq": "Equatorial Guinea",
266
- "er": "Eritrea",
267
- "ee": "Estonia",
268
- "sz": "Swaziland",
269
- "et": "Ethiopia",
270
- "fj": "Fiji",
271
- "fi": "Finland",
272
- "fr": "France",
273
- "ga": "Gabon",
274
- "gm": "Gambia",
275
- "ge": "Georgia",
276
- "de": "Germany",
277
- "gh": "Ghana",
278
- "gr": "Greece",
279
- "gd": "Grenada",
280
- "gt": "Guatemala",
281
- "gn": "Guinea",
282
- "gw": "Guinea-Bissau",
283
- "gy": "Guyana",
284
- "ht": "Haiti",
285
- "hn": "Honduras",
286
- "hk": "Hong Kong",
287
- "hu": "Hungary",
288
- "is": "Iceland",
289
- "in": "India",
290
- "id": "Indonesia",
291
- "ir": "Iran",
292
- "iq": "Iraq",
293
- "ie": "Ireland",
294
- "il": "Israel",
295
- "it": "Italy",
296
- "jm": "Jamaica",
297
- "jp": "Japan",
298
- "jo": "Jordan",
299
- "kz": "Kazakhstan",
300
- "ke": "Kenya",
301
- "ki": "Kiribati",
302
- "xk": "Kosovo",
303
- "kw": "Kuwait",
304
- "kg": "Kyrgyzstan",
305
- "la": "Laos",
306
- "lv": "Latvia",
307
- "lb": "Lebanon",
308
- "ls": "Lesotho",
309
- "lr": "Liberia",
310
- "ly": "Libya",
311
- "li": "Liechtenstein",
312
- "lt": "Lithuania",
313
- "lu": "Luxembourg",
314
- "mo": "Macao",
315
- "mk": "North Macedonia",
316
- "mg": "Madagascar",
317
- "mw": "Malawi",
318
- "my": "Malaysia",
319
- "mv": "Maldives",
320
- "ml": "Mali",
321
- "mt": "Malta",
322
- "mh": "Marshall Islands",
323
- "mr": "Mauritania",
324
- "mu": "Mauritius",
325
- "mx": "Mexico",
326
- "fm": "Micronesia",
327
- "md": "Moldova",
328
- "mc": "Monaco",
329
- "mn": "Mongolia",
330
- "me": "Montenegro",
331
- "ma": "Morocco",
332
- "mz": "Mozambique",
333
- "mm": "Myanmar",
334
- "na": "Namibia",
335
- "nr": "Nauru",
336
- "np": "Nepal",
337
- "nl": "Netherlands",
338
- "nz": "New Zealand",
339
- "ni": "Nicaragua",
340
- "ne": "Niger",
341
- "ng": "Nigeria",
342
- "kp": "North Korea",
343
- "no": "Norway",
344
- "om": "Oman",
345
- "pk": "Pakistan",
346
- "pw": "Palau",
347
- "ps": "Palestinian Territories",
348
- "pa": "Panama",
349
- "pg": "Papua New Guinea",
350
- "py": "Paraguay",
351
- "pe": "Peru",
352
- "ph": "Philippines",
353
- "pl": "Poland",
354
- "pt": "Portugal",
355
- "qa": "Qatar",
356
- "ro": "Romania",
357
- "ru": "Russian Federation",
358
- "rw": "Rwanda",
359
- "kn": "Saint Kitts and Nevis",
360
- "lc": "Saint Lucia",
361
- "vc": "St. Vincent and the Grenadines",
362
- "ws": "Samoa",
363
- "sm": "San Marino",
364
- "st": "Sao Tome and Principe",
365
- "sa": "Saudi Arabia",
366
- "sn": "Senegal",
367
- "rs": "Serbia",
368
- "sc": "Seychelles",
369
- "sl": "Sierra Leone",
370
- "sg": "Singapore",
371
- "sk": "Slovakia",
372
- "si": "Slovenia",
373
- "sb": "Solomon Islands",
374
- "so": "Somalia",
375
- "za": "South Africa",
376
- "kr": "South Korea",
377
- "ss": "South Sudan",
378
- "es": "Spain",
379
- "lk": "Sri Lanka",
380
- "sd": "Sudan",
381
- "sr": "Suriname",
382
- "se": "Sweden",
383
- "ch": "Switzerland",
384
- "sy": "Syria",
385
- "tw": "Taiwan",
386
- "tj": "Tajikistan",
387
- "tz": "Tanzania",
388
- "th": "Thailand",
389
- "tl": "Timor-Leste",
390
- "tg": "Togo",
391
- "to": "Tonga",
392
- "tt": "Trinidad and Tobago",
393
- "tn": "Tunisia",
394
- "tr": "TΓΌrkiye",
395
- "tm": "Turkmenistan",
396
- "tv": "Tuvalu",
397
- "ug": "Uganda",
398
- "ua": "Ukraine",
399
- "ae": "United Arab Emirates",
400
- "gb": "United Kingdom",
401
- "us": "United States of America",
402
- "uy": "Uruguay",
403
- "uz": "Uzbekistan",
404
- "vu": "Vanuatu",
405
- "va": "Vatican City",
406
- "ve": "Venezuela",
407
- "vn": "Vietnam",
408
- "ye": "Yemen",
409
- "zm": "Zambia",
410
- "zw": "Zimbabwe"
411
  }
 
1
+ COUNTRY_CODES = {
2
+ 'Afghanistan': 'af',
3
+ 'Albania': 'al',
4
+ 'Algeria': 'dz',
5
+ 'Andorra': 'ad',
6
+ 'Angola': 'ao',
7
+ 'Antigua and Barbuda': 'ag',
8
+ 'Argentina': 'ar',
9
+ 'Armenia': 'am',
10
+ 'Australia': 'au',
11
+ 'Austria': 'at',
12
+ 'Azerbaijan': 'az',
13
+ 'Bahamas': 'bs',
14
+ 'Bahrain': 'bh',
15
+ 'Bangladesh': 'bd',
16
+ 'Barbados': 'bb',
17
+ 'Belarus': 'by',
18
+ 'Belgium': 'be',
19
+ 'Belize': 'bz',
20
+ 'Benin': 'bj',
21
+ 'Bhutan': 'bt',
22
+ 'Bolivia': 'bo',
23
+ 'Bosnia and Herzegovina': 'ba',
24
+ 'Botswana': 'bw',
25
+ 'Brazil': 'br',
26
+ 'Brunei': 'bn',
27
+ 'Bulgaria': 'bg',
28
+ 'Burkina Faso': 'bf',
29
+ 'Burundi': 'bi',
30
+ 'Cambodia': 'kh',
31
+ 'Cameroon': 'cm',
32
+ 'Canada': 'ca',
33
+ 'Cape Verde': 'cv',
34
+ 'Central African Republic': 'cf',
35
+ 'Chad': 'td',
36
+ 'Chile': 'cl',
37
+ 'China': 'cn',
38
+ 'Colombia': 'co',
39
+ 'Comoros': 'km',
40
+ 'Congo': 'cg',
41
+ 'Congo (Dem. Rep.)': 'cd',
42
+ 'Costa Rica': 'cr',
43
+ 'Cote d\'Ivoire': 'ci',
44
+ 'Croatia': 'hr',
45
+ 'Cuba': 'cu',
46
+ 'Cyprus': 'cy',
47
+ 'Czech Republic': 'cz',
48
+ 'Denmark': 'dk',
49
+ 'Djibouti': 'dj',
50
+ 'Dominica': 'dm',
51
+ 'Dominican Republic': 'do',
52
+ 'Ecuador': 'ec',
53
+ 'Egypt': 'eg',
54
+ 'El Salvador': 'sv',
55
+ 'Equatorial Guinea': 'gq',
56
+ 'Eritrea': 'er',
57
+ 'Estonia': 'ee',
58
+ 'Eswatini': 'sz',
59
+ 'Ethiopia': 'et',
60
+ 'Fiji': 'fj',
61
+ 'Finland': 'fi',
62
+ 'France': 'fr',
63
+ 'Gabon': 'ga',
64
+ 'Gambia': 'gm',
65
+ 'Georgia': 'ge',
66
+ 'Germany': 'de',
67
+ 'Ghana': 'gh',
68
+ 'Greece': 'gr',
69
+ 'Grenada': 'gd',
70
+ 'Guatemala': 'gt',
71
+ 'Guinea': 'gn',
72
+ 'Guinea-Bissau': 'gw',
73
+ 'Guyana': 'gy',
74
+ 'Haiti': 'ht',
75
+ 'Honduras': 'hn',
76
+ 'Hong Kong': 'hk',
77
+ 'Hungary': 'hu',
78
+ 'Iceland': 'is',
79
+ 'India': 'in',
80
+ 'Indonesia': 'id',
81
+ 'Iran': 'ir',
82
+ 'Iraq': 'iq',
83
+ 'Ireland': 'ie',
84
+ 'Israel': 'il',
85
+ 'Italy': 'it',
86
+ 'Jamaica': 'jm',
87
+ 'Japan': 'jp',
88
+ 'Jordan': 'jo',
89
+ 'Kazakhstan': 'kz',
90
+ 'Kenya': 'ke',
91
+ 'Kiribati': 'ki',
92
+ 'Kosovo': 'xk',
93
+ 'Kuwait': 'kw',
94
+ 'Kyrgyzstan': 'kg',
95
+ 'Laos': 'la',
96
+ 'Latvia': 'lv',
97
+ 'Lebanon': 'lb',
98
+ 'Lesotho': 'ls',
99
+ 'Liberia': 'lr',
100
+ 'Libya': 'ly',
101
+ 'Liechtenstein': 'li',
102
+ 'Lithuania': 'lt',
103
+ 'Luxembourg': 'lu',
104
+ 'Macao': 'mo',
105
+ 'Macedonia': 'mk',
106
+ 'Madagascar': 'mg',
107
+ 'Malawi': 'mw',
108
+ 'Malaysia': 'my',
109
+ 'Maldives': 'mv',
110
+ 'Mali': 'ml',
111
+ 'Malta': 'mt',
112
+ 'Marshall Islands': 'mh',
113
+ 'Mauritania': 'mr',
114
+ 'Mauritius': 'mu',
115
+ 'Mexico': 'mx',
116
+ 'Micronesia': 'fm',
117
+ 'Moldova': 'md',
118
+ 'Monaco': 'mc',
119
+ 'Mongolia': 'mn',
120
+ 'Montenegro': 'me',
121
+ 'Morocco': 'ma',
122
+ 'Mozambique': 'mz',
123
+ 'Myanmar': 'mm',
124
+ 'Namibia': 'na',
125
+ 'Nauru': 'nr',
126
+ 'Nepal': 'np',
127
+ 'Netherlands': 'nl',
128
+ 'New Zealand': 'nz',
129
+ 'Nicaragua': 'ni',
130
+ 'Niger': 'ne',
131
+ 'Nigeria': 'ng',
132
+ 'North Korea': 'kp',
133
+ 'North Macedonia': 'mk',
134
+ 'Norway': 'no',
135
+ 'Oman': 'om',
136
+ 'Pakistan': 'pk',
137
+ 'Palau': 'pw',
138
+ 'Palestinian Territories': 'ps',
139
+ 'Panama': 'pa',
140
+ 'Papua New Guinea': 'pg',
141
+ 'Paraguay': 'py',
142
+ 'Peru': 'pe',
143
+ 'Philippines': 'ph',
144
+ 'Poland': 'pl',
145
+ 'Portugal': 'pt',
146
+ 'Qatar': 'qa',
147
+ 'Romania': 'ro',
148
+ 'Russia': 'ru',
149
+ 'Russian Federation': 'ru',
150
+ 'Rwanda': 'rw',
151
+ 'Saint Kitts and Nevis': 'kn',
152
+ 'Saint Lucia': 'lc',
153
+ 'Saint Vincent and the Grenadines': 'vc',
154
+ 'Samoa': 'ws',
155
+ 'San Marino': 'sm',
156
+ 'Sao Tome and Principe': 'st',
157
+ 'Saudi Arabia': 'sa',
158
+ 'Senegal': 'sn',
159
+ 'Serbia': 'rs',
160
+ 'Seychelles': 'sc',
161
+ 'Sierra Leone': 'sl',
162
+ 'Singapore': 'sg',
163
+ 'Slovakia': 'sk',
164
+ 'Slovenia': 'si',
165
+ 'Solomon Islands': 'sb',
166
+ 'Somalia': 'so',
167
+ 'South Africa': 'za',
168
+ 'South Korea': 'kr',
169
+ 'South Sudan': 'ss',
170
+ 'Spain': 'es',
171
+ 'Sri Lanka': 'lk',
172
+ 'St. Vincent and the Grenadines': 'vc',
173
+ 'Sudan': 'sd',
174
+ 'Suriname': 'sr',
175
+ 'Swaziland': 'sz',
176
+ 'Sweden': 'se',
177
+ 'Switzerland': 'ch',
178
+ 'Syria': 'sy',
179
+ 'Taiwan': 'tw',
180
+ 'Tajikistan': 'tj',
181
+ 'Tanzania': 'tz',
182
+ 'Thailand': 'th',
183
+ 'Timor-Leste': 'tl',
184
+ 'Togo': 'tg',
185
+ 'Tonga': 'to',
186
+ 'Trinidad and Tobago': 'tt',
187
+ 'Tunisia': 'tn',
188
+ 'Turkey': 'tr',
189
+ 'TΓΌrkiye': 'tr',
190
+ 'Turkmenistan': 'tm',
191
+ 'Tuvalu': 'tv',
192
+ 'Uganda': 'ug',
193
+ 'Ukraine': 'ua',
194
+ 'United Arab Emirates': 'ae',
195
+ 'United Kingdom': 'gb',
196
+ 'United States': 'us',
197
+ 'United States of America': 'us',
198
+ 'Uruguay': 'uy',
199
+ 'Uzbekistan': 'uz',
200
+ 'Vanuatu': 'vu',
201
+ 'Vatican City': 'va',
202
+ 'Venezuela': 've',
203
+ 'Viet Nam': 'vn',
204
+ 'Vietnam': 'vn',
205
+ 'Yemen': 'ye',
206
+ 'Zambia': 'zm',
207
+ 'Zimbabwe': 'zw'
208
+ }
209
+
210
+
211
+ REVERSE_COUNTRY_CODES = {
212
+ "af": "Afghanistan",
213
+ "al": "Albania",
214
+ "dz": "Algeria",
215
+ "ad": "Andorra",
216
+ "ao": "Angola",
217
+ "ag": "Antigua and Barbuda",
218
+ "ar": "Argentina",
219
+ "am": "Armenia",
220
+ "au": "Australia",
221
+ "at": "Austria",
222
+ "az": "Azerbaijan",
223
+ "bs": "Bahamas",
224
+ "bh": "Bahrain",
225
+ "bd": "Bangladesh",
226
+ "bb": "Barbados",
227
+ "by": "Belarus",
228
+ "be": "Belgium",
229
+ "bz": "Belize",
230
+ "bj": "Benin",
231
+ "bt": "Bhutan",
232
+ "bo": "Bolivia",
233
+ "ba": "Bosnia and Herzegovina",
234
+ "bw": "Botswana",
235
+ "br": "Brazil",
236
+ "bn": "Brunei",
237
+ "bg": "Bulgaria",
238
+ "bf": "Burkina Faso",
239
+ "bi": "Burundi",
240
+ "kh": "Cambodia",
241
+ "cm": "Cameroon",
242
+ "ca": "Canada",
243
+ "cv": "Cape Verde",
244
+ "cf": "Central African Republic",
245
+ "td": "Chad",
246
+ "cl": "Chile",
247
+ "cn": "China",
248
+ "co": "Colombia",
249
+ "km": "Comoros",
250
+ "cg": "Congo",
251
+ "cd": "Congo (Dem. Rep.)",
252
+ "cr": "Costa Rica",
253
+ "ci": "Cote d'Ivoire",
254
+ "hr": "Croatia",
255
+ "cu": "Cuba",
256
+ "cy": "Cyprus",
257
+ "cz": "Czech Republic",
258
+ "dk": "Denmark",
259
+ "dj": "Djibouti",
260
+ "dm": "Dominica",
261
+ "do": "Dominican Republic",
262
+ "ec": "Ecuador",
263
+ "eg": "Egypt",
264
+ "sv": "El Salvador",
265
+ "gq": "Equatorial Guinea",
266
+ "er": "Eritrea",
267
+ "ee": "Estonia",
268
+ "sz": "Swaziland",
269
+ "et": "Ethiopia",
270
+ "fj": "Fiji",
271
+ "fi": "Finland",
272
+ "fr": "France",
273
+ "ga": "Gabon",
274
+ "gm": "Gambia",
275
+ "ge": "Georgia",
276
+ "de": "Germany",
277
+ "gh": "Ghana",
278
+ "gr": "Greece",
279
+ "gd": "Grenada",
280
+ "gt": "Guatemala",
281
+ "gn": "Guinea",
282
+ "gw": "Guinea-Bissau",
283
+ "gy": "Guyana",
284
+ "ht": "Haiti",
285
+ "hn": "Honduras",
286
+ "hk": "Hong Kong",
287
+ "hu": "Hungary",
288
+ "is": "Iceland",
289
+ "in": "India",
290
+ "id": "Indonesia",
291
+ "ir": "Iran",
292
+ "iq": "Iraq",
293
+ "ie": "Ireland",
294
+ "il": "Israel",
295
+ "it": "Italy",
296
+ "jm": "Jamaica",
297
+ "jp": "Japan",
298
+ "jo": "Jordan",
299
+ "kz": "Kazakhstan",
300
+ "ke": "Kenya",
301
+ "ki": "Kiribati",
302
+ "xk": "Kosovo",
303
+ "kw": "Kuwait",
304
+ "kg": "Kyrgyzstan",
305
+ "la": "Laos",
306
+ "lv": "Latvia",
307
+ "lb": "Lebanon",
308
+ "ls": "Lesotho",
309
+ "lr": "Liberia",
310
+ "ly": "Libya",
311
+ "li": "Liechtenstein",
312
+ "lt": "Lithuania",
313
+ "lu": "Luxembourg",
314
+ "mo": "Macao",
315
+ "mk": "North Macedonia",
316
+ "mg": "Madagascar",
317
+ "mw": "Malawi",
318
+ "my": "Malaysia",
319
+ "mv": "Maldives",
320
+ "ml": "Mali",
321
+ "mt": "Malta",
322
+ "mh": "Marshall Islands",
323
+ "mr": "Mauritania",
324
+ "mu": "Mauritius",
325
+ "mx": "Mexico",
326
+ "fm": "Micronesia",
327
+ "md": "Moldova",
328
+ "mc": "Monaco",
329
+ "mn": "Mongolia",
330
+ "me": "Montenegro",
331
+ "ma": "Morocco",
332
+ "mz": "Mozambique",
333
+ "mm": "Myanmar",
334
+ "na": "Namibia",
335
+ "nr": "Nauru",
336
+ "np": "Nepal",
337
+ "nl": "Netherlands",
338
+ "nz": "New Zealand",
339
+ "ni": "Nicaragua",
340
+ "ne": "Niger",
341
+ "ng": "Nigeria",
342
+ "kp": "North Korea",
343
+ "no": "Norway",
344
+ "om": "Oman",
345
+ "pk": "Pakistan",
346
+ "pw": "Palau",
347
+ "ps": "Palestinian Territories",
348
+ "pa": "Panama",
349
+ "pg": "Papua New Guinea",
350
+ "py": "Paraguay",
351
+ "pe": "Peru",
352
+ "ph": "Philippines",
353
+ "pl": "Poland",
354
+ "pt": "Portugal",
355
+ "qa": "Qatar",
356
+ "ro": "Romania",
357
+ "ru": "Russian Federation",
358
+ "rw": "Rwanda",
359
+ "kn": "Saint Kitts and Nevis",
360
+ "lc": "Saint Lucia",
361
+ "vc": "St. Vincent and the Grenadines",
362
+ "ws": "Samoa",
363
+ "sm": "San Marino",
364
+ "st": "Sao Tome and Principe",
365
+ "sa": "Saudi Arabia",
366
+ "sn": "Senegal",
367
+ "rs": "Serbia",
368
+ "sc": "Seychelles",
369
+ "sl": "Sierra Leone",
370
+ "sg": "Singapore",
371
+ "sk": "Slovakia",
372
+ "si": "Slovenia",
373
+ "sb": "Solomon Islands",
374
+ "so": "Somalia",
375
+ "za": "South Africa",
376
+ "kr": "South Korea",
377
+ "ss": "South Sudan",
378
+ "es": "Spain",
379
+ "lk": "Sri Lanka",
380
+ "sd": "Sudan",
381
+ "sr": "Suriname",
382
+ "se": "Sweden",
383
+ "ch": "Switzerland",
384
+ "sy": "Syria",
385
+ "tw": "Taiwan",
386
+ "tj": "Tajikistan",
387
+ "tz": "Tanzania",
388
+ "th": "Thailand",
389
+ "tl": "Timor-Leste",
390
+ "tg": "Togo",
391
+ "to": "Tonga",
392
+ "tt": "Trinidad and Tobago",
393
+ "tn": "Tunisia",
394
+ "tr": "TΓΌrkiye",
395
+ "tm": "Turkmenistan",
396
+ "tv": "Tuvalu",
397
+ "ug": "Uganda",
398
+ "ua": "Ukraine",
399
+ "ae": "United Arab Emirates",
400
+ "gb": "United Kingdom",
401
+ "us": "United States of America",
402
+ "uy": "Uruguay",
403
+ "uz": "Uzbekistan",
404
+ "vu": "Vanuatu",
405
+ "va": "Vatican City",
406
+ "ve": "Venezuela",
407
+ "vn": "Vietnam",
408
+ "ye": "Yemen",
409
+ "zm": "Zambia",
410
+ "zw": "Zimbabwe"
411
  }
app/util/visa_availability_scraper_playwright.py CHANGED
@@ -1,8 +1,11 @@
1
  import asyncio
2
- import pandas as pd
3
  from typing import Dict, Optional, List
4
- from constant import COUNTRY_CODES, REVERSE_COUNTRY_CODES
5
  from playwright.async_api import async_playwright
 
 
 
 
6
 
7
  class PassportIndexVisaScraper:
8
  def __init__(self, debug: bool = True):
@@ -18,55 +21,102 @@ class PassportIndexVisaScraper:
18
  self.browser = None
19
  self.context = None
20
  self.page = None
 
 
21
 
22
  async def __aenter__(self):
23
  """Initialize browser with stealth mode"""
24
- self.playwright = await async_playwright().start()
 
 
 
 
 
 
25
 
 
26
  self.browser = await self.playwright.chromium.launch(
27
- headless=False, # Using headless mode
28
  args=[
29
  '--disable-blink-features=AutomationControlled',
30
  '--disable-dev-shm-usage',
31
  '--no-sandbox',
32
  '--disable-setuid-sandbox',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  '--disable-web-security',
34
- '--disable-features=IsolateOrigins,site-per-process'
 
 
 
35
  ]
36
  )
 
 
 
 
 
 
 
 
37
 
38
  # Create context with realistic settings
39
  self.context = await self.browser.new_context(
40
- viewport={'width': 1920, 'height': 1080},
41
- user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
42
  locale='en-US',
43
- timezone_id='America/New_York'
 
 
 
 
 
 
 
44
  )
45
 
46
  self.page = await self.context.new_page()
47
 
 
48
  await self.page.add_init_script("""
49
- // Override the navigator.webdriver property
50
- Object.defineProperty(navigator, 'webdriver', {
51
- get: () => undefined
52
- });
53
-
54
- // Override chrome property
55
- window.chrome = {
56
- runtime: {}
57
- };
58
-
59
- // Override permissions
60
- const originalQuery = window.navigator.permissions.query;
61
- window.navigator.permissions.query = (parameters) => (
62
- parameters.name === 'notifications' ?
63
- Promise.resolve({ state: Notification.permission }) :
64
- originalQuery(parameters)
65
- );
66
  """)
67
 
68
  if self.debug:
69
- print("πŸš€ Browser initialized with stealth mode")
70
 
71
  return self
72
 
@@ -78,8 +128,8 @@ class PassportIndexVisaScraper:
78
  await self.context.close()
79
  if self.browser:
80
  await self.browser.close()
81
- if self.playwright:
82
- await self.playwright.stop()
83
 
84
  if self.debug:
85
  print("πŸ”’ Browser closed")
@@ -88,44 +138,147 @@ class PassportIndexVisaScraper:
88
  """
89
  Navigate to the website and wait for it to load properly
90
  """
91
- try:
92
- if self.debug:
93
- print("πŸ“± Initializing session...")
94
-
95
- # Navigate to the page
96
  try:
 
 
 
 
 
 
 
 
97
  response = await self.page.goto(
98
  self.base_url,
99
- wait_until='domcontentloaded',
100
- timeout=30000
101
  )
102
- await self.page.wait_for_timeout(3000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- # Get the cl value from the page
105
  cl_value = await self.page.evaluate("""
106
  () => {
107
  const clInput = document.querySelector('#cl');
108
- return clInput ? clInput.value : 'bc2140a2d83928ce1112d01e610bad89';
 
 
 
 
 
 
 
 
 
109
  }
110
  """)
111
 
112
  if self.debug:
113
- print(f"βœ… Page loaded, session ID: {cl_value}")
114
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  return True
116
-
117
  except Exception as e:
118
- if self.debug:
119
- print(f"⚠️ Page load issue: {e}, continuing anyway...")
120
- return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  except Exception as e:
123
- print(f"❌ Error initializing session: {e}")
124
- return False
125
 
126
- async def check_visa_requirement_browser(self, passport_country: str, destination_country: str) -> Optional[Dict]:
127
  """
128
- Check visa requirements using browser automation
129
 
130
  Args:
131
  passport_country: Two-letter country code for passport
@@ -136,9 +289,12 @@ class PassportIndexVisaScraper:
136
  """
137
  try:
138
  if self.debug:
139
- print(f"🌐 Checking {passport_country.upper()} β†’ {destination_country.upper()}")
140
 
141
- # Get the current session ID from the page
 
 
 
142
  cl_value = await self.page.evaluate("""
143
  () => {
144
  const clInput = document.querySelector('#cl');
@@ -146,13 +302,15 @@ class PassportIndexVisaScraper:
146
  }
147
  """)
148
 
149
- # Make the API request through the browser with proper argument passing
150
  result = await self.page.evaluate("""
151
  async (args) => {
152
  const [passport, destination, sessionId] = args;
 
 
153
  const formData = new URLSearchParams();
154
- formData.append('d', destination);
155
- formData.append('s', passport);
156
  formData.append('cl', sessionId);
157
 
158
  try {
@@ -161,130 +319,254 @@ class PassportIndexVisaScraper:
161
  headers: {
162
  'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
163
  'X-Requested-With': 'XMLHttpRequest',
164
- 'Accept': 'application/json, text/javascript, */*; q=0.01'
 
 
165
  },
166
  body: formData.toString(),
167
- credentials: 'include'
 
168
  });
169
 
170
- if (!response.ok) {
171
- throw new Error(`HTTP ${response.status}`);
172
- }
173
 
174
- const data = await response.json();
175
- return data;
 
 
 
 
 
 
 
 
 
 
 
176
  } catch (error) {
177
  return { error: error.message };
178
  }
179
  }
180
- """, [passport_country.lower(), destination_country.lower(), cl_value])
181
 
182
- if result and 'error' not in result:
183
  if self.debug:
184
- print(f"βœ… Got result: {result}")
185
  return result
186
  elif result and 'error' in result:
187
- print(result)
188
- print(f"❌ API Error: {result['error']}")
189
  return None
190
  else:
191
  return None
192
 
193
  except Exception as e:
194
- print(f"❌ Error checking visa requirement: {e}")
 
195
  return None
196
 
197
- async def check_visa_interactive(self, passport_country: str, destination_country: str) -> Optional[Dict]:
198
  """
199
- Alternative method: Use the interactive UI to check visa requirements
200
  """
201
  try:
202
  if self.debug:
203
- print(f"πŸ–±οΈ Using interactive method for {passport_country.upper()} β†’ {destination_country.upper()}")
204
 
205
- # Click on the passport selector
206
- await self.page.click('.vch-select-pass')
207
- await self.page.wait_for_timeout(500)
208
 
209
- # Find and click the country in the list
210
- passport_selector = f'.vch-passports .s-div[data-ccode="{passport_country.lower()}"]'
211
- await self.page.wait_for_selector(passport_selector, timeout=3000)
212
- await self.page.click(passport_selector)
213
- await self.page.wait_for_timeout(500)
 
 
 
 
 
 
 
 
 
 
 
214
 
215
- # Click on the destination selector
216
- await self.page.click('.vch-select-des')
217
  await self.page.wait_for_timeout(500)
218
 
219
- # Find and click the destination country
220
- dest_selector = f'.vch-destinations .s-div[data-ccode="{destination_country.lower()}"]'
221
- await self.page.wait_for_selector(dest_selector, timeout=5000)
222
- await self.page.click(dest_selector)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  await self.page.wait_for_timeout(1000)
224
 
225
- # Get the result from the page
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  result = await self.page.evaluate("""
227
  () => {
228
  const resultElement = document.querySelector('.vch-result');
229
  if (resultElement) {
230
  const text = resultElement.querySelector('.text');
231
  const days = resultElement.querySelector('.days');
 
 
 
232
  return {
233
- text: text ? text.textContent : '',
234
- days: days ? days.textContent : '',
235
- pass: '""" + passport_country.lower() + """',
236
- dest: '""" + destination_country.upper() + """'
237
  };
238
  }
239
  return null;
240
  }
241
  """)
242
 
243
- return result
 
 
 
244
 
245
  except Exception as e:
246
  if self.debug:
247
- print(f"❌ Interactive method failed: {e}")
248
  return None
249
- async def check_multiple_source(self, passport_countries: List[str], destination: str, delay: float = 2.0) -> Dict:
 
250
  """
251
- Check visa requirements for multiple passport countries to a single destination.
252
-
253
- Args:
254
- passport_countries: List of two-letter country codes for passports.
255
- destination: Two-letter country code for the destination.
256
- delay: Delay between requests in seconds.
257
-
258
- Returns:
259
- Dictionary mapping passport country codes to visa information.
260
  """
261
- results = {}
262
-
263
- for i, passport in enumerate(passport_countries, 1):
264
- print(f"\n[{i}/{len(passport_countries)}] Checking {passport.upper()} β†’ {destination.upper()}...")
265
-
266
- # Try API method first
267
- result = await self.check_visa_requirement_browser(passport, destination)
268
 
269
- # If API fails, try interactive method
270
- if not result:
271
- result = await self.check_visa_interactive(passport, destination)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
- if result:
274
- results[passport] = result
275
- text = result.get('text', 'No text available')
276
- print(f" βœ… Result: {text}")
277
- else:
278
- results[passport] = None
279
- print(f" ❌ Failed to get result")
280
 
281
- # Rate limiting to avoid blocking
282
- if i < len(passport_countries):
283
- print(f" ⏳ Waiting {delay} seconds...")
284
- await asyncio.sleep(delay)
 
 
 
 
 
 
 
 
 
285
 
286
- return results
287
-
 
 
 
 
 
 
 
 
 
 
288
  async def check_multiple_destinations(self, passport_country: str, destinations: List[str], delay: float = 2.0) -> Dict:
289
  """
290
  Check visa requirements for multiple destinations
@@ -302,12 +584,7 @@ class PassportIndexVisaScraper:
302
  for i, dest in enumerate(destinations, 1):
303
  print(f"\n[{i}/{len(destinations)}] Checking {passport_country.upper()} β†’ {dest.upper()}...")
304
 
305
- # Try API method first
306
- result = await self.check_visa_requirement_browser(passport_country, dest)
307
-
308
- # If API fails, try interactive method
309
- if not result:
310
- result = await self.check_visa_interactive(passport_country, dest)
311
 
312
  if result:
313
  results[dest] = result
@@ -329,14 +606,57 @@ class PassportIndexVisaScraper:
329
  if not result:
330
  return "No information available"
331
 
332
- text = result.get('text', 'N/A')
333
- dest = result.get('dest', 'N/A')
334
- passport = result.get('pass', 'N/A')
335
-
336
- return f"{passport.upper()} β†’ {dest.upper()}: {text}"
 
 
 
337
 
338
 
339
  async def main():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  print("="*60)
341
  print(" Passport Index Visa Checker (Playwright)")
342
  print("="*60)
 
1
  import asyncio
2
+ import json
3
  from typing import Dict, Optional, List
 
4
  from playwright.async_api import async_playwright
5
+ from playwright_stealth import Stealth
6
+ from constant import COUNTRY_CODES, REVERSE_COUNTRY_CODES
7
+ import pandas as pd
8
+ import random
9
 
10
  class PassportIndexVisaScraper:
11
  def __init__(self, debug: bool = True):
 
21
  self.browser = None
22
  self.context = None
23
  self.page = None
24
+ self.playwright = None
25
+ self.stealth_manager = None
26
 
27
  async def __aenter__(self):
28
  """Initialize browser with stealth mode"""
29
+ # Initialize Playwright with Stealth using context manager pattern
30
+ self.stealth_manager = Stealth().use_async(async_playwright())
31
+ self.playwright = await self.stealth_manager.__aenter__()
32
+
33
+ # Random viewport for more natural appearance
34
+ viewport_width = random.randint(1366, 1920)
35
+ viewport_height = random.randint(768, 1080)
36
 
37
+ # Launch browser in headless mode with stealth settings
38
  self.browser = await self.playwright.chromium.launch(
39
+ headless=False,
40
  args=[
41
  '--disable-blink-features=AutomationControlled',
42
  '--disable-dev-shm-usage',
43
  '--no-sandbox',
44
  '--disable-setuid-sandbox',
45
+ f'--window-size={viewport_width},{viewport_height}',
46
+ '--disable-features=IsolateOrigins,site-per-process',
47
+ '--enable-features=NetworkService,NetworkServiceInProcess',
48
+ '--disable-infobars',
49
+ '--hide-scrollbars',
50
+ '--mute-audio',
51
+ '--disable-background-timer-throttling',
52
+ '--disable-renderer-backgrounding',
53
+ '--disable-features=TranslateUI',
54
+ '--disable-ipc-flooding-protection',
55
+ '--force-color-profile=srgb',
56
+ '--metrics-recording-only',
57
+ '--disable-hang-monitor',
58
+ '--disable-popup-blocking',
59
+ '--disable-prompt-on-repost',
60
+ '--disable-background-networking',
61
+ '--disable-breakpad',
62
+ '--disable-client-side-phishing-detection',
63
+ '--disable-component-extensions-with-background-pages',
64
+ '--disable-default-apps',
65
+ '--disable-extensions',
66
+ '--disable-features=TranslateUI,BlinkGenPropertyTrees',
67
+ '--disable-sync',
68
  '--disable-web-security',
69
+ '--disable-site-isolation-trials',
70
+ '--no-first-run',
71
+ '--no-default-browser-check',
72
+ '--no-pings'
73
  ]
74
  )
75
+
76
+ # Rotate user agents
77
+ user_agents = [
78
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
79
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
80
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
81
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
82
+ ]
83
 
84
  # Create context with realistic settings
85
  self.context = await self.browser.new_context(
86
+ viewport={'width': viewport_width, 'height': viewport_height},
87
+ user_agent=random.choice(user_agents),
88
  locale='en-US',
89
+ timezone_id='America/New_York',
90
+ accept_downloads=False,
91
+ has_touch=False,
92
+ is_mobile=False,
93
+ device_scale_factor=1,
94
+ bypass_csp=True,
95
+ ignore_https_errors=True,
96
+ java_script_enabled=True
97
  )
98
 
99
  self.page = await self.context.new_page()
100
 
101
+ # Add additional stealth measures
102
  await self.page.add_init_script("""
103
+ // Additional evasion beyond what stealth plugin provides
104
+ delete Object.getPrototypeOf(navigator).webdriver;
105
+
106
+ // Add some randomness to make fingerprint unique
107
+ const battery = navigator.getBattery;
108
+ if (battery) {
109
+ navigator.getBattery = async () => ({
110
+ charging: Math.random() > 0.5,
111
+ chargingTime: Math.floor(Math.random() * 10000),
112
+ dischargingTime: Math.floor(Math.random() * 10000),
113
+ level: Math.random()
114
+ });
115
+ }
 
 
 
 
116
  """)
117
 
118
  if self.debug:
119
+ print("πŸš€ Browser initialized with stealth mode (headless)")
120
 
121
  return self
122
 
 
128
  await self.context.close()
129
  if self.browser:
130
  await self.browser.close()
131
+ if hasattr(self, 'stealth_manager') and self.stealth_manager:
132
+ await self.stealth_manager.__aexit__(exc_type, exc_val, exc_tb)
133
 
134
  if self.debug:
135
  print("πŸ”’ Browser closed")
 
138
  """
139
  Navigate to the website and wait for it to load properly
140
  """
141
+ max_retries = 3
142
+ for attempt in range(max_retries):
 
 
 
143
  try:
144
+ if self.debug:
145
+ print(f"πŸ“± Initializing session... (Attempt {attempt + 1}/{max_retries})")
146
+
147
+ # Add random delay to appear more human
148
+ if attempt > 0:
149
+ await self.page.wait_for_timeout(random.randint(2000, 5000))
150
+
151
+ # Navigate to the page with proper wait
152
  response = await self.page.goto(
153
  self.base_url,
154
+ wait_until='networkidle',
155
+ timeout=60000
156
  )
157
+
158
+ # Check for Cloudflare challenge
159
+ page_content = await self.page.content()
160
+ if "Just a moment" in page_content or "cf-challenge" in page_content:
161
+ if self.debug:
162
+ print("⏳ Cloudflare challenge detected, waiting...")
163
+
164
+ # Wait for Cloudflare to complete
165
+ await self.page.wait_for_timeout(15000)
166
+
167
+ # Try to wait for actual content
168
+ try:
169
+ await self.page.wait_for_selector('.vch-table', state='visible', timeout=20000)
170
+ except:
171
+ if attempt < max_retries - 1:
172
+ continue
173
+ else:
174
+ if self.debug:
175
+ print("⚠️ Cloudflare challenge persists")
176
+ # Try to reload the page
177
+ await self.page.reload(wait_until='networkidle', timeout=60000)
178
+ await self.page.wait_for_timeout(5000)
179
+
180
+
181
+ # Wait for page to be ready - try multiple selectors
182
+ try:
183
+ # Try to wait for any of these elements that indicate page is loaded
184
+ await self.page.wait_for_selector('.vch-table', state='visible', timeout=15000)
185
+ except:
186
+ # If wait fails, just continue after a delay
187
+ if self.debug:
188
+ print("⚠️ Could not find expected elements, continuing anyway...")
189
+ await self.page.wait_for_timeout(3000)
190
+
191
+ # Close/hide the virtual keyboard if it exists
192
+ await self.close_virtual_keyboard()
193
 
194
+ # Get the session value from the page
195
  cl_value = await self.page.evaluate("""
196
  () => {
197
  const clInput = document.querySelector('#cl');
198
+ // Also check if the page has the visa checker elements
199
+ const hasVchTable = document.querySelector('.vch-table') !== null;
200
+ const hasPassSelect = document.querySelector('.vch-select-pass') !== null;
201
+
202
+ return {
203
+ cl: clInput ? clInput.value : null,
204
+ hasTable: hasVchTable,
205
+ hasSelectors: hasPassSelect,
206
+ url: window.location.href
207
+ };
208
  }
209
  """)
210
 
211
  if self.debug:
212
+ if cl_value and cl_value.get('cl'):
213
+ print(f"βœ… Page loaded, session ID: {cl_value['cl']}")
214
+ print(f" Table present: {cl_value['hasTable']}, Selectors present: {cl_value['hasSelectors']}")
215
+ print(f" URL: {cl_value['url']}")
216
+ else:
217
+ print(f"⚠️ Page loaded but missing elements: {cl_value}")
218
+ # If elements are missing, try one more time with a longer wait
219
+ if not cl_value.get('hasTable'):
220
+ await self.page.wait_for_timeout(10000)
221
+ cl_value = await self.page.evaluate("""
222
+ () => {
223
+ const clInput = document.querySelector('#cl');
224
+ const hasVchTable = document.querySelector('.vch-table') !== null;
225
+ const hasPassSelect = document.querySelector('.vch-select-pass') !== null;
226
+ return {
227
+ cl: clInput ? clInput.value : null,
228
+ hasTable: hasVchTable,
229
+ hasSelectors: hasPassSelect,
230
+ url: window.location.href
231
+ };
232
+ }
233
+ """)
234
+ if cl_value.get('hasTable'):
235
+ print(f"βœ… Elements loaded after additional wait")
236
+ else:
237
+ # Continue anyway, might still work
238
+ pass
239
+
240
  return True
241
+
242
  except Exception as e:
243
+ if attempt == max_retries - 1:
244
+ print(f"❌ Error initializing session after {max_retries} attempts: {e}")
245
+ return False
246
+ else:
247
+ if self.debug:
248
+ print(f"⚠️ Attempt {attempt + 1} failed: {e}")
249
+ continue
250
+ return False
251
+
252
+ async def close_virtual_keyboard(self):
253
+ """Close the virtual keyboard if it's open"""
254
+ try:
255
+ # Check if virtual keyboard exists and is visible
256
+ keyboard_visible = await self.page.evaluate("""
257
+ () => {
258
+ const keyboard = document.querySelector('#PI-VirtualKeyboard');
259
+ if (keyboard) {
260
+ // Hide the keyboard
261
+ keyboard.style.display = 'none';
262
+ keyboard.classList.remove('kioskboard-opened');
263
+ // Also hide any overlay elements
264
+ const overlays = document.querySelectorAll('.kioskboard-wrapper, .kioskboard-overlay');
265
+ overlays.forEach(el => el.style.display = 'none');
266
+ return true;
267
+ }
268
+ return false;
269
+ }
270
+ """)
271
+
272
+ if keyboard_visible and self.debug:
273
+ print("⌨️ Virtual keyboard hidden")
274
 
275
  except Exception as e:
276
+ if self.debug:
277
+ print(f"⚠️ Could not hide virtual keyboard: {e}")
278
 
279
+ async def check_visa_requirement_api(self, passport_country: str, destination_country: str) -> Optional[Dict]:
280
  """
281
+ Check visa requirements using direct API call with cookies
282
 
283
  Args:
284
  passport_country: Two-letter country code for passport
 
289
  """
290
  try:
291
  if self.debug:
292
+ print(f"🌐 Checking {passport_country.upper()} β†’ {destination_country.upper()} via API")
293
 
294
+ # Get cookies from the page
295
+ cookies = await self.context.cookies()
296
+
297
+ # Get the session ID
298
  cl_value = await self.page.evaluate("""
299
  () => {
300
  const clInput = document.querySelector('#cl');
 
302
  }
303
  """)
304
 
305
+ # Make the API request with proper cookies and headers
306
  result = await self.page.evaluate("""
307
  async (args) => {
308
  const [passport, destination, sessionId] = args;
309
+
310
+ // Create form data
311
  const formData = new URLSearchParams();
312
+ formData.append('d', destination.toLowerCase());
313
+ formData.append('s', passport.toLowerCase());
314
  formData.append('cl', sessionId);
315
 
316
  try {
 
319
  headers: {
320
  'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
321
  'X-Requested-With': 'XMLHttpRequest',
322
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
323
+ 'Origin': 'https://www.passportindex.org',
324
+ 'Referer': 'https://www.passportindex.org/travel-visa-checker/'
325
  },
326
  body: formData.toString(),
327
+ credentials: 'include',
328
+ mode: 'cors'
329
  });
330
 
331
+ const responseText = await response.text();
 
 
332
 
333
+ // Try to parse as JSON
334
+ try {
335
+ return JSON.parse(responseText);
336
+ } catch {
337
+ // Check if it's a Cloudflare challenge
338
+ if (responseText.includes("Just a moment") ||
339
+ responseText.includes("cf-challenge") ||
340
+ response.status === 403) {
341
+ return { error: 'Cloudflare block', status: response.status };
342
+ }
343
+ // Otherwise it might be a different error
344
+ return { error: 'Invalid response', status: response.status };
345
+ }
346
  } catch (error) {
347
  return { error: error.message };
348
  }
349
  }
350
+ """, [passport_country, destination_country, cl_value])
351
 
352
+ if result and 'error' not in result and 'text' in result:
353
  if self.debug:
354
+ print(f"βœ… Got API result: {json.dumps(result)}")
355
  return result
356
  elif result and 'error' in result:
357
+ if self.debug:
358
+ print(f"⚠️ API Error: {result['error']}")
359
  return None
360
  else:
361
  return None
362
 
363
  except Exception as e:
364
+ if self.debug:
365
+ print(f"❌ Error checking visa requirement via API: {e}")
366
  return None
367
 
368
+ async def check_visa_interactive_v2(self, passport_country: str, destination_country: str) -> Optional[Dict]:
369
  """
370
+ Alternative interactive method with better element handling
371
  """
372
  try:
373
  if self.debug:
374
+ print(f"πŸ–±οΈ Using interactive method v2 for {passport_country.upper()} β†’ {destination_country.upper()}")
375
 
376
+ # First, ensure virtual keyboard is hidden
377
+ await self.close_virtual_keyboard()
 
378
 
379
+ # Clear any previous selections via JavaScript
380
+ await self.page.evaluate("""
381
+ () => {
382
+ // Reset the form
383
+ const passSelect = document.querySelector('.vch-select-pass');
384
+ const destSelect = document.querySelector('.vch-select-des');
385
+ if (passSelect) passSelect.textContent = 'Select Passport';
386
+ if (destSelect) destSelect.textContent = 'Select Destination';
387
+
388
+ // Hide any open dropdowns
389
+ const passports = document.querySelector('.vch-passports');
390
+ const destinations = document.querySelector('.vch-destinations');
391
+ if (passports) passports.classList.remove('opener');
392
+ if (destinations) destinations.classList.remove('opener');
393
+ }
394
+ """)
395
 
 
 
396
  await self.page.wait_for_timeout(500)
397
 
398
+ # Select passport using JavaScript
399
+ passport_selected = await self.page.evaluate("""
400
+ (countryCode) => {
401
+ // Click the passport selector
402
+ const passSelect = document.querySelector('.vch-select-pass');
403
+ if (!passSelect) return false;
404
+ passSelect.click();
405
+
406
+ // Wait a bit for dropdown to open
407
+ setTimeout(() => {
408
+ // Find and click the country
409
+ const countryElement = document.querySelector(`.vch-passports .s-div[data-ccode="${countryCode.toLowerCase()}"]`);
410
+ if (countryElement) {
411
+ countryElement.click();
412
+ return true;
413
+ }
414
+ }, 300);
415
+
416
+ return false;
417
+ }
418
+ """, passport_country)
419
+
420
  await self.page.wait_for_timeout(1000)
421
 
422
+ # Select destination using JavaScript
423
+ destination_selected = await self.page.evaluate("""
424
+ (countryCode) => {
425
+ // Click the destination selector
426
+ const destSelect = document.querySelector('.vch-select-des');
427
+ if (!destSelect) return false;
428
+ destSelect.click();
429
+
430
+ // Wait a bit for dropdown to open
431
+ setTimeout(() => {
432
+ // Find and click the country
433
+ const countryElement = document.querySelector(`.vch-destinations .s-div[data-ccode="${countryCode.toLowerCase()}"]`);
434
+ if (countryElement) {
435
+ countryElement.click();
436
+ return true;
437
+ }
438
+ }, 300);
439
+
440
+ return false;
441
+ }
442
+ """, destination_country)
443
+
444
+ await self.page.wait_for_timeout(2000)
445
+
446
+ # Get the result
447
  result = await self.page.evaluate("""
448
  () => {
449
  const resultElement = document.querySelector('.vch-result');
450
  if (resultElement) {
451
  const text = resultElement.querySelector('.text');
452
  const days = resultElement.querySelector('.days');
453
+ const passSelect = document.querySelector('.vch-select-pass');
454
+ const destSelect = document.querySelector('.vch-select-des');
455
+
456
  return {
457
+ text: text ? text.textContent.trim() : '',
458
+ days: days ? days.textContent.trim() : '',
459
+ pass: passSelect ? passSelect.textContent.trim() : '',
460
+ dest: destSelect ? destSelect.textContent.trim() : ''
461
  };
462
  }
463
  return null;
464
  }
465
  """)
466
 
467
+ if result and result['text']:
468
+ return result
469
+
470
+ return None
471
 
472
  except Exception as e:
473
  if self.debug:
474
+ print(f"❌ Interactive method v2 failed: {e}")
475
  return None
476
+
477
+ async def check_visa_direct_manipulation(self, passport_country: str, destination_country: str) -> Optional[Dict]:
478
  """
479
+ Direct manipulation method - directly trigger the visa check
 
 
 
 
 
 
 
 
480
  """
481
+ try:
482
+ if self.debug:
483
+ print(f"πŸ”§ Using direct manipulation for {passport_country.upper()} β†’ {destination_country.upper()}")
 
 
 
 
484
 
485
+ # Directly call the visa checker function if it exists
486
+ result = await self.page.evaluate("""
487
+ async (args) => {
488
+ const [passport, destination] = args;
489
+ // Try to find and call the visa checker function directly
490
+ if (typeof visaChecker !== 'undefined' && typeof visaChecker.check === 'function') {
491
+ return await visaChecker.check(passport, destination);
492
+ }
493
+
494
+ // Alternative: manually trigger the check
495
+ const cl = document.querySelector('#cl')?.value || 'bc2140a2d83928ce1112d01e610bad89';
496
+
497
+ // Set the selections visually
498
+ const passSelect = document.querySelector('.vch-select-pass');
499
+ const destSelect = document.querySelector('.vch-select-des');
500
+
501
+ if (passSelect && destSelect) {
502
+ // Find country names
503
+ const passportElement = document.querySelector(`.vch-passports .s-div[data-ccode="${passport}"] .cname`);
504
+ const destElement = document.querySelector(`.vch-destinations .s-div[data-ccode="${destination}"] .cname`);
505
+
506
+ if (passportElement) passSelect.textContent = passportElement.textContent;
507
+ if (destElement) destSelect.textContent = destElement.textContent;
508
+
509
+ // Make the AJAX call
510
+ return new Promise((resolve) => {
511
+ $.ajax({
512
+ url: 'https://www.passportindex.org/core/visachecker.php',
513
+ type: 'POST',
514
+ data: {
515
+ d: destination,
516
+ s: passport,
517
+ cl: cl
518
+ },
519
+ success: function(response) {
520
+ // Update the UI
521
+ if (response.text) {
522
+ const resultElement = document.querySelector('.vch-result');
523
+ if (resultElement) {
524
+ resultElement.className = 'vch-result tv-grey ' + (response.vr ? 'tvvr' : 'tvvf');
525
+ const textElement = resultElement.querySelector('.text');
526
+ if (textElement) textElement.textContent = response.text;
527
+ }
528
+ }
529
+ resolve(response);
530
+ },
531
+ error: function(xhr) {
532
+ resolve({ error: 'Request failed', status: xhr.status });
533
+ }
534
+ });
535
+ });
536
+ }
537
+
538
+ return null;
539
+ }
540
+ """, [passport_country.lower(), destination_country.lower()])
541
 
542
+ return result
 
 
 
 
 
 
543
 
544
+ except Exception as e:
545
+ if self.debug:
546
+ print(f"❌ Direct manipulation failed: {e}")
547
+ return None
548
+
549
+ async def check_visa_requirement(self, passport_country: str, destination_country: str) -> Optional[Dict]:
550
+ """
551
+ Main method to check visa requirements - tries multiple approaches
552
+ """
553
+ # Try API method first
554
+ result = await self.check_visa_requirement_api(passport_country, destination_country)
555
+ if result:
556
+ return result
557
 
558
+ # Try direct manipulation
559
+ result = await self.check_visa_direct_manipulation(passport_country, destination_country)
560
+ if result:
561
+ return result
562
+
563
+ # Try interactive method v2
564
+ result = await self.check_visa_interactive_v2(passport_country, destination_country)
565
+ if result:
566
+ return result
567
+
568
+ return None
569
+
570
  async def check_multiple_destinations(self, passport_country: str, destinations: List[str], delay: float = 2.0) -> Dict:
571
  """
572
  Check visa requirements for multiple destinations
 
584
  for i, dest in enumerate(destinations, 1):
585
  print(f"\n[{i}/{len(destinations)}] Checking {passport_country.upper()} β†’ {dest.upper()}...")
586
 
587
+ result = await self.check_visa_requirement(passport_country, dest)
 
 
 
 
 
588
 
589
  if result:
590
  results[dest] = result
 
606
  if not result:
607
  return "No information available"
608
 
609
+ # Return clean JSON format
610
+ return json.dumps({
611
+ 'text': result.get('text', 'N/A'),
612
+ 'col': result.get('col', ''),
613
+ 'link': result.get('link', 0),
614
+ 'dest': result.get('dest', '').upper(),
615
+ 'pass': result.get('pass', '').upper()
616
+ })
617
 
618
 
619
  async def main():
620
+ """Main function to demonstrate usage"""
621
+ print("="*60)
622
+ print(" Passport Index Visa Checker (Playwright - Fixed)")
623
+ print("="*60)
624
+
625
+ async with PassportIndexVisaScraper(debug=True) as scraper:
626
+ # Initialize session
627
+ if not await scraper.initialize_session():
628
+ print("❌ Failed to initialize session")
629
+ return
630
+
631
+ print("\n" + "="*60)
632
+ print(" Testing visa requirements...")
633
+ print("="*60)
634
+
635
+ # Test single visa requirement
636
+ print("\nπŸ“ Single visa check: US β†’ GB")
637
+ print("-" * 40)
638
+ result = await scraper.check_visa_requirement('us', 'gb')
639
+ if result:
640
+ print(f"Result: {json.dumps(result)}")
641
+ else:
642
+ print("❌ Failed to get result")
643
+
644
+ # Test multiple destinations
645
+ print("\nπŸ“ Multiple destinations for US passport:")
646
+ print("-" * 40)
647
+ destinations = ['ca', 'mx', 'jp', 'au'] # Canada, Mexico, Japan, Australia
648
+ results = await scraper.check_multiple_destinations('us', destinations, delay=2.0)
649
+
650
+ print("\nπŸ“Š Summary:")
651
+ for dest, result in results.items():
652
+ if result:
653
+ print(f" βœ… {dest.upper()}: {json.dumps(result)}")
654
+ else:
655
+ print(f" ❌ US β†’ {dest.upper()}: Failed")
656
+
657
+
658
+
659
+ async def indo():
660
  print("="*60)
661
  print(" Passport Index Visa Checker (Playwright)")
662
  print("="*60)