mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-21 16:55:03 +00:00
feat: AFK Command
This commit is contained in:
parent
7f75d6d8d3
commit
b6c822e826
10 changed files with 262 additions and 0 deletions
31
backend/src/data/AFK.ts
Normal file
31
backend/src/data/AFK.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { BaseRepository } from "./BaseRepository";
|
||||||
|
import { getRepository, Repository } from "typeorm";
|
||||||
|
import { AFK as AFKEntity } from "./entities/AFK";
|
||||||
|
|
||||||
|
export class AFK extends BaseRepository {
|
||||||
|
private afk: Repository<AFKEntity>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.afk = getRepository(AFKEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserAFKStatus(user_id: string) {
|
||||||
|
return await this.afk.findOne({ user_id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAfkStatus(user_id: string, status: string) {
|
||||||
|
const settings = new AFKEntity();
|
||||||
|
settings.user_id = user_id;
|
||||||
|
settings.status = status;
|
||||||
|
|
||||||
|
return await this.afk.save(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearAFKStatus(user_id: string) {
|
||||||
|
const afk = await this.afk.findOne({ user_id });
|
||||||
|
if (!afk) return;
|
||||||
|
|
||||||
|
return await this.afk.delete({ user_id });
|
||||||
|
}
|
||||||
|
}
|
14
backend/src/data/entities/AFK.ts
Normal file
14
backend/src/data/entities/AFK.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Column, Entity, PrimaryColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity("afk")
|
||||||
|
export class AFK {
|
||||||
|
@Column()
|
||||||
|
@PrimaryColumn()
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
user_id: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
status: string;
|
||||||
|
}
|
34
backend/src/migrations/1617410382003-AFKStatus.ts
Normal file
34
backend/src/migrations/1617410382003-AFKStatus.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||||
|
|
||||||
|
export class AFKStatus1617410382003 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.createTable(
|
||||||
|
new Table({
|
||||||
|
name: "afk",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: "id",
|
||||||
|
type: "int",
|
||||||
|
isPrimary: true,
|
||||||
|
isGenerated: true,
|
||||||
|
generationStrategy: "increment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user_id",
|
||||||
|
type: "bigint",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
type: "varchar",
|
||||||
|
length: "255",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
return queryRunner.dropTable("afk");
|
||||||
|
}
|
||||||
|
}
|
46
backend/src/plugins/AFK/AFKPlugin.ts
Normal file
46
backend/src/plugins/AFK/AFKPlugin.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { PluginOptions } from "knub";
|
||||||
|
import { AFKPluginType, ConfigSchema } from './types';
|
||||||
|
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||||
|
import { AFK } from "src/data/AFK";
|
||||||
|
|
||||||
|
import { AfkSetCmd } from "./commands/AFKCmd";
|
||||||
|
import { AFKNotificationEvt } from "./events/AFKNotificationEvt";
|
||||||
|
|
||||||
|
const defaultOptions: PluginOptions<AFKPluginType> = {
|
||||||
|
config: {
|
||||||
|
can_afk: false,
|
||||||
|
allow_links: false,
|
||||||
|
allow_invites: false,
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
level: '>=50',
|
||||||
|
config: {
|
||||||
|
can_afk: true,
|
||||||
|
allow_links: true,
|
||||||
|
allow_invites: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AFKPlugin = zeppelinGuildPlugin<AFKPluginType>()("afk", {
|
||||||
|
showInDocs: true,
|
||||||
|
info: {
|
||||||
|
prettyName: "AFK",
|
||||||
|
description: "Allows you to set your AFK Status.",
|
||||||
|
},
|
||||||
|
|
||||||
|
configSchema: ConfigSchema,
|
||||||
|
defaultOptions,
|
||||||
|
|
||||||
|
commands: [AfkSetCmd],
|
||||||
|
events: [AFKNotificationEvt],
|
||||||
|
|
||||||
|
onLoad(pluginData) {
|
||||||
|
const { state } = pluginData;
|
||||||
|
|
||||||
|
state.afkUsers = new AFK();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
46
backend/src/plugins/AFK/commands/AFKCmd.ts
Normal file
46
backend/src/plugins/AFK/commands/AFKCmd.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { afkCmd } from "../types";
|
||||||
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
|
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||||
|
import { parseStatusMessage } from "../functions/parseStatusMessage";
|
||||||
|
|
||||||
|
export const AfkSetCmd = afkCmd({
|
||||||
|
trigger: ['afk', 'afk set'],
|
||||||
|
permission: 'can_afk',
|
||||||
|
|
||||||
|
signature: {
|
||||||
|
status: ct.string({ rest: true, required: true }),
|
||||||
|
},
|
||||||
|
|
||||||
|
async run({ message: msg, args, pluginData }) {
|
||||||
|
// Checks if the user is AFK, if so, return.
|
||||||
|
const isAfk = await pluginData.state.afkUsers.getUserAFKStatus(msg.author.id);
|
||||||
|
if (isAfk) return;
|
||||||
|
|
||||||
|
const status = args.status.join(" ");
|
||||||
|
|
||||||
|
// Check status length
|
||||||
|
if (status.length > 124) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, "Status length is above **124** characters.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks status based on configuration options
|
||||||
|
const parsed = parseStatusMessage(pluginData, msg.member, status);
|
||||||
|
if (typeof parsed === 'string') {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, parsed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set user status
|
||||||
|
const afk = await pluginData.state.afkUsers.setAfkStatus(
|
||||||
|
msg.author.id,
|
||||||
|
status,
|
||||||
|
);
|
||||||
|
|
||||||
|
sendSuccessMessage(pluginData, msg.channel, `AFK Status set to: **${afk.status}**`, {
|
||||||
|
roles: false,
|
||||||
|
everyone: false,
|
||||||
|
users: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
28
backend/src/plugins/AFK/events/AFKNotificationEvt.ts
Normal file
28
backend/src/plugins/AFK/events/AFKNotificationEvt.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { sendUserMentionMessage, sendWelcomeBackMessage } from "../functions/buildAFKMessages";
|
||||||
|
import { afkEvt } from "../types";
|
||||||
|
|
||||||
|
export const AFKNotificationEvt = afkEvt({
|
||||||
|
event: 'messageCreate',
|
||||||
|
|
||||||
|
listener: async ({ pluginData, args: { message } }) => {
|
||||||
|
// Mention Check (if someone mentions the AFK user)
|
||||||
|
if (message.mentions.length) {
|
||||||
|
const afk = await pluginData.state.afkUsers.getUserAFKStatus(message.mentions[0].id);
|
||||||
|
if (!afk) return;
|
||||||
|
|
||||||
|
sendUserMentionMessage(message, afk.status);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self AFK Check (if user is the one that's AFK)
|
||||||
|
const afk = await pluginData.state.afkUsers.getUserAFKStatus(message.author.id);
|
||||||
|
if (!afk) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pluginData.state.afkUsers.clearAFKStatus(message.author.id);
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
sendWelcomeBackMessage(message);
|
||||||
|
}
|
||||||
|
});
|
23
backend/src/plugins/AFK/functions/buildAFKMessages.ts
Normal file
23
backend/src/plugins/AFK/functions/buildAFKMessages.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Message } from "eris";
|
||||||
|
|
||||||
|
export function sendUserMentionMessage(message: Message, status: string) {
|
||||||
|
return message.channel.createMessage({
|
||||||
|
allowedMentions: {
|
||||||
|
users: [message.author.id],
|
||||||
|
everyone: false,
|
||||||
|
roles: false,
|
||||||
|
},
|
||||||
|
content: `<@!${message.author.id}>, the user mentioned is currently AFK: **${status}**`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendWelcomeBackMessage(message: Message) {
|
||||||
|
return message.channel.createMessage({
|
||||||
|
allowedMentions: {
|
||||||
|
users: [message.author.id],
|
||||||
|
everyone: false,
|
||||||
|
roles: false,
|
||||||
|
},
|
||||||
|
content: `<@!${message.author.id}>, welcome back!`,
|
||||||
|
});
|
||||||
|
}
|
18
backend/src/plugins/AFK/functions/parseStatusMessage.ts
Normal file
18
backend/src/plugins/AFK/functions/parseStatusMessage.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Member } from "eris";
|
||||||
|
import { GuildPluginData } from "knub";
|
||||||
|
import { hasPermission } from "src/pluginUtils";
|
||||||
|
import { AFKPluginType } from "../types";
|
||||||
|
|
||||||
|
// https://github.com/sapphire-project/utilities/blob/main/packages/discord-utilities/src/lib/regexes.ts#L58
|
||||||
|
const HttpUrlRegex = /^https?:\/\//;
|
||||||
|
|
||||||
|
// https://github.com/sapphire-project/utilities/blob/main/packages/discord-utilities/src/lib/regexes.ts#L25
|
||||||
|
const DiscordInviteLinkRegex = /^(?:https?:\/\/)?(?:www\.)?(?:discord\.gg\/|discord(?:app)?\.com\/invite\/)?(?<code>[\w\d-]{2,})$/i;
|
||||||
|
|
||||||
|
export function parseStatusMessage(pluginData: GuildPluginData<AFKPluginType>, member: Member, status: string) {
|
||||||
|
const allow_links = hasPermission(pluginData, "allow_links", { member });
|
||||||
|
const allow_invites = hasPermission(pluginData, "allow_invites", { member });
|
||||||
|
|
||||||
|
if (!allow_links && HttpUrlRegex.test(status)) return "Links are not allowed in an AFK status!";
|
||||||
|
if (!allow_invites && DiscordInviteLinkRegex.test(status)) return "Invites are not allowed in an AFK status!";
|
||||||
|
}
|
20
backend/src/plugins/AFK/types.ts
Normal file
20
backend/src/plugins/AFK/types.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import * as t from 'io-ts';
|
||||||
|
import { BasePluginType, guildCommand, guildEventListener } from 'knub';
|
||||||
|
import { AFK } from '../../data/AFK';
|
||||||
|
|
||||||
|
export const ConfigSchema = t.type({
|
||||||
|
can_afk: t.boolean,
|
||||||
|
allow_links: t.boolean,
|
||||||
|
allow_invites: t.boolean,
|
||||||
|
});
|
||||||
|
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||||
|
|
||||||
|
export interface AFKPluginType extends BasePluginType {
|
||||||
|
config: TConfigSchema;
|
||||||
|
state: {
|
||||||
|
afkUsers: AFK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const afkCmd = guildCommand<AFKPluginType>();
|
||||||
|
export const afkEvt = guildEventListener<AFKPluginType>();
|
|
@ -33,6 +33,7 @@ import { BotControlPlugin } from "./BotControl/BotControlPlugin";
|
||||||
import { GuildAccessMonitorPlugin } from "./GuildAccessMonitor/GuildAccessMonitorPlugin";
|
import { GuildAccessMonitorPlugin } from "./GuildAccessMonitor/GuildAccessMonitorPlugin";
|
||||||
import { TimeAndDatePlugin } from "./TimeAndDate/TimeAndDatePlugin";
|
import { TimeAndDatePlugin } from "./TimeAndDate/TimeAndDatePlugin";
|
||||||
import { CountersPlugin } from "./Counters/CountersPlugin";
|
import { CountersPlugin } from "./Counters/CountersPlugin";
|
||||||
|
import { AFKPlugin } from './AFK/AFKPlugin';
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export const guildPlugins: Array<ZeppelinGuildPluginBlueprint<any>> = [
|
export const guildPlugins: Array<ZeppelinGuildPluginBlueprint<any>> = [
|
||||||
|
@ -67,6 +68,7 @@ export const guildPlugins: Array<ZeppelinGuildPluginBlueprint<any>> = [
|
||||||
CustomEventsPlugin,
|
CustomEventsPlugin,
|
||||||
TimeAndDatePlugin,
|
TimeAndDatePlugin,
|
||||||
CountersPlugin,
|
CountersPlugin,
|
||||||
|
AFKPlugin,
|
||||||
];
|
];
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue