-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbot.php
More file actions
537 lines (499 loc) · 28.8 KB
/
bot.php
File metadata and controls
537 lines (499 loc) · 28.8 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
<?php
declare(strict_types=1);
/*
* This file is a part of the Civ13 project.
*
* Copyright (c) 2022-present Valithor Obsidion <valithor@valzargaming.com>
*/
namespace Civ13;
use \Exception;
use Civ13\Civ13;
use Clue\React\Redis\Factory as Redis;
use Discord\Discord;
use Discord\Stats;
use Discord\Builders\MessageBuilder;
use Discord\Helpers\CacheConfig;
use Discord\WebSockets\Intents;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
use React\EventLoop\Loop;
use React\Filesystem\Factory as FilesystemFactory;
use React\Http\Browser;
use WyriHaximus\React\Cache\Redis as RedisCache;
use function React\Async\async;
use function React\Promise\set_rejection_handler;
define('CIVILIZATIONBOT_START', microtime(true));
ini_set('display_errors', 1);
error_reporting(E_ALL);
set_time_limit(0);
ignore_user_abort(true);
ini_set('max_execution_time', 0);
ini_set('memory_limit', '-1'); // Unlimited memory usage
define('MAIN_INCLUDED', 1); // Token and SQL credential files may be protected locally and require this to be defined to access
//if (! $token_included = require getcwd() . '/token.php') // $token
//throw new \Exception('Token file not found. Create a file named token.php in the root directory with the bot token.');
if (! $autoloader = require file_exists(__DIR__.'/vendor/autoload.php') ? __DIR__.'/vendor/autoload.php' : __DIR__.'/../../autoload.php')
throw new \Exception('Composer autoloader not found. Run `composer install` and try again.');
function loadEnv(string $filePath = __DIR__ . '/.env'): void
{
if (! file_exists($filePath)) throw new Exception("The .env file does not exist.");
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$trimmedLines = array_map('trim', $lines);
$filteredLines = array_filter($trimmedLines, fn($line) => $line && ! str_starts_with($line, '#'));
array_walk($filteredLines, function($line) {
[$name, $value] = array_map('trim', explode('=', $line, 2));
if (! array_key_exists($name, $_ENV)) putenv(sprintf('%s=%s', $name, $value));
});
}
loadEnv(getcwd() . '/.env');
file_put_contents('output.log', ''); // Clear the contents of 'output.log'
$fileHandler = (new StreamHandler('output.log', Level::Debug))->setFormatter(new LineFormatter(null, null, true, true, true));
$stdoutHandler = (new StreamHandler('php://stdout', Level::Info))->setFormatter(new LineFormatter(null, null, true, true, true));
$logger = new Logger('Civ13', [$fileHandler, $stdoutHandler]);
Loop::addPeriodicTimer(60 * 10, fn() => $logger->reset()); // Flush all buffers every 10 minutes
$logger->info('Loading configurations for the bot...');
set_rejection_handler(function(\Throwable $e) use ($logger) {
if ($e->getMessage() !== 'Cannot resume a fiber that is not suspended') $logger->warning("Unhandled Promise Rejection: {$e->getMessage()} [{$e->getFile()}:{$e->getLine()}] " . str_replace('#', '\n#', $e->getTraceAsString()));
});
$discord = new Discord([
'loop' => Loop::get(),
'logger' => $logger,
/*
'cache' => new CacheConfig(
$interface = new RedisCache(
(new Redis(Loop::get()))->createLazyClient('127.0.0.1:6379'),
'dphp:cache:
'),
$compress = true, // Enable compression if desired
$sweep = false // Disable automatic cache sweeping if desired
),
*/
'socket_options' => [
'dns' => '8.8.8.8', // can change dns
],
'token' => getenv('TOKEN'),
'loadAllMembers' => true,
'storeMessages' => true, // Because why not?
'intents' => Intents::getAllIntents(),
'useTransportCompression' => false, // Disable zlib-stream
'usePayloadCompression' => true,
]);
$stats = Stats::new($discord);
$browser = new Browser(Loop::get());
$filesystem = FilesystemFactory::create(Loop::get());
include 'variable_functions.php';
$http_whitelist = [
$civ13_ip = gethostbyname('www.civ13.com'),
$vzg_ip = gethostbyname('www.valzargaming.com'),
$val_ip = gethostbyname('www.valgorithms.com'),
'47.134.24.40'
];
$webapi = null;
$socket = null;
/* Format:
'word' => 'bad word' // Bad word to look for
'duration' => duration ['1 minute', '1 hour', '1 day', '1 week', '1 month', '999 years'] // Duration of the ban
'reason' => 'reason' // Reason for the ban
'category' => rule category ['racism/discrimination', 'toxic', 'advertisement'] // Used to group bad words together by category
'method' => detection method ['exact', 'str_contains', 'str_ends_with', 'str_starts_with'] // Exact ignores partial matches, str_contains matches partial matches, etc.
'warnings' => 1 // Number of warnings before a ban
*/
$ic_badwords = $ooc_badwords = [
//['word' => 'badwordtestmessage', 'duration' => '1 minute', 'reason' => 'Violated server rule.', 'category' => 'test', 'method' => 'str_contains', 'warnings' => 1], // Used to test the system
['word' => 'beaner', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'chink', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'coon', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'exact', 'warnings' => 1],
['word' => 'fag', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'gook', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'kike', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'nigg', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'nlgg', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'niqq', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'tranny', 'duration' => '1 month', 'reason' => 'Racism and Discrimination.', 'category' => 'racism/discrimination', 'method' => 'str_contains', 'warnings' => 1],
['word' => 'cunt', 'duration' => '1 minute', 'reason' => 'You must not be toxic or too agitated in any OOC communication channels.', 'category' => 'toxic', 'method' => 'exact', 'warnings' => 5],
['word' => 'retard', 'duration' => '1 minute', 'reason' => 'You must not be toxic or too agitated in any OOC communication channels.', 'category' => 'toxic', 'method' => 'exact', 'warnings' => 5],
['word' => 'stfu', 'duration' => '1 minute', 'reason' => 'You must not be toxic or too agitated in any OOC communication channels.', 'category' => 'toxic', 'method' => 'exact', 'warnings' => 5],
['word' => 'kys', 'duration' => '1 week', 'reason' => 'You must not be toxic or too agitated in any OOC communication channels.', 'category' => 'toxic', 'method' => 'exact', 'warnings' => 1], // This is more severe than the others, so ban after only one warning
['word' => 'penis', 'duration' => '1 month', 'reason' => 'There is a zero tolerance policy towards any type of lewdness.', 'category' => 'erp', 'method' => 'str_contains', 'warnings' => 1], // This is more severe than the others, so ban after only one warning
['word' => 'vagina', 'duration' => '1 month', 'reason' => 'There is a zero tolerance policy towards any type of lewdness.', 'category' => 'erp', 'method' => 'str_contains', 'warnings' => 1], // This is more severe than the others, so ban after only one warning
['word' => 'sex', 'duration' => '1 month', 'reason' => 'There is a zero tolerance policy towards any type of lewdness.', 'category' => 'erp', 'method' => 'exact', 'warnings' => 1], // This is more severe than the others, so ban after only one warning
['word' => 'cum', 'duration' => '1 month', 'reason' => 'There is a zero tolerance policy towards any type of lewdness.', 'category' => 'erp', 'method' => 'exact', 'warnings' => 1], // This is more severe than the others, so ban after only one warning
['word' => 'discord.gg', 'duration' => '1 month', 'reason' => 'You must not post unauthorized Discord invitation links in any OOC communication channels.', 'category' => 'advertisement', 'method' => 'str_contains', 'warnings' => 2],
['word' => 'discord.com', 'duration' => '1 month', 'reason' => 'You must not post unauthorized Discord invitation links in any OOC communication channels.', 'category' => 'advertisement', 'method' => 'str_contains', 'warnings' => 2],
['word' => 'RU', 'duration' => '2 minutes', 'reason' => 'только английский.', 'category' => 'language', 'method' => 'russian', 'warnings' => 2],
['word' => 'CN', 'duration' => '2 minutes', 'reason' => '仅英语.', 'category' => 'language', 'method' => 'chinese', 'warnings' => 2],
['word' => 'KR', 'duration' => '2 minutes', 'reason' => '영어로만 제공.', 'category' => 'language', 'method' => 'korean', 'warnings' => 2],
];
$options = array(
'github' => 'https://github.com/VZGCoders/Civilizationbot',
'command_symbol' => '@Civilizationbot',
'owner_id' => '196253985072611328', // Taislin
'technician_id' => '116927250145869826', // Valithor
'civ13_guild_id' => '468979034571931648', // Civ13
'discord_invite' => 'https://civ13.com/discord',
'discord_formatted' => 'civ13.com slash discord',
'rules' => 'civ13.com slash rules',
'gitdir' => '/home/civ13/civ13-git', // Path to the git repository
'legacy' => true, // Whether to use the filesystem or SQL database system
'moderate' => true, // Whether to moderate in-game chat
// The Verify URL is where verification requests are sent to and where the verification list is retrieved from
// The website must return valid json when no parameters are passed to it and MUST allow POST requests including 'token', 'ckey', and 'discord'
// Reach out to Valithor if you need help setting up your website
'webserver_url' => 'www.valzargaming.com',
//'verify_url' => 'http://valzargaming.com:8080/verified/', // Leave this blank if you do not want to use the webserver, ckeys will be stored locally as provisional
'verify_url' => getenv('VERIFIER_HOST_ADDR') . ':' . getenv('VERIFIER_HOST_PORT') . '/verified/', // Local/Integrated Verifier Server
// 'serverinfo_url' => '', // URL of the serverinfo.json file, defaults to the webserver if left blank
'ooc_badwords' => $ooc_badwords,
'ic_badwords' => $ic_badwords,
'folders' => array(
// 'typespess_path' => '/home/civ13/civ13-typespess',
'ss14_basedir' => '/home/civ13/civ14'
),
'files' => array( // Server-specific file paths MUST start with the server name as defined in civ13_server_settings unless otherwise specified
// 'typespess_launch_server_path' => '/home/civ13/civ13-typespess/scripts/launch_server.sh',
),
'channel_ids' => array(
//'get-approved' => '690025163634376738', #get-approved (Deprecated)
'bot-stuff' => '932431238894092309', #bot-stuff
'information' => '857295113071362069', #information
'webserver-status' => '1106967195092783104', #webserver-{status}
'verifier-status' => '1231988255470125117', #verifier-{status}
'staff_bot' => '712685552155230278', // #staff-bot
'parole_logs' => '985606778916048966', // #parole-logs (for tracking)
'parole_notif' => '977715818731294790', // #parole-notif (for login/logout notifications)
'email' => '1225600172336353430', // #email
'ban_appeals' => '1019718839749062687' #ban-appeals
),
'role_ids' => array( // The keys in this array must directly correspond to the expected role names and as defined in GameServer.php. Do not alter these keys unless you know what you are doing.
/* Discord Staff Roles */
'Owner' => '468980650914086913', // Discord Server Owner
'Chief Technical Officer' => '791450326455681034', // Debug Host / Database admin
'Host' => '677873806513274880', // Server Host
'Head Admin' => '487608503553490965', // Deprecation TBD
//'Manager' => '496004389950193667', // Deprecated
'Ambassador' => '792826030796308503', // High Staff
//'Supervisor' => '561770271300911105', // Deprecated
'Admin' => '468982360659066912',
//'Moderator' => '823302316743589938', // Deprecated
//'Mentor' => '469297467918254085', // Deprecated
'Parolemin' => '743971427929030748', // Parole Admin
/* Discord Player Roles */
'SS14 Verified' => '1352299214326992937', // SS14 Verified
'Verified' => '468982790772228127', // Verified
'Banished' => '710328377210306641', // Banned in-game
'Permabanished' => '1126137099209425017', // Permanently banned in-game
'Paroled' => '745336314689355796', // On parole
/* Factions */
'Red Faction' => '1132678312301428886', // Redmenia
'Blue Faction' => '1132678353070067802', // Blugoslavia
'Faction Organizer' => '1089060051425165362', // Admin / Faction Organizer
/* Notification pings */
'mapswap' => '1200520534262284288', // Map Swap Ping
'round_start' => '1110597830403424328', // Round Start Ping
'2+' => '981963719804346418', // LowPopStart
'15+' => '981963721817620511', // 15+ Popping
'30+' => '981963696895062106', // 30+ Popping
/* Server pings (Deprecated) */
//'tdm' => '753768519203684445',
//'nomads' => '753768513671397427',
//'pers' => '753768492834095235',
),
);
$options['welcome_message'] = 'Welcome to the Civ13 Discord Server! Please read the rules and verify your account using the `/approveme` slash command (Civ13) or the `/verifyme` slash command (Civ14).' ; // . ' Failure to verify in a timely manner will result in an automatic removal from the server.';
/*
foreach (['а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я', 'і', 'ї', 'є'] as $char) { // // Ban use of Cyrillic characters
$arr = ['word' => $char, 'duration' => '999 years', 'reason' => 'только английский.', 'category' => 'language', 'method' => 'str_contains', 'warnings' => 2];
$options['ooc_badwords'][] = $arr;
$options['ic_badwords'][] = $arr;
}
*/
// Write editable configurations to a single JSON file
//$json = json_encode($options, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
//file_put_contents("config.json", $json);
// Load configurations from the JSON file
/*
$loadedData = [];
$json = file_get_contents("config.json");
$loadedData = json_decode($json, true);
foreach ($loadedData as $key => $value) $options[$key] = $value;
*/
//TODO: Move this to a separate file, like .env
$civ13_server_settings = [ // Server specific settings, listed in the order in which they appear on the VZG server list.
'tdm' => [
'supported' => true,
'enabled' => true,
'name' => 'TDM',
//'key' => 'tdm',
'ip' => $civ13_ip,
'port' => '1714',
'host' => 'Taislin',
'panic_bunker' => false,
'log_attacks' => false,
'legacy' => true,
'moderate' => true,
'legacy_relay' => false,
'basedir' => '/home/civ13/civ13-tdm',
// Primary channels
'discussion' => '799952134426591273',
'playercount' => '1048777462898761789',
// Chat relay channels
'ooc' => '1107016184328622233',
'lobby' => '1107021760483831988',
'asay' => '1107016769169801216',
'ic' => '1121531682198138920',
// Log channels
'transit' => '1107020747622326313',
'adminlog' => '1107024305927225455',
'debug' => '1106248157798600715',
'garbage' => '1107018726307528735',
'runtime' => '1107017103883632792',
'attack' => '1107017830160936980',
],
'nomads' => [ // This is the endpoint you'll add to config.txt, (e.g. WEBHOOK_ADDRESS http://127.0.0.1:55555/webhook/nomads)
'supported' => true, // Whether the server is supported by the remote webserver
'enabled' => true, // Whether the server should have commands handled by the bot
'name' => 'Nomads', // Name of the server and the prefix of the playercount channel (e.g. nomads-999)
//'key' => 'nomads', // This must match the top-level key in the civ13_server_settings array
'ip' => $civ13_ip, // IP of the server
'port' => '1715', // Port of the server
'host' => 'Taislin', // Host of the server
'panic_bunker' => true, // Panic mode will ban all users who are not verified
'log_attacks' => true, // Only recommended to set to false to mitigate logging spam
'legacy' => true, // Legacy mode will use the file system instead of an SQL database
'moderate' => true, // Whether chat moderation is enabled
'legacy_relay' => false, // How messages are relayed to the server
'basedir' => '/home/civ13/civ13-rp', // Base directory of the server
// Primary channels
'discussion' => '799952084505067581', // #nomads
'playercount' => '1048777424894185484', // nomads-#
// Chat relay channels
'ooc' => '1110001963405418616', // #ooc-nomads
'lobby' => '1110001986134347856', // #lobby-nomads
'asay' => '1110002005977604186', // #asay-nomads
'ic' => '1121531739114852432', // #ic-nomads
// Log channels
'transit' => '1110002027469221989', // #transit-nomads
'adminlog' => '1110002047123738624', // #adminlog-nomads
'debug' => '1106248132779593758', // #debug-nomads (debugging)
'garbage' => '1110002493259251752', // #garbage-nomads
'runtime' => '1110002671936602132', // #runtime-nomads
'attack' => '1110002697383448648', // #attack-nomads
],
'pers' => [
'supported' => true,
'enabled' => false,
'name' => 'Persistence',
//'key' => 'pers',
'ip' => $vzg_ip,
'port' => '1716',
'host' => 'ValZarGaming',
'panic_bunker' => true,
'log_attacks' => true,
'legacy' => true,
'moderate' => true,
'legacy_relay' => false,
'basedir' => '/home/valithor/VPS/civ13-rp',
// Primary channels
'discussion' => '799951945346711552',
'playercount' => '1090788345082298369',
// Chat relay channels
'ooc' => '1139614228408455388',
'lobby' => '1139614248222343282',
'asay' => '1139614266299785278',
'ic' => '1139614281512529941',
// Log channels
'transit' => '1139614542700216420',
'adminlog' => '1139614564577722448',
'debug' => '1139614582931984575',
'garbage' => '1139614596789964820',
'runtime' => '1139614629081915492',
'attack' => '1139614643954921593',
],
];
foreach ($civ13_server_settings as $key => $value) $civ13_server_settings[$key]['key'] = $key; // Key is intended to be a shortname for the full server, so defining both a full name and short key are required. Individual server settings will also get passed around and lose their primary key, so we need to reassign it.
$civ14_server_settings = [
'civ14' => [
//'supported' => true,
'enabled' => true,
'name' => 'Civilization 14',
'protocol' => 'http',
//'key' => 'civ14',
'ip' => $civ13_ip,
'port' => '1212',
'host' => 'Taislin',
//'panic_bunker' => true,
//'log_attacks' => true,
//'legacy' => true,
//'moderate' => true,
//'legacy_relay' => false,
'basedir' => '/home/civ13/civ14-server',
// Primary channels
'discussion' => '1364992147664932896',
'playercount' => '1354164249487737013',
],
];
foreach ($civ14_server_settings as $key => $value) $civ14_server_settings[$key]['key'] = $key; // Key is intended to be a shortname for the full server, so defining both a full name and short key are required. Individual server settings will also get passed around and lose their primary key, so we need to reassign it.
$hidden_options = [
'loop' => Loop::get(),
'discord' => $discord,
'browser' => $browser,
'filesystem' => $filesystem,
'logger' => $logger,
'stats' => $stats,
'webapi' => &$webapi,
'socket' => &$socket,
'web_address' => getenv('web_address') ?: 'www.civ13.com',
'http_port' => intval(getenv('http_port')) ?: 55555, // 25565 for testing on Windows
'http_key' => getenv('WEBAPI_TOKEN') ?: 'CHANGEME',
'http_whitelist' => $http_whitelist,
'civ_token' => getenv('CIV_TOKEN') ?: 'CHANGEME',
'civ13_server_settings' => $civ13_server_settings, // Server specific settings, listed in the order in which they appear on the VZG server list.
'functions' => array(
'init' => [
// 'on_ready' => $on_ready,
'status_changer_timer' => $status_changer_timer,
'status_changer_random' => $status_changer_random,
],
'misc' => [ // Custom functions
//
],
),
];
$options = array_merge($options, $hidden_options);
$civ13 = null;
$global_error_handler = async(function (int $errno, string $errstr, ?string $errfile, ?int $errline) use (&$civ13, &$logger, &$testing) {
/** @var ?Civ13 $civ13 */
if (
$civ13 && // If the bot is running
($channel = $civ13->discord->getChannel($civ13->channel_ids['staff_bot']))
// fsockopen
&& ! str_ends_with($errstr, 'Connection timed out')
&& ! str_ends_with($errstr, '(Connection timed out)')
&& ! str_ends_with($errstr, 'Connection refused') // Usually happens if the verifier server doesn't respond quickly enough
&& ! str_contains($errstr, '(Connection refused)') // Usually happens in localServerPlayerCount
//&& ! str_ends_with($errstr, 'Network is unreachable')
//&& ! str_ends_with($errstr, '(Network is unreachable)')
&& ! str_ends_with($errstr, '(A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond)')
// Connectivity issues
&& ! str_ends_with($errstr, 'No route to host') // Usually happens if the verifier server is down
&& ! str_ends_with($errstr, 'No address associated with hostname') // Either the DNS or the VPS is acting up
&& ! str_ends_with($errstr, 'Temporary failure in name resolution') // Either the DNS or the VPS is acting up
&& ! str_ends_with($errstr, 'Bad Gateway') // Usually happens if the verifier server's PHP-CGI is down
&& ! str_ends_with($errstr, '(Network is unreachable)') // stream_socket_connect issues within ReactPHP/socket
//&& ! str_ends_with($errstr, 'HTTP request failed!')
//&& ! str_contains($errstr, 'Undefined array key')
)
{
$logger->error($msg = sprintf("[%d] Fatal error on `%s:%d`: %s\nBacktrace:\n```\n%s\n```", $errno, $errfile, $errline, $errstr, implode("\n", array_map(fn($trace) => ($trace['file'] ?? '') . ':' . ($trace['line'] ?? '') . ($trace['function'] ?? ''), debug_backtrace()))));
if (isset($civ13->technician_id) && $tech_id = $civ13->technician_id) $msg = "<@{$tech_id}>, $msg";
if (! $testing) $civ13->sendMessage($channel, $msg);
}
});
set_error_handler($global_error_handler);
use React\Socket\SocketServer;
use React\Http\HttpServer;
use React\Http\Message\Response;
use Psr\Http\Message\ServerRequestInterface;
$socket = new SocketServer(
sprintf('%s:%s', '0.0.0.0', getenv('http_port') ?: 55555),
[
'tcp' => [
'so_reuseport' => true
]
],
Loop::get()
);
/**
* Handles the HTTP request using the HttpServiceManager.
*
* @param ServerRequestInterface $request The HTTP request object.
* @return Response The HTTP response object.
*/
$webapi = new HttpServer(Loop::get(), async(function (ServerRequestInterface $request) use (&$civ13, &$logger): Response
{
/** @var ?Civ13 $civ13 */
if (! $civ13 || ! $civ13 instanceof Civ13) {
$logger->warning('Civ13 instance not found. Please check the server settings.');
return new Response(Response::STATUS_SERVICE_UNAVAILABLE, ['Content-Type' => 'text/plain'], 'Service Unavailable');
}
if (! isset($civ13->httpServiceManager)) {
$logger->warning('HttpServiceManager not found. Please check the server settings.');
return new Response(Response::STATUS_SERVICE_UNAVAILABLE, ['Content-Type' => 'text/plain'], 'Service Unavailable');
}
if (! $civ13->ready) return new Response(Response::STATUS_SERVICE_UNAVAILABLE, ['Content-Type' => 'text/plain'], 'Service Not Yet Ready');
return $civ13->httpServiceManager->handle($request);
}));
/**
* This code snippet handles the error event of the web API.
* It logs the error message, file, line, and trace, and handles specific error cases.
* If the error message starts with 'Received request with invalid protocol version', it is ignored.
* If the error message starts with 'The response callback', it triggers a restart process.
* The restart process includes sending a message to a specific Discord channel and closing the socket connection.
* After a delay of 5 seconds, the script is restarted by calling the 'restart' function and closing the Discord connection.
*
* @param Exception $e The exception object representing the error.
* @param \Psr\Http\Message\RequestInterface|null $request The HTTP request object associated with the error, if available.
* @param object $civ13 The main object of the application.
* @param object $socket The socket object.
* @param bool $testing Flag indicating if the script is running in testing mode.
* @return void
*/
$webapi->on('error', async(function (Exception $e, ?\Psr\Http\Message\RequestInterface $request = null) use (&$civ13, &$logger, &$socket) {
if (
str_starts_with($e->getMessage(), 'Received request with invalid protocol version')
) return; // Ignore this error, it's not important
$error = "[WEBAPI] {$e->getMessage()} [{$e->getFile()}:{$e->getLine()}] " . str_replace('\n', PHP_EOL, $e->getTraceAsString());
$logger->error("[WEBAPI] $error");
if ($request) $logger->error('[WEBAPI] Request: ' . preg_replace('/(?<=key=)[^&]+/', '********', $request->getRequestTarget()));
if (str_starts_with($e->getMessage(), 'The response callback')) {
$logger->info('[WEBAPI] ERROR - RESTART');
/** @var ?Civ13 $civ13 */
if (! $civ13) return;
if (! getenv('testing') && isset($civ13->channel_ids['staff_bot']) && $channel = $civ13->discord->getChannel($civ13->channel_ids['staff_bot'])) {
$builder = Civ13::createBuilder()
->setContent('Restarting due to error in HttpServer API...')
->addFileFromContent('httpserver_error.txt', preg_replace('/(?<=key=)[^&]+/', '********', $error));
$channel->sendMessage($builder);
}
$socket->close();
if (! isset($civ13->timers['restart'])) $civ13->timers['restart'] = Loop::addTimer(5, fn() => $civ13->restart());
}
}));
//$events = ['MESSAGE_UPDATE'];
//$eventLogger = new \EventLogger\EventLogger($discord, $events);
use VerifierServer\Server as VerifierServer;
Loop::futureTick(async(static function () use (&$civ13, &$logger, $options, $civ13_server_settings, $civ14_server_settings) {
$verifier_server = new VerifierServer(
getenv('VERIFIER_HOST_ADDR'),
intval(getenv('VERIFIER_HOST_PORT'))
);
$verifier_server->init(Loop::get());
$verifier_server->setLogger($logger);
$verifier_server->setState([
getenv('CIV_TOKEN'),
getenv('VERIFIER_STORAGE_TYPE') ?: 'filesystem',
getenv('VERIFIER_JSON_PATH') ?: 'json/verified.json',
]);
$verifier_server->setSS14State([
getenv('CIV_TOKEN'),
getenv('SS14_VERIFIER_STORAGE_TYPE') ?: 'filesystem',
getenv('SS14_VERIFIER_JSON_PATH') ?: 'json/ss14verified.json',
]);
$verifier_server->setSS14OAuth2Endpoint(
getenv('SS14_OAUTH2_CLIENT_ID'),
getenv('SS14_OAUTH2_CLIENT_SECRET')
);
$verifier_server->setDiscordOAuth2Endpoint(
getenv('dwa_client_id'),
getenv('dwa_client_secret')
);
$civ13 = new Civ13(
$options,
$civ13_server_settings,
$civ14_server_settings,
$verifier_server
);
$civ13->run();
}));