Skip to content

Commit

Permalink
added anti-siege
Browse files Browse the repository at this point in the history
  • Loading branch information
clragon committed Mar 29, 2024
1 parent 4fa02a9 commit 33e8614
Show file tree
Hide file tree
Showing 8 changed files with 452 additions and 82 deletions.
6 changes: 5 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:nyxx/nyxx.dart';
import 'package:swan/plugins/anti_siege/plugin.dart';
import 'package:swan/plugins/anti_spam/plugin.dart';
import 'package:swan/plugins/dartdoc/plugin.dart';
import 'package:swan/plugins/database/database.dart';
Expand All @@ -16,7 +17,9 @@ Future<void> main() async {
await Nyxx.connectGatewayWithOptions(
GatewayApiOptions(
token: env.discordToken,
intents: GatewayIntents.allUnprivileged | GatewayIntents.messageContent,
intents: GatewayIntents.allUnprivileged |
GatewayIntents.messageContent |
GatewayIntents.guildMembers,
),
GatewayClientOptions(
plugins: [
Expand All @@ -32,6 +35,7 @@ Future<void> main() async {
PasteFiles(),
DartdocSearch(),
AntiSpam(),
AntiSiege(),
],
),
);
Expand Down
150 changes: 150 additions & 0 deletions lib/plugins/anti_siege/plugin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:nyxx/nyxx.dart';
import 'package:swan/plugins/anti_spam/plugin.dart';
import 'package:swan/plugins/base/messages.dart';
import 'package:swan/plugins/base/plugin.dart';
import 'package:swan/plugins/database/database.dart';
import 'package:swan/plugins/database/plugin.dart';
import 'package:swan/plugins/env/plugin.dart';

class AntiSiege extends BotPlugin {
@override
String get name => 'AntiSiege';

@override
String? buildHelpText(NyxxGateway client) {
return 'When enabled, users who join and have a low account age will be kicked or banned.\n\n'
'To enable this plugin, set the appropriate channels: '
'`${client.env.commandPrefix}anti-siege #mod-logs`\n'
'To disable this plugin, use `${client.env.commandPrefix}anti-siege off`';
}

static const Duration _accountAgeThreshold = Duration(hours: 5);

@override
void afterConnect(NyxxGateway client) {
super.afterConnect(client);

client.onMessageCreate.listen((event) => _tryUpdateConfig(client, event));

client.onGuildMemberAdd.listen((event) async {
final guild = await event.guild.get();
final member = await event.member.get();

final config = await client.db.antiSiegeConfig(guild.id.value).first;
if (config == null) return;

final logChannel = await client.channels[Snowflake(config.logChannelId)]
.fetch() as TextChannel;

final user = await member.user?.get();
if (user == null) return;
if (user.isBot) return;

final accountAge = DateTime.now().difference(user.id.timestamp);

if (accountAge > _accountAgeThreshold) return;

Role? role = guild.roleList.firstWhereOrNull((e) => e.name == 'early');
role ??= await guild.roles.create(
RoleBuilder(name: 'early'),
);

await member.addRole(role.id);
await member.update(
MemberUpdateBuilder(
communicationDisabledUntil:
DateTime.now().add(const Duration(minutes: 60)).toUtc(),
),
);

logger.info(
'Muted ${user.id} for having an account younger than ${_accountAgeThreshold.inMinutes} minutes.',
);

await logChannel.sendMessage(MessageBuilder(
embeds: [
EmbedBuilder(
color: DiscordColor.parseHexString('#03d7fc'),
title: 'Anti-Siege',
description:
'Muted <@${user.id}> for having an account younger than ${_accountAgeThreshold.inMinutes} minutes.',
),
],
));
});
}

void _tryUpdateConfig(NyxxGateway client, MessageCreateEvent event) async {
RegExp command = RegExp(
r'^'
'${client.env.commandPrefix}'
r'anti-siege\s+'
r'(?<logs>(<#(\d+)>)|off)?\s*'
r'$',
);

RegExpMatch? match = command.firstMatch(event.message.content);

if (match == null) return;

final member = await event.member?.get();
if (member == null) return;

final channel = await event.message.channel.get();
if (channel is! GuildChannel) return;

final guild = await event.guild?.get();
if (guild == null) return;

final permissions = await computePermissions(guild, channel, member);
if (!permissions.canManageGuild) return;

try {
final String? mode = match.namedGroup('logs');
if (mode == null || mode.isEmpty) {
logger.info('No log channel provided for anti-siege mode.');
await (channel as TextChannel).sendMessage(MessageBuilder(
replyId: event.message.id,
content: 'Please provide a log channel.',
));
return;
}

if (mode == 'off') {
logger.info('Disabled anti-siege mode for ${guild.id}');
client.db.deleteAntiSiegeConfig(guild.id.value);
await (channel as TextChannel).sendMessage(MessageBuilder(
replyId: event.message.id,
content: 'Disabled anti-siege mode.',
));
return;
}

Snowflake logChannelId =
Snowflake.parse(mode.substring(2, mode.length - 1));

client.db.setAntiSiegeConfig(
AntiSiegeConfigsCompanion.insert(
guildId: Value(guild.id.value),
logChannelId: logChannelId.value,
),
);

logger.info(
'Enabled anti-siege mode for ${guild.id}. log in $logChannelId');
await (channel as TextChannel).sendMessage(MessageBuilder(
replyId: event.message.id,
content:
'Enabled anti-siege mode. Logs will be sent to <#$logChannelId>',
));
} on FormatException {
await (channel as TextChannel).sendMessage(MessageBuilder(
replyId: event.message.id,
content: "Couldn't parse channel IDs.",
));
logger.warning('Failed to parse anti-siege config from:\n${event.link}');
}
}
}
6 changes: 3 additions & 3 deletions lib/plugins/dartdoc/dartdoc_entry.freezed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ part of 'dartdoc_entry.dart';
T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');

DartdocEntry _$DartdocEntryFromJson(Map<String, dynamic> json) {
return _DartdocEntry.fromJson(json);
Expand Down Expand Up @@ -221,7 +221,7 @@ class _$DartdocEntryImpl implements _DartdocEntry {
}

@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$DartdocEntryImpl &&
Expand Down Expand Up @@ -409,7 +409,7 @@ class _$DartdocEnclosedByImpl implements _DartdocEnclosedBy {
}

@override
bool operator ==(dynamic other) {
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$DartdocEnclosedByImpl &&
Expand Down
34 changes: 32 additions & 2 deletions lib/plugins/database/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,44 @@ import 'package:drift/native.dart';

part 'database.g.dart';

@DriftDatabase(tables: [AntiSpamConfigs])
@DriftDatabase(tables: [AntiSpamConfigs, AntiSiegeConfigs])
class SwanDatabase extends _$SwanDatabase {
SwanDatabase()
: super(
NativeDatabase.createInBackground(File('config/database.sqlite3')),
);

@override
int get schemaVersion => 1;
int get schemaVersion => 2;

@override
MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (migrator, from, to) async {
if (from == 1) {
await migrator.createTable(antiSiegeConfigs);
}
},
);

Stream<AntiSpamConfig?> antiSpamConfig(int id) =>
(select(antiSpamConfigs)..where((tbl) => tbl.guildId.equals(id)))
.watchSingleOrNull();

Future<void> setAntiSpamConfig(AntiSpamConfigsCompanion config) =>
into(antiSpamConfigs).insertOnConflictUpdate(config);

Future<void> deleteAntiSpamConfig(int id) =>
(delete(antiSpamConfigs)..where((tbl) => tbl.guildId.equals(id))).go();

Stream<AntiSiegeConfig?> antiSiegeConfig(int id) =>
(select(antiSiegeConfigs)..where((tbl) => tbl.guildId.equals(id)))
.watchSingleOrNull();

Future<void> setAntiSiegeConfig(AntiSiegeConfigsCompanion config) =>
into(antiSiegeConfigs).insertOnConflictUpdate(config);

Future<void> deleteAntiSiegeConfig(int id) =>
(delete(antiSiegeConfigs)..where((tbl) => tbl.guildId.equals(id))).go();
}

class AntiSpamConfigs extends Table {
Expand All @@ -31,3 +53,11 @@ class AntiSpamConfigs extends Table {
IntColumn get warningChannelId => integer()();
IntColumn get rulesChannelId => integer()();
}

class AntiSiegeConfigs extends Table {
@override
Set<Column> get primaryKey => {guildId};

IntColumn get guildId => integer()();
IntColumn get logChannelId => integer()();
}
Loading

0 comments on commit 33e8614

Please sign in to comment.