UNPKG

@ayanaware/bentocord

Version:

Bentocord is a Bento plugin designed to rapidly build fully functional Discord Bots.

325 lines 13.6 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BentocordInterface = void 0; /* eslint-disable @typescript-eslint/no-unused-vars */ const bento_1 = require("@ayanaware/bento"); const BentocordVariable_1 = require("./BentocordVariable"); const DiscordPermission_1 = require("./discord/constants/DiscordPermission"); const PermissionScope_1 = require("./interfaces/PermissionScope"); // TODO: Add documentation class BentocordInterface { constructor() { this.name = '@ayanaware/bentocord:Interface'; this.replaceable = true; this.prefixes = new Map(); this.permissions = new Map(); } /** * Used to determine what shards this process controls. * @returns Shard Data */ async getShardData() { return { shardIds: [0], shardCount: 1 }; } /** * Check if userId is a bot owner. * @param userId Discord User ID * @returns boolean */ async isOwner(userId) { const owners = this.owners.split(',').map(o => o.trim()); return owners.includes(userId); } // HELP async getHelpEmbed(embed) { const name = this.api.getProperty('APPLICATION_NAME') || 'Bentocord'; const version = this.api.getProperty('APPLICATION_VERSION') || ''; embed.setAuthor(`${name} ${version}`); return embed; } // COMMAND /** * Default & Required selfPermissions for command * @param command Command * @param ctx AnyCommandContext * @returns Array<DiscordPermission> */ async selfPermissions(command, ctx) { return [DiscordPermission_1.DiscordPermission.SEND_MESSAGES]; } /** * Allow's for domain-specific disabling of commands based on context. * Useful for many things, but not limited to: * - Disable free instance when paid instance is in the server * - Simple blacklist functionality * - Literally anything else you might want to check before running a command * @param command Command * @param ctx CommandContext * @returns boolean Whether or not to continue execution of command */ async checkCommand(command, ctx) { return true; } // PREFIXES /** * Get the prefix for a given snowflake (ex: guildId). * @param snowflake The snowflake * @returns The prefix */ async getPrefix(snowflake) { return this.prefixes.get(snowflake); } /** * Set the prefix for a given snowflake. * @param snowflake The snowflake * @param prefix The prefix */ async setPrefix(snowflake, prefix) { if (prefix == null) { this.prefixes.delete(snowflake); return; } this.prefixes.set(snowflake, prefix); } /** Extra ALWAYS available prefixes */ async getExtraPrefixes() { return []; } async resolveAlias(name, args, message) { return [undefined, undefined]; } // LOCALIZATION async formatNumber(num, ctx) { return null; } async formatDate(date, ctx) { return null; } /** * Get locale code for a given context. * @param ctx Translation Context (Snowflakes) * @returns Locale Code */ async getLocale(ctx) { return null; } /** * Format a translation string * @param key Translation key * @param repl Translation Replacements * @param ctx Translation Context (Snowflakes) * @param backup Translation Backup * @returns Translated string */ async formatTranslation(key, repl, ctx, backup) { // support basic interpolation for backup strings, simplifying a lot of logic in bentocord if (!backup) return null; for (const [k, v] of Object.entries(repl ?? {})) backup = backup.replace(new RegExp(`{${k}}`, 'gi'), v.toString()); return backup; } /** * Format a translation in all available languages. * @param key Translation Key * @param repl Translation Replacements * @returns Object, key is language, value is translation */ async formatTranslationMap(key, repl) { return {}; } /** * Can be used to convert translations to only the subset that discord supports * @param translations Translation Map from formatTranslationMap * @returns Converted Translation Map */ async convertTranslationMap(translations) { return translations; } // PERMISSIONS /** * Get the permission for a given snowflake. * @param permission The permission to check. * @param snowflake The snowflake to check (usually guildId or userId) * @param scope The scope to check. * @returns Whether the permission is allowed. */ async getPermission(permission, snowflake, scope) { let key = `${permission}`; if (scope) key = `${scope.scope}.${key}`; if (snowflake) key = `${snowflake}.${key}`; return this.permissions.get(key) || null; } /** * Set the permission for a given snowflake. * @param permission The permission to set. * @param value Whether the permission is allowed. * @param snowflake The snowflake to set (usually guildId or userId) * @param scope The scope to set. */ async setPermission(permission, value, snowflake, scope) { let key = `${permission}`; if (scope) key = `${scope.scope}.${key}`; if (snowflake) key = `${snowflake}.${key}`; if (value == null) { this.permissions.delete(key); return; } this.permissions.set(key, value); } /** * Find permission override for a given context (guild, channel, user) * @param permission Permission to check * @param snowflakes Snowflakes of the context * @returns Tuple with [state, where] boolean if explicitly set, otherwise null */ async findPermission(permission, snowflakes) { if (snowflakes.userId && await this.isOwner(snowflakes.userId)) return [true, 'owner']; // Global Check const globalCheck = await this.getPermission(permission); if (typeof globalCheck === 'boolean') return [globalCheck, 'global']; // Global User Check const userCheck = await this.getPermission(permission, snowflakes.userId); if (typeof userCheck === 'boolean') return [userCheck, 'user']; // Guild Checks if (snowflakes.guildId) { const guildId = snowflakes.guildId; const checks = { // Advanced Permissions [PermissionScope_1.PermissionScopeType.MEMBER_CHANNEL]: [snowflakes.userId, snowflakes.channelId], [PermissionScope_1.PermissionScopeType.ROLE_CHANNEL]: [snowflakes.roleIds, snowflakes.channelId], // General Permissions [PermissionScope_1.PermissionScopeType.MEMBER]: [snowflakes.userId], [PermissionScope_1.PermissionScopeType.ROLE]: [snowflakes.roleIds], [PermissionScope_1.PermissionScopeType.CHANNEL]: [snowflakes.channelId], // ChannelId }; // Some Explications of this insanity: // - We loop over each check, and lookup the permission value. // - In most cases `scope` is just the direct snowflake (e.g. userId, roleId, channelId) // - However for advanced permissions, we append the remaining snowflakes (e.g. userId.channelId) // This results in a ordered list of checks of decreasing specificity. // Currently our checks, in order, are: User in Channel, Role in Channel, User, Role, Channel, Guild for (const [type, check] of Object.entries(checks)) { const [first, ...rest] = check; // handle roleIds Array if (Array.isArray(first)) { for (const roleId of first) { let roleScope = roleId; if (rest.length) roleScope += `.${rest.join('.')}`; const v = await this.getPermission(permission, guildId, { type, scope: roleScope }); if (typeof v === 'boolean') return [v, type]; } continue; } let scope = first; if (rest.length) scope += `.${rest.join('.')}`; const value = await this.getPermission(permission, guildId, { type, scope }); if (typeof value === 'boolean') return [value, type]; } // Guild Wide Check const guildCheck = await this.getPermission(permission, guildId); if (typeof guildCheck === 'boolean') return [guildCheck, 'server']; } return [null, null]; } /** * Check if ctx has a given permission. * @param ctx CommandContext * @param perm Permission * @param def Permission defaults * @returns Whether context has the provided permission. */ async checkPermission(ctx, perm, def) { // convert permission to "." separated string if (Array.isArray(perm)) perm = perm.join('.'); // bot-owner bypass (bot owner has all permissions) if (await this.isOwner(ctx.userId)) return true; // get defaults let defaults = def ?? { user: true, admin: true }; if (typeof defaults === 'boolean') defaults = { user: defaults, admin: true }; // create messagecontext const permCtx = { userId: ctx.userId, channelId: ctx.channelId }; if (ctx.guildId) permCtx.guildId = ctx.guildId; if (ctx.member?.roles) permCtx.roleIds = ctx.member.roles; // explicit check const checks = [perm]; // prevent explicit-only permissions from being modified via 'all' checks // such permissions should only be explicitly granted by a bot owner if (defaults.user || defaults.admin) { // "all.category" check const category = ctx.command.definition.category; if (category) checks.push(`all.${category}`); // "all" check checks.push('all'); } // cache guild-admin check const isGuildAdmin = ctx.member && ctx.member.permissions.has('administrator'); // loop checks, break on explicit state for (const permission of checks) { const [state, where] = await this.findPermission(permission, permCtx); if (typeof state !== 'boolean') continue; // override found // explicit allow if (state) return true; // explicit deny if (where === 'global') { await ctx.createTranslatedResponse('BENTOCORD_PERMISSION_GLOBAL_DENIED', { permission }, 'Permission `{permission}` is denied globally. As this can only be done by a bot owner, it was likely intentional.'); } else if (where === 'user') { await ctx.createTranslatedResponse('BENTOCORD_PERMISSION_USER_DENIED', { permission }, 'Permission `{permission}` is globally denied for you. As this can only be done by a bot owner, it was likely intentional.'); } else { // guild-admin bypass: unless revoked globally, guild-admin has all admin permissions, prevents lockout if (defaults.admin && isGuildAdmin) return true; await ctx.createTranslatedResponse('BENTOCORD_PERMISSION_DENIED', { permission, where }, 'Permission `{permission}` has been denied on the `{where}` level.'); } return false; } // no override found // guild-admin bypass: guild-admin has all admin permissions if (defaults.admin && isGuildAdmin) return true; // check if default allowed if (defaults.user) return true; // default denied await ctx.createTranslatedResponse('BENTOCORD_PERMISSION_DENIED_DEFAULT', { permission: perm }, 'Permission `{permission}` is denined by default. Please contact a server administrator to grant you this permission.'); return false; } } __decorate([ (0, bento_1.Variable)({ name: BentocordVariable_1.BentocordVariable.BENTOCORD_BOT_OWNERS, default: '' }), __metadata("design:type", String) ], BentocordInterface.prototype, "owners", void 0); exports.BentocordInterface = BentocordInterface; //# sourceMappingURL=BentocordInterface.js.map