Add name history plugin
This commit is contained in:
parent
50c6233190
commit
681517341e
7 changed files with 245 additions and 6 deletions
64
src/data/GuildNameHistory.ts
Normal file
64
src/data/GuildNameHistory.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { BaseRepository } from "./BaseRepository";
|
||||||
|
import { getRepository, Repository } from "typeorm";
|
||||||
|
import { NameHistoryEntry } from "./entities/NameHistoryEntry";
|
||||||
|
|
||||||
|
const MAX_ENTRIES_PER_USER = 10;
|
||||||
|
|
||||||
|
export class GuildNameHistory extends BaseRepository {
|
||||||
|
private nameHistory: Repository<NameHistoryEntry>;
|
||||||
|
|
||||||
|
constructor(guildId) {
|
||||||
|
super(guildId);
|
||||||
|
this.nameHistory = getRepository(NameHistoryEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByUserId(userId): Promise<NameHistoryEntry[]> {
|
||||||
|
return this.nameHistory.find({
|
||||||
|
where: {
|
||||||
|
guild_id: this.guildId,
|
||||||
|
user_id: userId
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
id: "DESC"
|
||||||
|
},
|
||||||
|
take: MAX_ENTRIES_PER_USER
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastEntryByType(userId, type): Promise<NameHistoryEntry> {
|
||||||
|
return this.nameHistory.findOne({
|
||||||
|
where: {
|
||||||
|
guild_id: this.guildId,
|
||||||
|
user_id: userId,
|
||||||
|
type
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
id: "DESC"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async addEntry(userId, type, value) {
|
||||||
|
await this.nameHistory.insert({
|
||||||
|
guild_id: this.guildId,
|
||||||
|
user_id: userId,
|
||||||
|
type,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup (leave only the last MAX_ENTRIES_PER_USER entries)
|
||||||
|
const lastEntries = await this.getByUserId(userId);
|
||||||
|
if (lastEntries.length > MAX_ENTRIES_PER_USER) {
|
||||||
|
const earliestEntry = lastEntries[lastEntries.length - 1];
|
||||||
|
if (!earliestEntry) return;
|
||||||
|
|
||||||
|
this.nameHistory
|
||||||
|
.createQueryBuilder()
|
||||||
|
.where("guild_id = :guildId", { guildId: this.guildId })
|
||||||
|
.andWhere("user_id = :userId", { userId })
|
||||||
|
.andWhere("id < :id", { id: earliestEntry.id })
|
||||||
|
.delete()
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
src/data/NameHistoryEntryTypes.ts
Normal file
4
src/data/NameHistoryEntryTypes.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export enum NameHistoryEntryTypes {
|
||||||
|
Username = 1,
|
||||||
|
Nickname
|
||||||
|
}
|
18
src/data/entities/NameHistoryEntry.ts
Normal file
18
src/data/entities/NameHistoryEntry.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Entity, Column, PrimaryColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity("name_history")
|
||||||
|
export class NameHistoryEntry {
|
||||||
|
@Column()
|
||||||
|
@PrimaryColumn()
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column() guild_id: string;
|
||||||
|
|
||||||
|
@Column() user_id: string;
|
||||||
|
|
||||||
|
@Column() type: number;
|
||||||
|
|
||||||
|
@Column() value: string;
|
||||||
|
|
||||||
|
@Column() timestamp: string;
|
||||||
|
}
|
|
@ -61,6 +61,7 @@ import { CasesPlugin } from "./plugins/Cases";
|
||||||
import { MutesPlugin } from "./plugins/Mutes";
|
import { MutesPlugin } from "./plugins/Mutes";
|
||||||
import { SlowmodePlugin } from "./plugins/Slowmode";
|
import { SlowmodePlugin } from "./plugins/Slowmode";
|
||||||
import { StarboardPlugin } from "./plugins/Starboard";
|
import { StarboardPlugin } from "./plugins/Starboard";
|
||||||
|
import { NameHistoryPlugin } from "./plugins/NameHistory";
|
||||||
|
|
||||||
// Run latest database migrations
|
// Run latest database migrations
|
||||||
logger.info("Running database migrations");
|
logger.info("Running database migrations");
|
||||||
|
@ -72,12 +73,13 @@ connect().then(async conn => {
|
||||||
});
|
});
|
||||||
client.setMaxListeners(100);
|
client.setMaxListeners(100);
|
||||||
|
|
||||||
const basePlugins = ["message_saver", "cases", "mutes"];
|
const basePlugins = ["message_saver", "name_history", "cases", "mutes"];
|
||||||
|
|
||||||
const bot = new Knub(client, {
|
const bot = new Knub(client, {
|
||||||
plugins: [
|
plugins: [
|
||||||
// Base plugins (always enabled)
|
// Base plugins (always enabled)
|
||||||
MessageSaverPlugin,
|
MessageSaverPlugin,
|
||||||
|
NameHistoryPlugin,
|
||||||
CasesPlugin,
|
CasesPlugin,
|
||||||
MutesPlugin,
|
MutesPlugin,
|
||||||
|
|
||||||
|
@ -95,10 +97,7 @@ connect().then(async conn => {
|
||||||
StarboardPlugin
|
StarboardPlugin
|
||||||
],
|
],
|
||||||
|
|
||||||
globalPlugins: [
|
globalPlugins: [BotControlPlugin, LogServerPlugin],
|
||||||
BotControlPlugin,
|
|
||||||
LogServerPlugin
|
|
||||||
],
|
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
getEnabledPlugins(guildId, guildConfig): string[] {
|
getEnabledPlugins(guildId, guildConfig): string[] {
|
||||||
|
|
62
src/migrations/1546778415930-CreateNameHistoryTable.ts
Normal file
62
src/migrations/1546778415930-CreateNameHistoryTable.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||||
|
|
||||||
|
export class CreateNameHistoryTable1546778415930 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: "name_history",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: "id",
|
||||||
|
type: "int",
|
||||||
|
unsigned: true,
|
||||||
|
isGenerated: true,
|
||||||
|
generationStrategy: "increment",
|
||||||
|
isPrimary: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "guild_id",
|
||||||
|
type: "bigint",
|
||||||
|
unsigned: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user_id",
|
||||||
|
type: "bigint",
|
||||||
|
unsigned: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type",
|
||||||
|
type: "tinyint",
|
||||||
|
unsigned: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "value",
|
||||||
|
type: "varchar",
|
||||||
|
length: "128",
|
||||||
|
isNullable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timestamp",
|
||||||
|
type: "datetime",
|
||||||
|
default: "CURRENT_TIMESTAMP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
indices: [
|
||||||
|
{
|
||||||
|
columnNames: ["guild_id", "user_id"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnNames: ["type"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnNames: ["timestamp"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.dropTable("name_history");
|
||||||
|
}
|
||||||
|
}
|
85
src/plugins/NameHistory.ts
Normal file
85
src/plugins/NameHistory.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { Plugin, decorators as d } from "knub";
|
||||||
|
import { GuildNameHistory } from "../data/GuildNameHistory";
|
||||||
|
import { Member, Message, Relationship, User } from "eris";
|
||||||
|
import { NameHistoryEntryTypes } from "../data/NameHistoryEntryTypes";
|
||||||
|
import { createChunkedMessage, errorMessage, trimLines } from "../utils";
|
||||||
|
|
||||||
|
export class NameHistoryPlugin extends Plugin {
|
||||||
|
public static pluginName = "name_history";
|
||||||
|
|
||||||
|
protected nameHistory: GuildNameHistory;
|
||||||
|
|
||||||
|
getDefaultOptions() {
|
||||||
|
return {
|
||||||
|
permissions: {
|
||||||
|
view: false
|
||||||
|
},
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
level: ">=50",
|
||||||
|
permissions: {
|
||||||
|
view: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.nameHistory = GuildNameHistory.getInstance(this.guildId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@d.command("names", "<userId:userId>")
|
||||||
|
@d.permission("view")
|
||||||
|
async namesCmd(msg: Message, args: { userId: string }) {
|
||||||
|
const names = await this.nameHistory.getByUserId(args.userId);
|
||||||
|
if (!names) {
|
||||||
|
msg.channel.createMessage(errorMessage("No name history found for that user!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = names.map(entry => {
|
||||||
|
const type = entry.type === NameHistoryEntryTypes.Username ? "Username" : "Nickname";
|
||||||
|
const value = entry.value || "<none>";
|
||||||
|
return `\`[${entry.timestamp}]\` ${type} **${value}**`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = this.bot.users.get(args.userId);
|
||||||
|
const currentUsername = user ? `${user.username}#${user.discriminator}` : args.userId;
|
||||||
|
|
||||||
|
const message = trimLines(`
|
||||||
|
Name history for **${currentUsername}**:
|
||||||
|
|
||||||
|
${rows.join("\n")}
|
||||||
|
`);
|
||||||
|
createChunkedMessage(msg.channel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@d.event("userUpdate")
|
||||||
|
async onUserUpdate(user: User, oldUser: { username: string; discriminator: string; avatar: string }) {
|
||||||
|
console.log("onUserUpdate", user.username, oldUser.username);
|
||||||
|
if (user.username !== oldUser.username || user.discriminator !== oldUser.discriminator) {
|
||||||
|
const newUsername = `${user.username}#${user.discriminator}`;
|
||||||
|
await this.nameHistory.addEntry(user.id, NameHistoryEntryTypes.Username, newUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@d.event("presenceUpdate")
|
||||||
|
async onPresenceUpdate(other: Member | Relationship) {
|
||||||
|
const user = other.user;
|
||||||
|
const username = `${user.username}#${user.discriminator}`;
|
||||||
|
|
||||||
|
const lastEntry = await this.nameHistory.getLastEntryByType(user.id, NameHistoryEntryTypes.Username);
|
||||||
|
if (!lastEntry || lastEntry.value !== username) {
|
||||||
|
await this.nameHistory.addEntry(user.id, NameHistoryEntryTypes.Username, username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@d.event("guildMemberUpdate")
|
||||||
|
async onGuildMemberUpdate(_, member: Member, oldMember: { nick: string; roles: string[] }) {
|
||||||
|
if (member.nick !== oldMember.nick) {
|
||||||
|
await this.nameHistory.addEntry(member.id, NameHistoryEntryTypes.Nickname, member.nick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import at = require("lodash.at");
|
import at = require("lodash.at");
|
||||||
import { Guild, GuildAuditLogEntry } from "eris";
|
import { Guild, GuildAuditLogEntry, TextableChannel } from "eris";
|
||||||
import url from "url";
|
import url from "url";
|
||||||
import tlds from "tlds";
|
import tlds from "tlds";
|
||||||
import emojiRegex from "emoji-regex";
|
import emojiRegex from "emoji-regex";
|
||||||
|
@ -276,6 +276,13 @@ export function chunkMessageLines(str: string): string[] {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createChunkedMessage(channel: TextableChannel, messageText: string) {
|
||||||
|
const chunks = chunkMessageLines(messageText);
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
await channel.createMessage(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function noop() {
|
export function noop() {
|
||||||
// IT'S LITERALLY NOTHING
|
// IT'S LITERALLY NOTHING
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue