Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
<?php
namespace MauticPlugin\MauticSocialBundle\Command;
use MauticPlugin\MauticSocialBundle\Entity\MonitoringRepository;
use MauticPlugin\MauticSocialBundle\Model\MonitoringModel;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'mautic:social:monitoring',
description: 'Looks at the records of monitors and iterates through them.'
)]
class MauticSocialMonitoringCommand extends Command
{
public function __construct(
private MonitoringModel $monitoringModel,
) {
parent::__construct();
}
protected function configure()
{
$this
->addOption('mid', 'i', InputOption::VALUE_OPTIONAL, 'The id of a specific monitor record to process')
->addOption(
'batch-size',
null,
InputOption::VALUE_REQUIRED,
'The maximum number of iterations the cron runs per cycle. This value gets distributed by the number of monitor records published'
)
->addOption('query-count', null, InputOption::VALUE_OPTIONAL, 'The number of records to search for per iteration. Default is 100.', 100);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// get the mid from the cli
$batchSize = $input->getOption('batch-size');
// monitor record
$monitorId = $input->getOption('mid');
$monitorList = $this->getMonitors($monitorId);
// no mid found, quit now
if (!$monitorList->count()) {
$output->writeln('No published monitors found. Make sure the id you supplied is published');
return Command::SUCCESS;
}
if (!is_numeric($batchSize)) {
$output->writeln('batch-size is not number.');
return self::FAILURE;
}
// max iterations
$maxPerIterations = ceil((int) $batchSize / count($monitorList));
foreach ($monitorList as $monitor) {
$output->writeln('Executing Monitor Item '.$monitor->getId());
$resultCode = $this->processMonitorListItem($monitor, $maxPerIterations, $input, $output);
$output->writeln('Result Code: '.$resultCode);
}
return Command::SUCCESS;
}
/**
* @return \Doctrine\ORM\Tools\Pagination\Paginator
*/
protected function getMonitors($id = null)
{
$filter = [
'start' => 0,
'limit' => 100,
];
/** @var MonitoringRepository $repository */
$repository = $this->monitoringModel->getRepository();
if (null !== $id) {
$filter['filter'] = [
'force' => [
[
'column' => $repository->getTableAlias().'.id',
'expr' => 'eq',
'value' => (int) $id,
],
],
];
}
return $repository->getPublishedEntities($filter);
}
/**
* @return bool|int
*
* @throws \Exception
*/
protected function processMonitorListItem($listItem, float $maxPerIterations, InputInterface $input, OutputInterface $output)
{
// @todo set this up to use the command type per-monitor record.
$networkType = $listItem->getNetworkType();
$commandName = '';
// hashtag command
if ('twitter_hashtag' == $networkType) {
$commandName = 'social:monitor:twitter:hashtags';
}
// mention command
if ('twitter_handle' == $networkType) {
$commandName = 'social:monitor:twitter:mentions';
}
if ('' == $commandName) {
$output->writeln('Matching command not found.');
return 1;
}
// monitor hash command
$command = $this->getApplication()->find($commandName);
// create command options
$cliArgs = [
'command' => $commandName,
'--mid' => $listItem->getId(),
'--max-runs' => $maxPerIterations,
'--query-count' => $input->getOption('query-count'),
];
// execute the command
$returnCode = $command->run(new ArrayInput($cliArgs), $output);
return $returnCode;
}
}

View File

