modlog import

This commit is contained in:
Erik 2022-06-13 12:41:56 +03:00
parent 2ea7d097f4
commit 4f75472c37
Signed by untrusted user: 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;
}
// TODO Move settings to a settings object insteadd of being in a mess with everything else in the data object
async updateSettings(settings) {
if (!this._settings) await this.settings();
try {

View File

@ -1,3 +1,4 @@
const { InfractionMigrator, Util } = require("../../../../utilities");
const SettingsMigrator = require("../../../../utilities/SettingsMigrator");
const { SlashCommand } = require("../../../interfaces");
@ -6,6 +7,7 @@ const dbs = {
'3': 'newgbot'
};
const { MONGODB_HOST, MONGODB_USERNAME, MONGODB_PASSWORD, MONGODB_V2_HOST } = process.env;
class ImportCommand extends SlashCommand {
constructor(client) {
@ -17,7 +19,7 @@ class ImportCommand extends SlashCommand {
guildOnly: true,
memberPermissions: ['ADMINISTRATOR'],
options: [{
name: ['settings', 'modlogs'],
name: ['settings'],
type: 'SUB_COMMAND',
options: [{
name: 'version',
@ -27,24 +29,49 @@ class ImportCommand extends SlashCommand {
{ 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 settings = await guild.settings();
if (settings._imported);
let settings = await guild.settings();
if (settings._imported[subcommand.name]) return { emoji: 'failure', index: 'COMMAND_IMPORT_IMPORTED', params: { thing: Util.capitalise(subcommand.name) } };
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;
const migrator = new SettingsMigrator(this.client, guild, {
host: MONGODB_HOST,
result._edit = true;
return result;
}
async modlogs(guild, version, overwrite = false) {
const migrator = new InfractionMigrator(this.client, guild, {
host: version === '2' ? MONGODB_V2_HOST : MONGODB_HOST,
username: MONGODB_USERNAME,
password: MONGODB_PASSWORD,
database: dbs[version], // Default to v3
@ -54,7 +81,48 @@ class ImportCommand extends SlashCommand {
await migrator.connect();
let imported = null;
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) {
this.client.logger.error(err.stack);
return { index: 'COMMAND_IMPORT_ERROR', params: { message: err.message }, emoji: 'failure' };
@ -68,7 +136,7 @@ class ImportCommand extends SlashCommand {
if (typeof webhook === 'string') {
const hooks = await guild.fetchWebhooks();
const hook = hooks.get(webhook);
if(hook) await guild.updateWebhook('messages', hook);
if (hook) await guild.updateWebhook('messages', hook);
} else if (version === '3') {
delete webhook.feature;
await this.client.storageManager.mongodb.webhooks.updateOne({ feature: 'messages', guild: guild.id }, webhook);

View File

@ -1,3 +1,5 @@
const { ObjectId } = require("mongodb");
class MongodbTable {
constructor(client, provider, opts = {}) {
@ -11,11 +13,11 @@ class MongodbTable {
//Data Search
async find(query, opts = {}, { sort, skip, limit } = {}) { //opts: { projection: ... }
find(query, opts = {}, { sort, skip, limit } = {}) { //opts: { projection: ... }
query = this._handleData(query);
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 (skip) cursor.skip(skip);
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) {
query = this._handleData(query);
@ -164,17 +169,15 @@ class MongodbTable {
});
}
//Lazy Function
// Shouldn't be necessary anymore -- cleanup later
_handleData(data) { //Convert data._id to Mongo ObjectIds (gets converted to plaintext through shard communication)
// if (data._id) {
// if (typeof data._id === 'string') data._id = ObjectId(data._id);
// else if (typeof data._id === 'object') data._id = {
// $in: Object.values(data._id)[0].map((id) => {
// return ObjectId(id);
// })
// };
// }
_handleData(data) { //Convert data._id to Mongo ObjectIds
if (data._id) {
if (typeof data._id === 'string') data._id = ObjectId(data._id);
else if (typeof data._id === 'object') data._id = {
$in: Object.values(data._id)[0].map((id) => {
return ObjectId(id);
})
};
}
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`);
}
async migrate() {
async import() {
this.logger.debug(`Attempting settings migration for ${this.guild}`);
@ -65,10 +65,10 @@ class SettingsMigrator {
const settings = await this.mongo.findOne(collection, filter);
if (!settings) return Promise.reject(new Error('No old settings found'));
const { _version } = settings;
if (!_version) return Promise.reject(new Error('Unable to determine configuration version'));
// const { _version } = settings;
// 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,
permissions = null;
@ -93,7 +93,7 @@ class SettingsMigrator {
permissions.guildId = this.guild;
}
translated._imported = true;
// translated._imported = true;
translated.guildId = 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)
settings.moderation = {
channel: result.modlogs || null,
@ -344,6 +360,7 @@ class SettingsMigrator {
const _points = entries.filter(([key]) => !key.includes('Expire') && !['enabled', 'associations', 'multiplier'].includes(key));
for (const [key, value] of _points) points[key.toUpperCase()] = value;
// eslint-disable-next-line prefer-const
for (let [key, value] of _expirations) {
key = key.replace('Expire', '').toUpperCase();
expirations[key] = value;

View File

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