Skip to content

Commit

Permalink
Feat: Complex rules (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
RikudouSage committed Apr 11, 2024
1 parent 5f09649 commit d614dad
Show file tree
Hide file tree
Showing 13 changed files with 529 additions and 4 deletions.
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -26,6 +26,7 @@
"symfony/apache-pack": "^1.0",
"symfony/console": "7.0.*",
"symfony/dotenv": "7.0.*",
"symfony/expression-language": "7.0.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "7.0.*",
"symfony/http-client": "7.0.*",
Expand Down
65 changes: 64 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions config/packages/messenger.yaml
Expand Up @@ -25,6 +25,7 @@ framework:
App\Message\RemovePostMessage: async
App\Message\RestoreCommentMessage: async
App\Message\RestorePostMessage: async
App\Message\RunExpressionAsyncMessage: async
App\Message\SendNotificationAsyncMessage: async
App\Message\UnbanUserMessage: async
# Route your messages to the transports
Expand Down
5 changes: 5 additions & 0 deletions config/services.yaml
Expand Up @@ -87,3 +87,8 @@ services:
App\MessageHandler\UnbanUserHandler:
arguments:
$removalLogValidity: '@app.log_validity'

App\Service\ExpressionLanguage:
calls:
- registerProvider: ['@App\Service\ExpressionLanguageFunctions']
Symfony\Component\ExpressionLanguage\ExpressionLanguage: '@App\Service\ExpressionLanguage'
31 changes: 31 additions & 0 deletions migrations/Version20240411103740.php
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240411103740 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE complex_rules (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, type VARCHAR(180) NOT NULL, rule CLOB NOT NULL, actions CLOB NOT NULL, run_configuration VARCHAR(180) DEFAULT \'not_aborted\' NOT NULL, enabled BOOLEAN DEFAULT 1 NOT NULL)');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE complex_rules');
}
}
43 changes: 43 additions & 0 deletions src/Automod/Enum/ComplexRuleType.php
@@ -0,0 +1,43 @@
<?php

namespace App\Automod\Enum;

use App\Dto\Model\EnrichedInstanceData;
use App\Dto\Model\LocalUser;
use InvalidArgumentException;
use Rikudou\LemmyApi\Response\Model\Person;
use Rikudou\LemmyApi\Response\View\CommentReportView;
use Rikudou\LemmyApi\Response\View\CommentView;
use Rikudou\LemmyApi\Response\View\PostReportView;
use Rikudou\LemmyApi\Response\View\PostView;
use Rikudou\LemmyApi\Response\View\PrivateMessageReportView;
use Rikudou\LemmyApi\Response\View\RegistrationApplicationView;

enum ComplexRuleType: string
{
case Post = 'post';
case Comment = 'comment';
case Person = 'person';
case CommentReport = 'comment_report';
case PostReport = 'post_report';
case PrivateMessageReport = 'private_message_report';
case RegistrationApplication = 'registration_application';
case LocalUser = 'local_user';
case Instance = 'instance';

public static function fromClass(string $class): self
{
return match ($class) {
PostView::class => self::Post,
CommentView::class => self::Comment,
Person::class => self::Person,
CommentReportView::class => self::CommentReport,
PostReportView::class => self::PostReport,
PrivateMessageReportView::class => self::PrivateMessageReport,
RegistrationApplicationView::class => self::RegistrationApplication,
LocalUser::class => self::LocalUser,
EnrichedInstanceData::class => self::Instance,
default => throw new InvalidArgumentException("Unsupported class: {$class}"),
};
}
}
100 changes: 100 additions & 0 deletions src/Automod/ModAction/ComplexRuleAction.php
@@ -0,0 +1,100 @@
<?php

namespace App\Automod\ModAction;

use App\Automod\Enum\ComplexRuleType;
use App\Context\Context;
use App\Dto\Model\EnrichedInstanceData;
use App\Dto\Model\LocalUser;
use App\Enum\FurtherAction;
use App\Enum\RunConfiguration;
use App\Repository\ComplexRuleRepository;
use App\Service\ExpressionLanguage;
use LogicException;
use Rikudou\LemmyApi\LemmyApi;
use Rikudou\LemmyApi\Response\Model\Person;
use Rikudou\LemmyApi\Response\View\CommentReportView;
use Rikudou\LemmyApi\Response\View\CommentView;
use Rikudou\LemmyApi\Response\View\PostReportView;
use Rikudou\LemmyApi\Response\View\PostView;
use Rikudou\LemmyApi\Response\View\PrivateMessageReportView;
use Rikudou\LemmyApi\Response\View\RegistrationApplicationView;
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\SyntaxError;

