modlog import

This commit is contained in:
Erik 2022-06-13 12:41:56 +03:00
parent 2ea7d097f4
commit 4f75472c37
Signed by: Navy.gif
GPG Key ID: 811EC0CD80E7E5FB
6 changed files with 258 additions and 31 deletions

View File

@ -134,6 +134,7 @@ class GuildWrapper {
return this._settings; return this._settings;
} }
// TODO Move settings to a settings object insteadd of being in a mess with everything else in the data object
async updateSettings(settings) { async updateSettings(settings) {
if (!this._settings) await this.settings(); if (!this._settings) await this.settings();
try { try {

View File

@ -1,3 +1,4 @@
const { InfractionMigrator, Util } = require("../../../../utilities");
const SettingsMigrator = require("../../../../utilities/SettingsMigrator"); const SettingsMigrator = require("../../../../utilities/SettingsMigrator");
const { SlashCommand } = require("../../../interfaces"); const { SlashCommand } = require("../../../interfaces");
@ -6,6 +7,7 @@ const dbs = {
'3': 'newgbot' '3': 'newgbot'
}; };
const { MONGODB_HOST, MONGODB_USERNAME, MONGODB_PASSWORD, MONGODB_V2_HOST } = process.env;
class ImportCommand extends SlashCommand { class ImportCommand extends SlashCommand {
constructor(client) { constructor(client) {
@ -17,7 +19,7 @@ class ImportCommand extends SlashCommand {
guildOnly: true, guildOnly: true,
memberPermissions: ['ADMINISTRATOR'], memberPermissions: ['ADMINISTRATOR'],
options: [{ options: [{
name: ['settings', 'modlogs'], name: ['settings'],
type: 'SUB_COMMAND', type: 'SUB_COMMAND',
options: [{ options: [{
name: 'version', name: 'version',
@ -27,24 +29,49 @@ class ImportCommand extends SlashCommand {
{ name: 'v3', value: '3' } { name: 'v3', value: '3' }
] ]
}] }]
}, {
name: 'modlogs',
type: 'SUB_COMMAND',
options: [{
name: 'version',
description: 'Which version do you want to import from',
choices: [
{ name: 'v2', value: '2' },
{ name: 'v3', value: '3' }
]
}, {
name: 'overwrite',
description: 'Whether any existing logs should be overwritten by the imports. By default new ones are bumped',
type: 'BOOLEAN'
}]
}] }]
}); });
} }
async execute(invoker, { version }) { async execute(invoker, { version, overwrite }) {
const { subcommand, guild } = invoker; const { subcommand, guild } = invoker;
const settings = await guild.settings(); let settings = await guild.settings();
if (settings._imported); if (settings._imported[subcommand.name]) return { emoji: 'failure', index: 'COMMAND_IMPORT_IMPORTED', params: { thing: Util.capitalise(subcommand.name) } };
version = version?.value || '3'; version = version?.value || '3';
if(subcommand.name === 'modlogs') return { content: 'Not supported yet' }; await invoker.reply({ index: 'COMMAND_IMPORT_WORKING', emoji: 'loading' });
const result = await this[subcommand.name](guild, version, overwrite.value);
// TODO split into settings and modlogs // This looks ridiculous but it's to keep track of what's been imported
settings = guild._settings;
if (!settings.imported) settings.imported = {};
settings.imported[subcommand.name] = true;
const { MONGODB_HOST, MONGODB_USERNAME, MONGODB_PASSWORD } = process.env; result._edit = true;
const migrator = new SettingsMigrator(this.client, guild, { return result;
host: MONGODB_HOST,
}
async modlogs(guild, version, overwrite = false) {
const migrator = new InfractionMigrator(this.client, guild, {
host: version === '2' ? MONGODB_V2_HOST : MONGODB_HOST,
username: MONGODB_USERNAME, username: MONGODB_USERNAME,
password: MONGODB_PASSWORD, password: MONGODB_PASSWORD,
database: dbs[version], // Default to v3 database: dbs[version], // Default to v3
@ -54,7 +81,48 @@ class ImportCommand extends SlashCommand {
await migrator.connect(); await migrator.connect();
let imported = null; let imported = null;
try { try {
imported = await migrator.migrate(); imported = await migrator.import();
imported.sort((a, b) => a.case - b.case);
} catch (err) {
this.client.logger.error(err.stack);
return { index: 'COMMAND_IMPORT_ERROR', params: { message: err.message }, emoji: 'failure' };
}
await migrator.end();
console.log(imported);
if (overwrite) { // Overwrite any existing logs with the imported ones
await this.client.mongodb.infractions.deleteMany({ guild: guild.id });
await this.client.mongodb.infractions.insertMany(imported);
} else { // Bump existing logs by the highest case id from imported logs
const highestOldId = imported[imported.length - 1];
const existingLogs = await this.client.mongodb.infractions.find({ guild: guild.id });
for (const log of existingLogs) {
log.case += highestOldId;
await this.client.mongodb.infractions.updateOne({ _id: log._id }, { case: log.case });
}
guild._settings.caseId += highestOldId;
await guild.updateSettings({ caseId: guild._settings.caseId });
}
return { content: 'blah' };
}
async settings(guild, version) {
// const { MONGODB_HOST, MONGODB_USERNAME, MONGODB_PASSWORD } = process.env;
const migrator = new SettingsMigrator(this.client, guild, {
host: version === '2' ? MONGODB_V2_HOST : MONGODB_HOST,
username: MONGODB_USERNAME,
password: MONGODB_PASSWORD,
database: dbs[version], // Default to v3
version
});
await migrator.connect();
let imported = null;
try {
imported = await migrator.import();
} catch (err) { } catch (err) {
this.client.logger.error(err.stack); this.client.logger.error(err.stack);
return { index: 'COMMAND_IMPORT_ERROR', params: { message: err.message }, emoji: 'failure' }; return { index: 'COMMAND_IMPORT_ERROR', params: { message: err.message }, emoji: 'failure' };

View File

@ -1,3 +1,5 @@
const { ObjectId } = require("mongodb");
class MongodbTable { class MongodbTable {
constructor(client, provider, opts = {}) { constructor(client, provider, opts = {}) {
@ -11,11 +13,11 @@ class MongodbTable {
//Data Search //Data Search
async find(query, opts = {}, { sort, skip, limit } = {}) { //opts: { projection: ... } find(query, opts = {}, { sort, skip, limit } = {}) { //opts: { projection: ... }
query = this._handleData(query); query = this._handleData(query);
if (!this.provider._initialized) return Promise.reject(new Error('MongoDB is not connected.')); if (!this.provider._initialized) return Promise.reject(new Error('MongoDB is not connected.'));
const cursor = await this.collection.find(query, opts); const cursor = this.collection.find(query, opts);
if (sort) cursor.sort(sort); if (sort) cursor.sort(sort);
if (skip) cursor.skip(skip); if (skip) cursor.skip(skip);
if (limit) cursor.limit(limit); if (limit) cursor.limit(limit);
@ -70,7 +72,10 @@ class MongodbTable {
}); });
} }
//NOTE: insertMany? insertMany(documents, options = {}) {
if (!this.provider._initialized) return Promise.reject(new Error('MongoDB is not connected.'));
return this.collection.insertMany(documents, options);
}
deleteOne(query) { deleteOne(query) {
query = this._handleData(query); query = this._handleData(query);
@ -164,17 +169,15 @@ class MongodbTable {
}); });
} }
//Lazy Function _handleData(data) { //Convert data._id to Mongo ObjectIds
// Shouldn't be necessary anymore -- cleanup later if (data._id) {
_handleData(data) { //Convert data._id to Mongo ObjectIds (gets converted to plaintext through shard communication) if (typeof data._id === 'string') data._id = ObjectId(data._id);
// if (data._id) { else if (typeof data._id === 'object') data._id = {
// if (typeof data._id === 'string') data._id = ObjectId(data._id); $in: Object.values(data._id)[0].map((id) => {
// else if (typeof data._id === 'object') data._id = { return ObjectId(id);
// $in: Object.values(data._id)[0].map((id) => { })
// return ObjectId(id); };
// }) }
// };
// }
return data; return data;
} }

View File

@ -0,0 +1,137 @@
const MongoWrapper = require('./SimpleMongoWrapper');
const Logger = require('./Logger');
const modtypes = {
'hardban': 'BAN',
'ban': 'BAN',
'tempban': 'BAN',
'softban': 'SOFTBAN',
'kick': 'KICK',
'note': 'NOTE',
'mute': 'MUTE',
'unmute': 'UNMUTE',
'warn': 'WARN',
};
class InfractionMigrator {
constructor(client, guild, dbConfig) {
this._config = dbConfig;
this.client = client;
this.mongo = new MongoWrapper(dbConfig);
this.guild = guild.id || guild;
this.logger = new Logger(this);
}
async connect() {
this.logger.info(`Connecting to mongo: ${this._config.database}`);
await this.mongo.init();
this.logger.info(`Mongo connected`);
}
async end() {
this.logger.info(`Disconnecting ${this._config.database}`);
await this.mongo.close();
this.logger.info(`Disconnected`);
}
async import() {
this.logger.debug(`Attempting modlogs migration for ${this.guild}`);
const { version } = this._config;
let idIdentifier = null,
collection = null;
if (version === '2') {
collection = 'discord_infractions';
idIdentifier = 'guild';
} else {
collection = 'infractions';
idIdentifier = 'guild';
}
const filter = { [idIdentifier]: this.guild };
const infractions = await this.mongo.find(collection, filter);
if (!infractions.length) return Promise.reject(new Error('No infractions found'));
const translated = this[version](infractions);
return translated;
}
'2'(infractions) {
const result = [];
for (const infraction of infractions) {
const base = this.infractionBase;
base.id = `${infraction.guild}:${infraction.id}`;
base.case = infraction.id;
base.reason = infraction.reason;
base.type = modtypes[infraction.type];
base.duration = infraction.modlength * 1000;
base.timestamp = infraction.timestamp * 1000;
base.guild = infraction.guild;
base.executor = infraction.staff;
base.target = infraction.user;
base.resolved = infraction.resolved;
base.points = infraction.modpoints;
base.expiration = infraction.expires * 1000;
base.dmLogMessage = infraction.dm_message_id;
base.modLogMessage = infraction.message_id;
result.push(base);
}
return result;
}
'3'(infractions) {
const result = [];
for (const infraction of infractions) {
// Ensure the infractions have all properties, shouldn't be any inconsistencies between 3 and 3.slash but just in case
result.push({ ...this.infractionBase, ...infraction });
}
return result;
}
get infractionBase() {
return {
id: null,
guild: null,
channel: null,
channelName: null,
message: null,
executor: null,
executorTag: null,
target: null,
targetTag: null,
targetType: null,
type: null,
case: null,
timestamp: null,
duration: null,
callback: null,
reason: null,
data: null,
flags: null,
points: null,
expiration: null,
modLogMessage: null,
dmLogMessage: null,
modLogChannel: null,
resolved: null,
changes: null,
_callbacked: true
};
}
}
module.exports = InfractionMigrator;

View File

@ -46,7 +46,7 @@ class SettingsMigrator {
this.logger.info(`Disconnected`); this.logger.info(`Disconnected`);
} }
async migrate() { async import() {
this.logger.debug(`Attempting settings migration for ${this.guild}`); this.logger.debug(`Attempting settings migration for ${this.guild}`);
@ -65,10 +65,10 @@ class SettingsMigrator {
const settings = await this.mongo.findOne(collection, filter); const settings = await this.mongo.findOne(collection, filter);
if (!settings) return Promise.reject(new Error('No old settings found')); if (!settings) return Promise.reject(new Error('No old settings found'));
const { _version } = settings; // const { _version } = settings;
if (!_version) return Promise.reject(new Error('Unable to determine configuration version')); // if (!_version) return Promise.reject(new Error('Unable to determine configuration version'));
const translated = this[_version](settings); const translated = this[version](settings);
let webhook = null, let webhook = null,
permissions = null; permissions = null;
@ -93,7 +93,7 @@ class SettingsMigrator {
permissions.guildId = this.guild; permissions.guildId = this.guild;
} }
translated._imported = true; // translated._imported = true;
translated.guildId = this.guild; translated.guildId = this.guild;
this.logger.info(`Settings migration for ${this.guild}`); this.logger.info(`Settings migration for ${this.guild}`);
@ -220,6 +220,22 @@ class SettingsMigrator {
}; };
} }
if (selfrole) {
settings.selfrole = {
roles: selfrole.roles, message: null, channel: null, text: null
};
}
if (activity) settings.activity = activity;
if (killitwithfire) {
settings.dehoist = {
enabled: killitwithfire.enabled,
begin: killitwithfire.startsWith,
characters: [],
strict: killitwithfire.strict
};
}
if (moderation || modlogs) if (moderation || modlogs)
settings.moderation = { settings.moderation = {
channel: result.modlogs || null, channel: result.modlogs || null,
@ -344,6 +360,7 @@ class SettingsMigrator {
const _points = entries.filter(([key]) => !key.includes('Expire') && !['enabled', 'associations', 'multiplier'].includes(key)); const _points = entries.filter(([key]) => !key.includes('Expire') && !['enabled', 'associations', 'multiplier'].includes(key));
for (const [key, value] of _points) points[key.toUpperCase()] = value; for (const [key, value] of _points) points[key.toUpperCase()] = value;
// eslint-disable-next-line prefer-const
for (let [key, value] of _expirations) { for (let [key, value] of _expirations) {
key = key.replace('Expire', '').toUpperCase(); key = key.replace('Expire', '').toUpperCase();
expirations[key] = value; expirations[key] = value;

View File

@ -3,5 +3,6 @@ module.exports = {
BinaryTree: require('./BinaryTree.js'), BinaryTree: require('./BinaryTree.js'),
FilterUtil: require('./FilterUtil.js'), FilterUtil: require('./FilterUtil.js'),
Logger: require('./Logger.js'), Logger: require('./Logger.js'),
SettingsMigrator: require('./SettingsMigrator') SettingsMigrator: require('./SettingsMigrator'),
InfractionMigrator: require('./InfractionMigrator')
}; };