@@ -0,0 +1,300 @@
<?php
namespace MauticPlugin\MauticSocialBundle\Command;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
use MauticPlugin\MauticSocialBundle\Event\SocialMonitorEvent;
use MauticPlugin\MauticSocialBundle\Helper\TwitterCommandHelper;
use MauticPlugin\MauticSocialBundle\Integration\TwitterIntegration;
use MauticPlugin\MauticSocialBundle\SocialEvents;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
abstract class MonitorTwitterBaseCommand extends Command
{
/**
* @var TwitterIntegration
*/
protected $twitter;
/**
* @var InputInterface
*/
protected $input;
/**
* @var OutputInterface
*/
protected $output;
/**
* @var int
*/
protected $maxRuns = 5;
/**
* @var int
*/
protected $runCount = 0;
/**
* @var int
*/
protected $queryCount = 100;
public function __construct(
protected EventDispatcherInterface $dispatcher,
protected Translator $translator,
protected IntegrationHelper $integrationHelper,
private TwitterCommandHelper $twitterCommandHelper,
CoreParametersHelper $coreParametersHelper,
) {
$this->translator->setLocale($coreParametersHelper->get('locale', 'en_US'));
parent::__construct();
}
/**
* Command configuration. Set the name, description, and options here.
*/
protected function configure()
{
$this
->addOption(
'mid',
'i',
InputOption::VALUE_REQUIRED,
'The id of the monitor record'
)
->addOption(
'max-runs',
null,
InputOption::VALUE_REQUIRED,
'The maximum number of recursive iterations permitted',
5
)
->addOption(
'query-count',
null,
InputOption::VALUE_OPTIONAL,
'The number of records to search for per iteration.',
100
)
->addOption(
'show-posts',
null,
InputOption::VALUE_NONE,
'Use this option to display the posts retrieved'
)
->addOption(
'show-stats',
null,
InputOption::VALUE_NONE,
'Use this option to display the stats of the tweets fetched'
);
}
/**
* Used in various areas to set name of the network being searched.
*
* @return string twitter|facebook etc..
*/
abstract public function getNetworkName();
/**
* Search for tweets by creating your own search criteria.
*
* @param Monitoring $monitor
*
* @return array The results of makeRequest
*/
abstract protected function getTweets($monitor);
/**
* Main execution method. Gets the integration settings, processes the search criteria.
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
$this->maxRuns = $this->input->getOption('max-runs');
$this->queryCount = $this->input->getOption('query-count');
$twitterIntegration = $this->integrationHelper->getIntegrationObject('Twitter');
if (false === $twitterIntegration || false === $twitterIntegration->getIntegrationSettings()->getIsPublished()) {
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.not.published'));
return Command::FAILURE;
}
\assert($twitterIntegration instanceof TwitterIntegration);
$this->twitter = $twitterIntegration;
if (!$this->twitter->isAuthorized()) {
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.not.configured'));
return Command::FAILURE;
}
// get the mid from the cli
$mid = (int) $input->getOption('mid');
if (!$mid) {
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.mid.empty'));
return Command::FAILURE;
}
$this->twitterCommandHelper->setOutput($output);
$monitor = $this->twitterCommandHelper->getMonitor($mid);
if (!$monitor || !$monitor->getId()) {
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.monitor.does.not.exist', ['%id%' => $mid]));
return Command::FAILURE;
}
// process the monitor
$this->processMonitor($monitor);
$this->dispatcher->dispatch(
new SocialMonitorEvent($this->getNetworkName(), $monitor, $this->twitterCommandHelper->getManipulatedLeads(), $this->twitterCommandHelper->getNewLeadsCount(), $this->twitterCommandHelper->getUpdatedLeadsCount()),
SocialEvents::MONITOR_POST_PROCESS
);
return Command::SUCCESS;
}
/**
* Process the monitor record.
*
* @Note: Keeping this method here instead of in the twitterCommandHelper
* so that the hashtag and mention commands can easily extend it.
*
* @param Monitoring $monitor
*
* @return bool
*/
protected function processMonitor($monitor)
{
$results = $this->getTweets($monitor);
if (false === $results || !isset($results['statuses'])) {
$this->output->writeln('No statuses found');
if (!empty($results['errors'])) {
foreach ($results['errors'] as $error) {
$this->output->writeln($error['code'].': '.$error['message']);
}
}
return 0;
}
if (count($results['statuses'])) {
$this->twitterCommandHelper->createLeadsFromStatuses($results['statuses'], $monitor);
} else {
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.no.new.tweets'));
}
$this->twitterCommandHelper->setMonitorStats($monitor, $results['search_metadata']);
$this->printInformation($monitor, $results);
// get stats after being updated
$stats = $monitor->getStats();
++$this->runCount;
// if we have stats and a next results request, process it here
// @todo add a check for max iterations
if (is_array($stats) && array_key_exists('max_id_str', $stats)
&& ($this->runCount < $this->maxRuns)
&& count($results['statuses'])
) {
// recursive
$this->processMonitor($monitor);
}
return 0;
}
/**
* Prints all the returned tweets.
*
* @param array $statuses
*/
protected function printTweets($statuses)
{
if (!$this->input->getOption('show-posts') && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERY_VERBOSE) {
return;
}
foreach ($statuses as $status) {
$this->output->writeln('-- tweet -- ');
$this->output->writeln('ID: '.$status['id']);
$this->output->writeln('Message: '.$status['text']);
$this->output->writeln('Handle: '.$status['user']['screen_name']);
$this->output->writeln('Name: '.$status['user']['name']);
$this->output->writeln('Location: '.$status['user']['location']);
$this->output->writeln('Profile Img: '.$status['user']['profile_image_url']);
$this->output->writeln('Profile Description: '.$status['user']['description']);
$this->output->writeln('// tweet // ');
}
}
/**
* Prints the search query metadata from twitter.
* Only shows stats if explicitly requested or if we're in verbose mode.
*
* @param array $metadata
*/
protected function printQueryMetadata($metadata)
{
if (!$this->input->getOption('show-stats') && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
return;
}
$this->output->writeln('-- search meta -- ');
$this->output->writeln('max_id_str: '.$metadata['max_id_str']);
$this->output->writeln('since_id_str: '.$metadata['since_id_str']);
$this->output->writeln('Page Count: '.$metadata['count']);
$this->output->writeln('query: '.$metadata['query']);
if (array_key_exists('next_results', $metadata)) {
$this->output->writeln('next results: '.$metadata['next_results']);
}
$this->output->writeln('// search meta // ');
}
/**
* Prints a summary of the search query.
* Only shows stats if explicitly requested or if we're in verbose mode.
*
* @param Monitoring $monitor
* @param array $results
*/
protected function printInformation($monitor, $results)
{
if (!$this->input->getOption('show-stats') && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
return;
}
$this->output->writeln('------------------------');
$this->output->writeln($monitor->getTitle());
$this->output->writeln('Published '.$monitor->isPublished());
$this->output->writeln($monitor->getNetworkType());
$this->output->writeln('New Leads '.$this->twitterCommandHelper->getNewLeadsCount());
$this->output->writeln('Updated Leads '.$this->twitterCommandHelper->getUpdatedLeadsCount());
$this->printQueryMetadata($results['search_metadata']);
$this->printTweets($results['statuses']);
$this->output->writeln('------------------------');
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace MauticPlugin\MauticSocialBundle\Command;
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(
name: 'social:monitor:twitter:hashtags',
description: 'Looks at our monitoring records and finds hashtags'
)]
class MonitorTwitterHashtagsCommand extends MonitorTwitterBaseCommand
{
/**
* Search for tweets by hashtag.
*
* @param Monitoring $monitor
*
* @return bool|array False if missing the hashtag, otherwise the array response from Twitter
*/
protected function getTweets($monitor)
{
$params = $monitor->getProperties();
$stats = $monitor->getStats();
if (!array_key_exists('hashtag', $params)) {
$this->output->writeln('No hashtag was found!');
return false;
}
$searchUrl = $this->twitter->getApiUrl('search/tweets');
$requestQuery = [
'q' => '#'.$params['hashtag'],
'count' => $this->queryCount,
];
// if we have a max id string use it here
if (is_array($stats) && array_key_exists('max_id_str', $stats) && $stats['max_id_str']) {
$requestQuery['since_id'] = $stats['max_id_str'];
}
return $this->twitter->makeRequest($searchUrl, $requestQuery);
}
public function getNetworkName(): string
{
return 'twitter';
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace MauticPlugin\MauticSocialBundle\Command;
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(
name: 'social:monitor:twitter:mentions',
description: 'Searches for mentioned tweets'
)]
class MonitorTwitterMentionsCommand extends MonitorTwitterBaseCommand
{
/**
* Search for tweets by mention.
*
* @param Monitoring $monitor
*
* @return bool|array False if missing the twitter handle, otherwise the array response from Twitter
*/
protected function getTweets($monitor)
{
$params = $monitor->getProperties();
$stats = $monitor->getStats();
if (!array_key_exists('handle', $params)) {
$this->output->writeln('No twitter handle was found!');
return false;
}
$mentionsUrl = $this->twitter->getApiUrl('search/tweets');
$requestQuery = [
'q' => '@'.$params['handle'],
'count' => $this->queryCount,
];
// if we have a max id string use it here
if (is_array($stats) && array_key_exists('max_id_str', $stats) && $stats['max_id_str']) {
$requestQuery['since_id'] = $stats['max_id_str'];
}
return $this->twitter->makeRequest($mentionsUrl, $requestQuery);
}
public function getNetworkName(): string
{
return 'twitter';
}
}