From 27c51bdd230ac6733dc6ea329274596865115e14 Mon Sep 17 00:00:00 2001 From: Zi Xing Date: Sat, 18 Dec 2021 20:23:02 -0500 Subject: [PATCH] Init --- .gitignore | 1 + .ppm_package | 1 + Makefile | 41 +++++++ botsrc/.htaccess | 7 ++ botsrc/SynicalBot.php | 227 +++++++++++++++++++++++++++++++++++++ botsrc/main | 177 +++++++++++++++++++++++++++++ botsrc/package.json | 233 ++++++++++++++++++++++++++++++++++++++ botsrc/worker | 252 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 939 insertions(+) create mode 100644 .gitignore create mode 100644 .ppm_package create mode 100644 Makefile create mode 100644 botsrc/.htaccess create mode 100644 botsrc/SynicalBot.php create mode 100644 botsrc/main create mode 100644 botsrc/package.json create mode 100644 botsrc/worker diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/.ppm_package b/.ppm_package new file mode 100644 index 0000000..503cafe --- /dev/null +++ b/.ppm_package @@ -0,0 +1 @@ +botsrc \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..732f7ac --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +clean: + # Removes the build directory + rm -rf build + +update: + # Updates the package.json file + ppm --generate-package="botsrc" + +build: + # Compiles the package + mkdir build + ppm --compile="botsrc" --directory="build" + +install: + # Installs the compiled package to the system + ppm --fix-conflict --no-prompt --install="build/net.intellivoid.spam_protection_bot.ppm" --branch="production" + +install_fast: + # Installs the compiled package to the system + ppm --fix-conflict --no-prompt --skip-dependencies --install="build/net.intellivoid.spam_protection_bot.ppm" --branch="production" + +run: + # Runs the bot + ppm --main="net.intellivoid.spam_protection_bot" --version="latest" + +stop: + # Stops the main execution point + pkill -f 'main=net.intellivoid.spam_protection_bot' + +stop_workers: + # Stops the sub-workers created by BackgroundWorker + pkill -f 'worker-name=PublicServerchanBot' + +debug: + # Starts the bot, kills all the workers and focuses on one worker in STDOUT + # Run with -i to ignore possible errors. + make stop + screen -dm bash -c 'ppm --main="net.intellivoid.spam_protection_bot" --version="latest"' + sleep 3 + make stop_workers + php botsrc/worker.php \ No newline at end of file diff --git a/botsrc/.htaccess b/botsrc/.htaccess new file mode 100644 index 0000000..2eb8f39 --- /dev/null +++ b/botsrc/.htaccess @@ -0,0 +1,7 @@ +Options -Indexes +Order deny,allow +Deny from all + + + Allow from all + \ No newline at end of file diff --git a/botsrc/SynicalBot.php b/botsrc/SynicalBot.php new file mode 100644 index 0000000..4b14ca4 --- /dev/null +++ b/botsrc/SynicalBot.php @@ -0,0 +1,227 @@ +setName('TelegramService'); + $TelegramSchema->setDefinition('BotName', ''); + $TelegramSchema->setDefinition('BotToken', ''); + $TelegramSchema->setDefinition('BotEnabled', true); + $TelegramSchema->setDefinition('UseTestServers', false); + $TelegramSchema->setDefinition('EnableCustomServer', true); + $TelegramSchema->setDefinition('CustomEndpoint', 'http://127.0.0.1:8081'); + $TelegramSchema->setDefinition('CustomDownloadEndpoint', '/file/bot{API_KEY}'); + $TelegramSchema->setDefinition('MainOperators', []); + $TelegramSchema->setDefinition('LoggingChannel', 'SpamProtectionLogs'); + $TelegramSchema->setDefinition('VerboseLogging', false); + + $acm->defineSchema($TelegramSchema); + + $BackgroundWorkerSchema = new Schema(); + $BackgroundWorkerSchema->setName('BackgroundWorker'); + $BackgroundWorkerSchema->setDefinition('Host', '127.0.0.1'); + $BackgroundWorkerSchema->setDefinition('Port', 4730); + $BackgroundWorkerSchema->setDefinition('MaxWorkers', 5); + $acm->defineSchema($BackgroundWorkerSchema); + + $RedisSchema = new Schema(); + $RedisSchema->setName('Redis'); + $RedisSchema->setDefinition('Host', '127.0.0.1'); + $RedisSchema->setDefinition('Port', 6379); + $RedisSchema->setDefinition('Username', ''); + $RedisSchema->setDefinition('Password', ''); + $RedisSchema->setDefinition('Database', 0); + $acm->defineSchema($RedisSchema); + + $acm->updateConfiguration(); + return $acm; + } + + /** + * Returns the Telegram Service configuration + * + * @return mixed + * @throws Exception + */ + public static function getTelegramConfiguration() + { + return self::autoConfig()->getConfiguration('TelegramService'); + } + + /** + * Returns the database configuration + * + * @return mixed + * @throws Exception + */ + public static function getDatabaseConfiguration() + { + return self::autoConfig()->getConfiguration('Database'); + } + + /** + * Returns the redis configuration + * + * @return mixed + * @throws Exception + * @noinspection PhpUnused + */ + public static function getRedisConfiguration() + { + return self::autoConfig()->getConfiguration('Redis'); + } + + /** + * Returns the background worker configuration + * + * @return mixed + * @throws Exception + */ + public static function getBackgroundWorkerConfiguration() + { + return self::autoConfig()->getConfiguration('BackgroundWorker'); + } + + /** + * @return TelegramClientManager + */ + public static function getTelegramClientManager(): TelegramClientManager + { + return self::$TelegramClientManager; + } + + /** + * @return BackgroundWorker + */ + public static function getBackgroundWorker(): BackgroundWorker + { + return self::$BackgroundWorker; + } + + /** + * @return VerboseAdventure + */ + public static function getLogHandler(): VerboseAdventure + { + return self::$LogHandler; + } + + /** + * @param VerboseAdventure $LogHandler + */ + public static function setLogHandler(VerboseAdventure $LogHandler): void + { + self::$LogHandler = $LogHandler; + } + + /** + * @return int + */ + public static function getLastWorkerActivity(): int + { + return self::$LastWorkerActivity; + } + + /** + * @param int $LastWorkerActivity + * @noinspection PhpUnused + */ + public static function setLastWorkerActivity(int $LastWorkerActivity): void + { + self::$LastWorkerActivity = $LastWorkerActivity; + } + + /** + * @return bool + */ + public static function isSleeping(): bool + { + return self::$IsSleeping; + } + + /** + * @param bool $IsSleeping + */ + public static function setIsSleeping(bool $IsSleeping): void + { + self::$IsSleeping = $IsSleeping; + } + + /** + * Determines if this current worker should save resources by going to sleep or wake up depending on the + * last activity cycle + * @noinspection PhpUnused + */ + public static function processSleepCycle() + { + if(time() - self::getLastWorkerActivity() > 60) + { + if(self::isSleeping() == false) + { + self::getTelegramClientManager()->disconnectDatabase(); + self::setIsSleeping(true); + } + } + else + { + if(self::isSleeping() == true) + { + self::getTelegramClientManager()->connectDatabase(); + self::setIsSleeping(false); + } + } + } + } \ No newline at end of file diff --git a/botsrc/main b/botsrc/main new file mode 100644 index 0000000..1c1f1e6 --- /dev/null +++ b/botsrc/main @@ -0,0 +1,177 @@ +log(EventType::INFO, 'Starting Service', 'Main'); + + try + { + if($TelegramServiceConfiguration['EnableCustomServer']) + { + Request::setCustomBotApiUri( + $TelegramServiceConfiguration['CustomEndpoint'], + $TelegramServiceConfiguration['CustomDownloadEndpoint'] + ); + + define('TELEGRAM_ENDPOINT', $TelegramServiceConfiguration['CustomEndpoint']); + define('TELEGRAM_DOWNLOAD_ENDPOINT', + str_ireplace('{API_KEY}', $TelegramServiceConfiguration['BotToken'], $TelegramServiceConfiguration['CustomDownloadEndpoint'])); + } + else + { + define('TELEGRAM_ENDPOINT', 'https://api.telegram.org'); + define('TELEGRAM_DOWNLOAD_ENDPOINT', '/file/bot' . $TelegramServiceConfiguration['BotToken']); + } + + $telegram = new Longman\TelegramBot\Telegram( + $TelegramServiceConfiguration['BotToken'], + $TelegramServiceConfiguration['BotName'], + $TelegramServiceConfiguration['UseTestServers'] + ); + $telegram->setVerboseLogging($TelegramServiceConfiguration['VerboseLogging']); + } + catch (Longman\TelegramBot\Exception\TelegramException $e) + { + SynicalBot::getLogHandler()->logException($e, 'Main'); + exit(255); + } + + $telegram->useGetUpdatesWithoutDatabase(); + + // Start the workers using the supervisor + SynicalBot::getLogHandler()->log(EventType::INFO, 'Starting Supervisor', 'Main'); + + try + { + SynicalBot::$BackgroundWorker = new BackgroundWorker(); + SynicalBot::$BackgroundWorker->getSupervisor()->setDisplayOutput(TELEGRAM_BOT_NAME, true); + SynicalBot::getBackgroundWorker()->getClient()->addServer($BackgroundWorkerConfiguration['Host'], (int)$BackgroundWorkerConfiguration['Port']); + SynicalBot::getBackgroundWorker()->getSupervisor()->startWorkers( + getcwd() . DIRECTORY_SEPARATOR . 'worker', TELEGRAM_BOT_NAME, + (int)$BackgroundWorkerConfiguration['MaxWorkers'] + ); + } + catch(Exception $e) + { + SynicalBot::getLogHandler()->logException($e, 'Main'); + exit(255); + } + + // Start the verdict thread + $phpExecutableFinder = new PhpExecutableFinder(); + $phpBinLocation = $phpExecutableFinder->find(); + $VerdictThread = new Process([$phpBinLocation, getcwd() . DIRECTORY_SEPARATOR . 'verdict_worker']); + $VerdictCallbackThread = function ($type, $buffer) + { + $buffer_split = implode("\n", explode("\r\n", $buffer)); + $buffer_split = explode("\n", $buffer_split); + foreach ($buffer_split as $item) { + if (strlen($item) == 0) + { + continue; + } + if (stripos($item, 'flush(gearman_could_not_connect)')) + { + continue; + } + switch (strtolower($type)) { + case "out": + case StdType::STDOUT: + SynicalBot::getLogHandler()->log(EventType::INFO, $item, 'Verdict Thread'); + break; + case "err": + case StdType::STDERR: + SynicalBot::getLogHandler()->log(EventType::ERROR, $item, 'Verdict Thread'); + break; + } + } + }; + $VerdictThread->start($VerdictCallbackThread); + + $next_event_update = time() + 60; + $total_update_count = 0; + + // Start listening to updates + while(true) + { + /** @noinspection PhpUnhandledExceptionInspection */ + SynicalBot::$BackgroundWorker->getSupervisor()->monitor(TELEGRAM_BOT_NAME); + if($VerdictThread->isRunning() == false) + { + $VerdictThread->restart($VerdictCallbackThread); + } + + try + { + $server_response = $telegram->handleBackgroundUpdates(SynicalBot::getBackgroundWorker()); + + if ($server_response->isOk()) + { + $update_count = count($server_response->getResult()); + if($update_count > 0) + { + $total_update_count += $update_count; + if(time() >= $next_event_update) + { + SynicalBot::getLogHandler()->log(EventType::INFO, 'Processed ' . $total_update_count . ' update(s)', 'Main'); + $total_update_count = 0; + $next_event_update = time() + 60; + } + } + } + else + { + SynicalBot::getLogHandler()->log(EventType::ERROR, 'Failed to fetch updates: ' . $server_response->printError(true), 'Main'); + + } + } + catch (TelegramException $e) + { + SynicalBot::getLogHandler()->logException($e, 'Main'); + } + } diff --git a/botsrc/package.json b/botsrc/package.json new file mode 100644 index 0000000..f480c3e --- /dev/null +++ b/botsrc/package.json @@ -0,0 +1,233 @@ +{ + "package": { + "package_name": "net.intellivoid.synical_bot", + "name": "Telegram SynicalBot", + "version": "3.0.0.0", + "author": "Zi Xing Narrakas", + "organization": "Intellivoid", + "description": "A next-generation group management bot", + "url": "https://github.com/intellivoid/SynicalBot", + "dependencies": [ + { + "package": "net.intellivoid.acm2", + "version": "latest", + "source": "default@github/intellivoid/acm2", + "required": true + }, + { + "package": "net.intellivoid.background_worker", + "version": "latest", + "source": "default@github/intellivoid/BackgroundWorker", + "required": true + }, + { + "package": "net.intellivoid.coffeehouse", + "version": "latest", + "source": "default@github/intellivoid/coffeehouse", + "required": true + }, + { + "package": "net.intellivoid.deepanalytics", + "version": "latest", + "source": "default@github/intellivoid/DeepAnalytics", + "required": true + }, + { + "package": "net.intellivoid.spam_protection", + "version": "latest", + "source": "default@github/intellivoid/SpamProtection", + "required": true + }, + { + "package": "net.intellivoid.telegram_client_manager", + "version": "latest", + "source": "default@github/intellivoid/IVA-Telegram", + "required": true + }, + { + "package": "net.intellivoid.pop", + "version": "latest", + "source": "default@github/intellivoid/pop", + "required": true + }, + { + "package": "net.intellivoid.msqg", + "version": "latest", + "source": "default@github/intellivoid/msqg", + "required": true + }, + { + "package": "net.intellivoid.ziproto", + "version": "latest", + "source": "default@github/intellivoid/ziproto", + "required": true + }, + { + "package": "net.intellivoid.verbose_adventure", + "version": "latest", + "source": "default@github/intellivoid/VerboseAdventure", + "required": true + }, + { + "package": "net.intellivoid.tdlib", + "version": "latest", + "source": "default@github/intellivoid/tdlib", + "required": true + }, + { + "package": "net.intellivoid.tmpfile", + "version": "latest", + "source": "default@github/intellivoid/TmpFile", + "required": true + }, + { + "package": "net.intellivoid.proclib", + "version": "latest", + "source": "default@github/intellivoid/ProcLib", + "required": true + } + ], + "configuration": { + "autoload_method": "generated_spl", + "main": { + "execution_point": "main", + "create_symlink": false, + "name": null + }, + "post_installation": [], + "pre_installation": [] + } + }, + "components": [ + { + "required": true, + "file": "commands/AgdemoteCommand.php" + }, + { + "required": true, + "file": "commands/AgpromoteCommand.php" + }, + { + "required": true, + "file": "commands/AppealCommand.php" + }, + { + "required": true, + "file": "commands/BakaMitaiCommand.php" + }, + { + "required": true, + "file": "commands/BlacklistCommand.php" + }, + { + "required": true, + "file": "commands/BlCommand.php" + }, + { + "required": true, + "file": "commands/CallbackqueryCommand.php" + }, + { + "required": true, + "file": "commands/CheckAppealCommand.php" + }, + { + "required": true, + "file": "commands/CreateInviteCommand.php" + }, + { + "required": true, + "file": "commands/FinalVerdictCommand.php" + }, + { + "required": true, + "file": "commands/GenericmessageCommand.php" + }, + { + "required": true, + "file": "commands/HelpCommand.php" + }, + { + "required": true, + "file": "commands/LanguageCommand.php" + }, + { + "required": true, + "file": "commands/LCommand.php" + }, + { + "required": true, + "file": "commands/LogCommand.php" + }, + { + "required": true, + "file": "commands/MsgInfoCommand.php" + }, + { + "required": true, + "file": "commands/NewchatmembersCommand.php" + }, + { + "required": true, + "file": "commands/OpdemoteCommand.php" + }, + { + "required": true, + "file": "commands/OppromoteCommand.php" + }, + { + "required": true, + "file": "commands/PredictionsCommand.php" + }, + { + "required": true, + "file": "commands/PropCommand.php" + }, + { + "required": true, + "file": "commands/ResetCacheCommand.php" + }, + { + "required": true, + "file": "commands/ResetPredictionsCommand.php" + }, + { + "required": true, + "file": "commands/SettingsCommand.php" + }, + { + "required": true, + "file": "commands/StartCommand.php" + }, + { + "required": true, + "file": "commands/StatsCommand.php" + }, + { + "required": true, + "file": "commands/WhoisCommand.php" + }, + { + "required": true, + "file": "SpamProtectionBot.php" + } + ], + "files": [ + ".htaccess", + "commands/help_docs/blacklist_flags.html", + "commands/help_docs/false_detections.html", + "commands/help_docs/help.html", + "commands/help_docs/help.png", + "commands/help_docs/language.html", + "commands/help_docs/msginfo.html", + "commands/help_docs/settings.html", + "commands/help_docs/spam_protection.html", + "commands/help_docs/start.html", + "commands/help_docs/unwanted_users.html", + "commands/help_docs/whois.html", + "main", + "package.json", + "verdict_worker", + "worker" + ] +} \ No newline at end of file diff --git a/botsrc/worker b/botsrc/worker new file mode 100644 index 0000000..4befda5 --- /dev/null +++ b/botsrc/worker @@ -0,0 +1,252 @@ +setVerboseLogging($TelegramServiceConfiguration['VerboseLogging']); + + if(file_exists($current_directory . DIRECTORY_SEPARATOR . 'SpamProtectionBot.php')) + { + $telegram->addCommandsPaths([$current_directory . DIRECTORY_SEPARATOR . 'commands']); + } + elseif(file_exists(__DIR__ . DIRECTORY_SEPARATOR . 'SpamProtectionBot.php')) + { + $telegram->addCommandsPaths([__DIR__ . DIRECTORY_SEPARATOR . 'commands']); + } + else + { + print('Cannot locate commands path'); + exit(1); + } + + TelegramLog::initialize(); + } + catch (Longman\TelegramBot\Exception\TelegramException $e) + { + SynicalBot::getLogHandler()->logException($e, 'Worker'); + exit(255); + } + + try + { + $telegram->enableMySql(array( + 'host' => $DatabaseConfiguration['Host'], + 'port' => $DatabaseConfiguration['Port'], + 'user' => $DatabaseConfiguration['Username'], + 'password' => $DatabaseConfiguration['Password'], + 'database' => $DatabaseConfiguration['Database'], + )); + + $telegram->enableRedis( + $RedisConfiguration['Host'], + (int)$RedisConfiguration['Port'], + (int)$RedisConfiguration['Database'], + empty($RedisConfiguration['Username']) ? null : $RedisConfiguration['Username'], + empty($RedisConfiguration['Password']) ? null : $RedisConfiguration['Password'] + ); + } + catch(Exception $e) + { + SynicalBot::getLogHandler()->logException($e, 'Worker'); + exit(255); + } + + // Start the worker instance + SynicalBot::$DeepAnalytics = new DeepAnalytics(); + + // Create the database connections + SynicalBot::$TelegramClientManager = new TelegramClientManager(); + if(SynicalBot::$TelegramClientManager->getDatabase()->connect_error) + { + SynicalBot::getLogHandler()->log(EventType::ERROR, 'Failed to initialize TelegramClientManager, ' . SynicalBot::$TelegramClientManager->getDatabase()->connect_error, 'Worker'); + exit(255); + } + + SynicalBot::$SpamProtection = new SpamProtection(); + if(SynicalBot::$SpamProtection->getDatabase()->connect_error) + { + SynicalBot::getLogHandler()->log(EventType::ERROR, 'Failed to initialize SpamProtection, ' . SynicalBot::$SpamProtection->getDatabase()->connect_error, 'Worker'); + exit(255); + } + + SynicalBot::$CoffeeHouse = new CoffeeHouse(); + if(SynicalBot::$CoffeeHouse->getDatabase()->connect_error) + { + SynicalBot::getLogHandler()->log(EventType::ERROR, 'Failed to initialize CoffeeHouse, ' . SynicalBot::$CoffeeHouse->getDatabase()->connect_error, 'Worker'); + exit(255); + } + + try + { + $BackgroundWorker = new BackgroundWorker(); + $BackgroundWorker->getWorker()->addServer( + $BackgroundWorkerConfiguration['Host'], + (int)$BackgroundWorkerConfiguration['Port'] + ); + } + catch(Exception $e) + { + SynicalBot::getLogHandler()->logException($e, 'Worker'); + exit(255); + } + + // Define the function 'process_batch' to process a batch of Updates from Telegram in the background + $BackgroundWorker->getWorker()->addFunction($telegram->getBotUsername() . '_updates', function(GearmanJob $job) use ($telegram) + { + try + { + /** @noinspection PhpCastIsUnnecessaryInspection */ + SynicalBot::setLastWorkerActivity((int)time()); // Set the last activity timestamp + SynicalBot::processSleepCycle(); // Wake worker if it's sleeping + + $ServerResponse = new ServerResponse(json_decode($job->workload(), true), TELEGRAM_BOT_NAME); + if(is_null($ServerResponse->getResult()) == false) + { + $UpdateCount = count($ServerResponse->getResult()); + + if($UpdateCount > 0) + { + SynicalBot::getLogHandler()->log(EventType::INFO, 'Processing ' . $UpdateCount . ' update(s)', 'Worker'); + + /** @var Update $result */ + foreach ($ServerResponse->getResult() as $result) + { + try + { + if(TelegramLog::isVerboseLogging()) + SynicalBot::getLogHandler()->log(EventType::INFO, 'Processing update ID ' . $result->getUpdateId(), 'Worker'); + $telegram->processUpdate($result); + } + catch(Exception $e) + { + SynicalBot::getLogHandler()->logException($e, 'Worker'); + } + } + } + } + + } + catch(Exception $e) + { + SynicalBot::getLogHandler()->logException($e, 'Worker'); + } + + }); + + // Set the timeout to 5 seconds + $BackgroundWorker->getWorker()->getGearmanWorker()->setTimeout(500); + + while(true) + { + try + { + $BackgroundWorker->getWorker()->work(false); + + if($BackgroundWorker->getWorker()->getGearmanWorker()->returnCode() == GEARMAN_TIMEOUT) + { + SynicalBot::processSleepCycle(); + } + } + catch(Exception $e) + { + SynicalBot::getLogHandler()->logException($e, 'Worker'); + exit(255); + } + } + +