/**
* @implements ModAction<PostView|CommentView|Person|CommentReportView|PostReportView|PrivateMessageReportView|RegistrationApplicationView|LocalUser|EnrichedInstanceData>
*/
final readonly class ComplexRuleAction implements ModAction
{
public function __construct(
private ComplexRuleRepository $ruleRepository,
private ExpressionLanguage $expressionLanguage,
private LemmyApi $api,
) {
}

public function shouldRun(object $object): bool
{
$type = ComplexRuleType::fromClass(get_class($object));
$rules = $this->ruleRepository->findBy([
'enabled' => true,
'type' => $type,
]);

if (!count($rules)) {
return false;
}

foreach ($rules as $rule) {
$expression = $rule->getRule();
if ($this->expressionLanguage->evaluate($expression, ['object' => $object])) {
return true;
}
}

return false;
}

public function takeAction(object $object, Context $context = new Context()): FurtherAction
{
$this->expressionLanguage->addFunction(new ExpressionFunction(
'notify',
fn () => throw new LogicException('Uncompilable function'),
function (array $expressionContext, string $message) use ($context): bool {
$context->addMessage($message);
return true;
}
));

$type = ComplexRuleType::fromClass(get_class($object));

$canContinue = true;
$rules = $this->ruleRepository->findBy(['enabled' => true, 'type' => $type]);
foreach ($rules as $rule) {
if (!$canContinue && $rule->getRunConfiguration() !== RunConfiguration::Always) {
continue;
}
$expression = $rule->getActions();
try {
$result = (bool) $this->expressionLanguage->evaluate($expression, [
'object' => $object,
'lemmy' => $this->api,
]);
} catch (SyntaxError $e) {
$context->addMessage("There's a syntax error in complex rule with id '{$rule->getId()}': {$e->getMessage()}");
continue;
}
if (!$result) {
$canContinue = false;
}
}

return $canContinue ? FurtherAction::CanContinue : FurtherAction::ShouldAbort;
}

public function getRunConfiguration(): RunConfiguration
{
return RunConfiguration::Always;
}
}
99 changes: 99 additions & 0 deletions src/Entity/ComplexRule.php
@@ -0,0 +1,99 @@
<?php

namespace App\Entity;

use App\Automod\Enum\ComplexRuleType;
use App\Enum\RunConfiguration;
use App\Repository\ComplexRuleRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Table(name: 'complex_rules')]
#[ORM\Entity(repositoryClass: ComplexRuleRepository::class)]
class ComplexRule
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\Column(length: 180, enumType: ComplexRuleType::class)]
private ?ComplexRuleType $type = null;

#[ORM\Column(type: Types::TEXT)]
private ?string $rule = null;

#[ORM\Column(type: Types::TEXT)]
private ?string $actions = null;

#[ORM\Column(length: 180, enumType: RunConfiguration::class, options: ['default' => RunConfiguration::WhenNotAborted->value])]
private RunConfiguration $runConfiguration = RunConfiguration::WhenNotAborted;

#[ORM\Column(options: ['default' => true])]
private bool $enabled = true;

public function getId(): ?int
{
return $this->id;
}

public function getType(): ?ComplexRuleType
{
return $this->type;
}

public function setType(ComplexRuleType $type): static
{
$this->type = $type;

return $this;
}

public function getRule(): ?string
{
return $this->rule;
}

public function setRule(string $rule): static
{
$this->rule = $rule;

return $this;
}

public function getActions(): ?string
{
return $this->actions;
}

public function setActions(string $actions): static
{
$this->actions = $actions;

return $this;
}

public function getRunConfiguration(): RunConfiguration
{
return $this->runConfiguration;
}

public function setRunConfiguration(RunConfiguration $runConfiguration): static
{
$this->runConfiguration = $runConfiguration;

return $this;
}

public function isEnabled(): bool
{
return $this->enabled;
}

public function setEnabled(bool $enabled): static
{
$this->enabled = $enabled;

return $this;
}
}
6 changes: 3 additions & 3 deletions src/Enum/RunConfiguration.php
Expand Up @@ -2,8 +2,8 @@

namespace App\Enum;

enum RunConfiguration
enum RunConfiguration: string
{
case Always;
case WhenNotAborted;
case Always = 'always';
case WhenNotAborted = 'not_aborted';
}

0 comments on commit d614dad

Please sign in to comment.