diff options
Diffstat (limited to 'src/lib/common/util')
-rw-r--r-- | src/lib/common/util/Arg.ts | 192 | ||||
-rw-r--r-- | src/lib/common/util/Format.ts | 119 | ||||
-rw-r--r-- | src/lib/common/util/Minecraft.ts | 349 | ||||
-rw-r--r-- | src/lib/common/util/Minecraft_Test.ts | 86 | ||||
-rw-r--r-- | src/lib/common/util/Moderation.ts | 556 |
5 files changed, 0 insertions, 1302 deletions
diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts deleted file mode 100644 index d362225..0000000 --- a/src/lib/common/util/Arg.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { - type BaseBushArgumentType, - type BushArgumentType, - type BushArgumentTypeCaster, - type CommandMessage, - type SlashMessage -} from '#lib'; -import { Argument, type Command, type Flag, type ParsedValuePredicate } from 'discord-akairo'; -import { type Message } from 'discord.js'; - -/** - * Casts a phrase to this argument's type. - * @param type - The type to cast to. - * @param message - Message that called the command. - * @param phrase - Phrase to process. - */ -export async function cast<T extends ATC>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<ATCR<T>>; -export async function cast<T extends KBAT>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<BAT[T]>; -export async function cast(type: AT | ATC, message: CommandMessage | SlashMessage, phrase: string): Promise<any>; -export async function cast( - this: ThisType<Command>, - type: ATC | AT, - message: CommandMessage | SlashMessage, - phrase: string -): Promise<any> { - return Argument.cast.call(this, type as any, message.client.commandHandler.resolver, message as Message, phrase); -} - -/** - * Creates a type that is the left-to-right composition of the given types. - * If any of the types fails, the entire composition fails. - * @param types - Types to use. - */ -export function compose<T extends ATC>(...types: T[]): ATCATCR<T>; -export function compose<T extends KBAT>(...types: T[]): ATCBAT<T>; -export function compose(...types: (AT | ATC)[]): ATC; -export function compose(...types: (AT | ATC)[]): ATC { - return Argument.compose(...(types as any)); -} - -/** - * Creates a type that is the left-to-right composition of the given types. - * If any of the types fails, the composition still continues with the failure passed on. - * @param types - Types to use. - */ -export function composeWithFailure<T extends ATC>(...types: T[]): ATCATCR<T>; -export function composeWithFailure<T extends KBAT>(...types: T[]): ATCBAT<T>; -export function composeWithFailure(...types: (AT | ATC)[]): ATC; -export function composeWithFailure(...types: (AT | ATC)[]): ATC { - return Argument.composeWithFailure(...(types as any)); -} - -/** - * Checks if something is null, undefined, or a fail flag. - * @param value - Value to check. - */ -export function isFailure(value: any): value is null | undefined | (Flag & { value: any }) { - return Argument.isFailure(value); -} - -/** - * Creates a type from multiple types (product type). - * Only inputs where each type resolves with a non-void value are valid. - * @param types - Types to use. - */ -export function product<T extends ATC>(...types: T[]): ATCATCR<T>; -export function product<T extends KBAT>(...types: T[]): ATCBAT<T>; -export function product(...types: (AT | ATC)[]): ATC; -export function product(...types: (AT | ATC)[]): ATC { - return Argument.product(...(types as any)); -} - -/** - * Creates a type where the parsed value must be within a range. - * @param type - The type to use. - * @param min - Minimum value. - * @param max - Maximum value. - * @param inclusive - Whether or not to be inclusive on the upper bound. - */ -export function range<T extends ATC>(type: T, min: number, max: number, inclusive?: boolean): ATCATCR<T>; -export function range<T extends KBAT>(type: T, min: number, max: number, inclusive?: boolean): ATCBAT<T>; -export function range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC; -export function range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC { - return Argument.range(type as any, min, max, inclusive); -} - -/** - * Creates a type that parses as normal but also tags it with some data. - * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed. - * @param type - The type to use. - * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. - */ -export function tagged<T extends ATC>(type: T, tag?: any): ATCATCR<T>; -export function tagged<T extends KBAT>(type: T, tag?: any): ATCBAT<T>; -export function tagged(type: AT | ATC, tag?: any): ATC; -export function tagged(type: AT | ATC, tag?: any): ATC { - return Argument.tagged(type as any, tag); -} - -/** - * Creates a type from multiple types (union type). - * The first type that resolves to a non-void value is used. - * Each type will also be tagged using `tagged` with themselves. - * @param types - Types to use. - */ -export function taggedUnion<T extends ATC>(...types: T[]): ATCATCR<T>; -export function taggedUnion<T extends KBAT>(...types: T[]): ATCBAT<T>; -export function taggedUnion(...types: (AT | ATC)[]): ATC; -export function taggedUnion(...types: (AT | ATC)[]): ATC { - return Argument.taggedUnion(...(types as any)); -} - -/** - * Creates a type that parses as normal but also tags it with some data and carries the original input. - * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed. - * @param type - The type to use. - * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string. - */ -export function taggedWithInput<T extends ATC>(type: T, tag?: any): ATCATCR<T>; -export function taggedWithInput<T extends KBAT>(type: T, tag?: any): ATCBAT<T>; -export function taggedWithInput(type: AT | ATC, tag?: any): ATC; -export function taggedWithInput(type: AT | ATC, tag?: any): ATC { - return Argument.taggedWithInput(type as any, tag); -} - -/** - * Creates a type from multiple types (union type). - * The first type that resolves to a non-void value is used. - * @param types - Types to use. - */ -export function union<T extends ATC>(...types: T[]): ATCATCR<T>; -export function union<T extends KBAT>(...types: T[]): ATCBAT<T>; -export function union(...types: (AT | ATC)[]): ATC; -export function union(...types: (AT | ATC)[]): ATC { - return Argument.union(...(types as any)); -} - -/** - * Creates a type with extra validation. - * If the predicate is not true, the value is considered invalid. - * @param type - The type to use. - * @param predicate - The predicate function. - */ -export function validate<T extends ATC>(type: T, predicate: ParsedValuePredicate): ATCATCR<T>; -export function validate<T extends KBAT>(type: T, predicate: ParsedValuePredicate): ATCBAT<T>; -export function validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC; -export function validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC { - return Argument.validate(type as any, predicate); -} - -/** - * Creates a type that parses as normal but also carries the original input. - * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed. - * @param type - The type to use. - */ -export function withInput<T extends ATC>(type: T): ATC<ATCR<T>>; -export function withInput<T extends KBAT>(type: T): ATCBAT<T>; -export function withInput(type: AT | ATC): ATC; -export function withInput(type: AT | ATC): ATC { - return Argument.withInput(type as any); -} - -type BushArgumentTypeCasterReturn<R> = R extends BushArgumentTypeCaster<infer S> ? S : R; -/** ```ts - * <R = unknown> = BushArgumentTypeCaster<R> - * ``` */ -type ATC<R = unknown> = BushArgumentTypeCaster<R>; -/** ```ts - * keyof BaseBushArgumentType - * ``` */ -type KBAT = keyof BaseBushArgumentType; -/** ```ts - * <R> = BushArgumentTypeCasterReturn<R> - * ``` */ -type ATCR<R> = BushArgumentTypeCasterReturn<R>; -/** ```ts - * BushArgumentType - * ``` */ -type AT = BushArgumentType; -/** ```ts - * BaseBushArgumentType - * ``` */ -type BAT = BaseBushArgumentType; - -/** ```ts - * <T extends BushArgumentTypeCaster> = BushArgumentTypeCaster<BushArgumentTypeCasterReturn<T>> - * ``` */ -type ATCATCR<T extends BushArgumentTypeCaster> = BushArgumentTypeCaster<BushArgumentTypeCasterReturn<T>>; -/** ```ts - * <T extends keyof BaseBushArgumentType> = BushArgumentTypeCaster<BaseBushArgumentType[T]> - * ``` */ -type ATCBAT<T extends keyof BaseBushArgumentType> = BushArgumentTypeCaster<BaseBushArgumentType[T]>; diff --git a/src/lib/common/util/Format.ts b/src/lib/common/util/Format.ts deleted file mode 100644 index debaf4b..0000000 --- a/src/lib/common/util/Format.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { type CodeBlockLang } from '#lib'; -import { - bold as discordBold, - codeBlock as discordCodeBlock, - escapeBold as discordEscapeBold, - escapeCodeBlock as discordEscapeCodeBlock, - escapeInlineCode as discordEscapeInlineCode, - escapeItalic as discordEscapeItalic, - escapeMarkdown, - escapeSpoiler as discordEscapeSpoiler, - escapeStrikethrough as discordEscapeStrikethrough, - escapeUnderline as discordEscapeUnderline, - inlineCode as discordInlineCode, - italic as discordItalic, - spoiler as discordSpoiler, - strikethrough as discordStrikethrough, - underscore as discordUnderscore -} from 'discord.js'; - -/** - * Wraps the content inside a codeblock with no language. - * @param content The content to wrap. - */ -export function codeBlock(content: string): string; - -/** - * Wraps the content inside a codeblock with the specified language. - * @param language The language for the codeblock. - * @param content The content to wrap. - */ -export function codeBlock(language: CodeBlockLang, content: string): string; -export function codeBlock(languageOrContent: string, content?: string): string { - return typeof content === 'undefined' - ? discordCodeBlock(discordEscapeCodeBlock(`${languageOrContent}`)) - : discordCodeBlock(`${languageOrContent}`, discordEscapeCodeBlock(`${content}`)); -} - -/** - * Wraps the content inside \`backticks\`, which formats it as inline code. - * @param content The content to wrap. - */ -export function inlineCode(content: string): string { - return discordInlineCode(discordEscapeInlineCode(`${content}`)); -} - -/** - * Formats the content into italic text. - * @param content The content to wrap. - */ -export function italic(content: string): string { - return discordItalic(discordEscapeItalic(`${content}`)); -} - -/** - * Formats the content into bold text. - * @param content The content to wrap. - */ -export function bold(content: string): string { - return discordBold(discordEscapeBold(`${content}`)); -} - -/** - * Formats the content into underscored text. - * @param content The content to wrap. - */ -export function underscore(content: string): string { - return discordUnderscore(discordEscapeUnderline(`${content}`)); -} - -/** - * Formats the content into strike-through text. - * @param content The content to wrap. - */ -export function strikethrough(content: string): string { - return discordStrikethrough(discordEscapeStrikethrough(`${content}`)); -} - -/** - * Wraps the content inside spoiler (hidden text). - * @param content The content to wrap. - */ -export function spoiler(content: string): string { - return discordSpoiler(discordEscapeSpoiler(`${content}`)); -} - -/** - * Formats input: makes it bold and escapes any other markdown - * @param text The input - */ -export function input(text: string): string { - return bold(sanitizeInputForDiscord(`${text}`)); -} - -/** - * Formats input for logs: makes it highlighted - * @param text The input - */ -export function inputLog(text: string): string { - return `<<${sanitizeWtlAndControl(`${text}`)}>>`; -} - -/** - * Removes all characters in a string that are either control characters or change the direction of text etc. - * @param str The string you would like sanitized - */ -export function sanitizeWtlAndControl(str: string) { - // eslint-disable-next-line no-control-regex - return `${str}`.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, ''); -} - -/** - * Removed wtl and control characters and escapes any other markdown - * @param text The input - */ -export function sanitizeInputForDiscord(text: string): string { - return escapeMarkdown(sanitizeWtlAndControl(`${text}`)); -} - -export { escapeMarkdown } from 'discord.js'; diff --git a/src/lib/common/util/Minecraft.ts b/src/lib/common/util/Minecraft.ts deleted file mode 100644 index a12ebf2..0000000 --- a/src/lib/common/util/Minecraft.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { Byte, Int, parse } from '@ironm00n/nbt-ts'; -import { BitField } from 'discord.js'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -export enum FormattingCodes { - Black = '§0', - DarkBlue = '§1', - DarkGreen = '§2', - DarkAqua = '§3', - DarkRed = '§4', - DarkPurple = '§5', - Gold = '§6', - Gray = '§7', - DarkGray = '§8', - Blue = '§9', - Green = '§a', - Aqua = '§b', - Red = '§c', - LightPurple = '§d', - Yellow = '§e', - White = '§f', - - Obfuscated = '§k', - Bold = '§l', - Strikethrough = '§m', - Underline = '§n', - Italic = '§o', - Reset = '§r' -} - -// https://minecraft.fandom.com/wiki/Formatting_codes -export const formattingInfo = { - [FormattingCodes.Black]: { - foreground: 'rgb(0, 0, 0)', - foregroundDarker: 'rgb(0, 0, 0)', - background: 'rgb(0, 0, 0)', - backgroundDarker: 'rgb(0, 0, 0)', - ansi: '\u001b[0;30m' - }, - [FormattingCodes.DarkBlue]: { - foreground: 'rgb(0, 0, 170)', - foregroundDarker: 'rgb(0, 0, 118)', - background: 'rgb(0, 0, 42)', - backgroundDarker: 'rgb(0, 0, 29)', - ansi: '\u001b[0;34m' - }, - [FormattingCodes.DarkGreen]: { - foreground: 'rgb(0, 170, 0)', - foregroundDarker: 'rgb(0, 118, 0)', - background: 'rgb(0, 42, 0)', - backgroundDarker: 'rgb(0, 29, 0)', - ansi: '\u001b[0;32m' - }, - [FormattingCodes.DarkAqua]: { - foreground: 'rgb(0, 170, 170)', - foregroundDarker: 'rgb(0, 118, 118)', - background: 'rgb(0, 42, 42)', - backgroundDarker: 'rgb(0, 29, 29)', - ansi: '\u001b[0;36m' - }, - [FormattingCodes.DarkRed]: { - foreground: 'rgb(170, 0, 0)', - foregroundDarker: 'rgb(118, 0, 0)', - background: 'rgb(42, 0, 0)', - backgroundDarker: 'rgb(29, 0, 0)', - ansi: '\u001b[0;31m' - }, - [FormattingCodes.DarkPurple]: { - foreground: 'rgb(170, 0, 170)', - foregroundDarker: 'rgb(118, 0, 118)', - background: 'rgb(42, 0, 42)', - backgroundDarker: 'rgb(29, 0, 29)', - ansi: '\u001b[0;35m' - }, - [FormattingCodes.Gold]: { - foreground: 'rgb(255, 170, 0)', - foregroundDarker: 'rgb(178, 118, 0)', - background: 'rgb(42, 42, 0)', - backgroundDarker: 'rgb(29, 29, 0)', - ansi: '\u001b[0;33m' - }, - [FormattingCodes.Gray]: { - foreground: 'rgb(170, 170, 170)', - foregroundDarker: 'rgb(118, 118, 118)', - background: 'rgb(42, 42, 42)', - backgroundDarker: 'rgb(29, 29, 29)', - ansi: '\u001b[0;37m' - }, - [FormattingCodes.DarkGray]: { - foreground: 'rgb(85, 85, 85)', - foregroundDarker: 'rgb(59, 59, 59)', - background: 'rgb(21, 21, 21)', - backgroundDarker: 'rgb(14, 14, 14)', - ansi: '\u001b[0;90m' - }, - [FormattingCodes.Blue]: { - foreground: 'rgb(85, 85, 255)', - foregroundDarker: 'rgb(59, 59, 178)', - background: 'rgb(21, 21, 63)', - backgroundDarker: 'rgb(14, 14, 44)', - ansi: '\u001b[0;94m' - }, - [FormattingCodes.Green]: { - foreground: 'rgb(85, 255, 85)', - foregroundDarker: 'rgb(59, 178, 59)', - background: 'rgb(21, 63, 21)', - backgroundDarker: 'rgb(14, 44, 14)', - ansi: '\u001b[0;92m' - }, - [FormattingCodes.Aqua]: { - foreground: 'rgb(85, 255, 255)', - foregroundDarker: 'rgb(59, 178, 178)', - background: 'rgb(21, 63, 63)', - backgroundDarker: 'rgb(14, 44, 44)', - ansi: '\u001b[0;96m' - }, - [FormattingCodes.Red]: { - foreground: 'rgb(255, 85, 85)', - foregroundDarker: 'rgb(178, 59, 59)', - background: 'rgb(63, 21, 21)', - backgroundDarker: 'rgb(44, 14, 14)', - ansi: '\u001b[0;91m' - }, - [FormattingCodes.LightPurple]: { - foreground: 'rgb(255, 85, 255)', - foregroundDarker: 'rgb(178, 59, 178)', - background: 'rgb(63, 21, 63)', - backgroundDarker: 'rgb(44, 14, 44)', - ansi: '\u001b[0;95m' - }, - [FormattingCodes.Yellow]: { - foreground: 'rgb(255, 255, 85)', - foregroundDarker: 'rgb(178, 178, 59)', - background: 'rgb(63, 63, 21)', - backgroundDarker: 'rgb(44, 44, 14)', - ansi: '\u001b[0;93m' - }, - [FormattingCodes.White]: { - foreground: 'rgb(255, 255, 255)', - foregroundDarker: 'rgb(178, 178, 178)', - background: 'rgb(63, 63, 63)', - backgroundDarker: 'rgb(44, 44, 44)', - ansi: '\u001b[0;97m' - }, - - [FormattingCodes.Obfuscated]: { ansi: '\u001b[8m' }, - [FormattingCodes.Bold]: { ansi: '\u001b[1m' }, - [FormattingCodes.Strikethrough]: { ansi: '\u001b[9m' }, - [FormattingCodes.Underline]: { ansi: '\u001b[4m' }, - [FormattingCodes.Italic]: { ansi: '\u001b[3m' }, - [FormattingCodes.Reset]: { ansi: '\u001b[0m' } -} as const; - -export type McItemId = Lowercase<string>; -export type SbItemId = Uppercase<string>; -export type MojangJson = string; -export type SbRecipeItem = `${SbItemId}:${number}` | ''; -export type SbRecipe = { - [Location in `${'A' | 'B' | 'C'}${1 | 2 | 3}`]: SbRecipeItem; -}; -export type InfoType = 'WIKI_URL' | ''; - -export type Slayer = `${'WOLF' | 'BLAZE' | 'EMAN'}_${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`; - -export interface RawNeuItem { - itemid: McItemId; - displayname: string; - nbttag: MojangJson; - damage: number; - lore: string[]; - recipe?: SbRecipe; - internalname: SbItemId; - modver: string; - infoType: InfoType; - info?: string[]; - crafttext: string; - vanilla?: boolean; - useneucraft?: boolean; - slayer_req?: Slayer; - clickcommand?: string; - x?: number; - y?: number; - z?: number; - island?: string; - recipes?: { type: string; cost: any[]; result: SbItemId }[]; - /** @deprecated */ - parent?: SbItemId; - noseal?: boolean; -} - -export enum HideFlagsBits { - Enchantments = 1, - AttributeModifiers = 2, - Unbreakable = 4, - CanDestroy = 8, - CanPlaceOn = 16, - /** - * potion effects, shield pattern info, "StoredEnchantments", written book - * "generation" and "author", "Explosion", "Fireworks", and map tooltips - */ - OtherInformation = 32, - Dyed = 64 -} - -export type HideFlagsString = keyof typeof HideFlagsBits; - -export class HideFlags extends BitField<HideFlagsString> { - public static override Flags = HideFlagsBits; -} - -export const formattingCode = new RegExp( - `§[${Object.values(FormattingCodes) - .filter((v) => v.startsWith('§')) - .map((v) => v.substring(1)) - .join('')}]` -); - -export function removeMCFormatting(str: string) { - return str.replaceAll(formattingCode, ''); -} - -const repo = path.join(__dirname, '..', '..', '..', '..', '..', 'neu-item-repo-dangerous'); - -export interface NbtTag { - overrideMeta?: Byte; - Unbreakable?: Int; - ench?: string[]; - HideFlags?: HideFlags; - SkullOwner?: SkullOwner; - display?: NbtTagDisplay; - ExtraAttributes?: ExtraAttributes; -} - -export interface SkullOwner { - Id?: string; - Properties?: { - textures?: { Value?: string }[]; - }; -} - -export interface NbtTagDisplay { - Lore?: string[]; - color?: Int; - Name?: string; -} - -export type RuneId = string; - -export interface ExtraAttributes { - originTag?: Origin; - id?: string; - generator_tier?: Int; - boss_tier?: Int; - enchantments?: { hardened_mana?: Int }; - dungeon_item_level?: Int; - runes?: { [key: RuneId]: Int }; - petInfo?: PetInfo; -} - -export interface PetInfo { - type: 'ZOMBIE'; - active: boolean; - exp: number; - tier: 'COMMON' | 'UNCOMMON' | 'RARE' | 'EPIC' | 'LEGENDARY'; - hideInfo: boolean; - candyUsed: number; -} - -export type Origin = 'SHOP_PURCHASE'; - -const neuConstantsPath = path.join(repo, 'constants'); -const neuPetsPath = path.join(neuConstantsPath, 'pets.json'); -const neuPets = (await import(neuPetsPath, { assert: { type: 'json' } })) as PetsConstants; -const neuPetNumsPath = path.join(neuConstantsPath, 'petnums.json'); -const neuPetNums = (await import(neuPetNumsPath, { assert: { type: 'json' } })) as PetNums; - -export interface PetsConstants { - pet_rarity_offset: Record<string, number>; - pet_levels: number[]; - custom_pet_leveling: Record<string, { type: number; pet_levels: number[]; max_level: number }>; - pet_types: Record<string, string>; -} - -export interface PetNums { - [key: string]: { - [key: string]: { - '1': { - otherNums: number[]; - statNums: Record<string, number>; - }; - '100': { - otherNums: number[]; - statNums: Record<string, number>; - }; - 'stats_levelling_curve'?: `${number};${number};${number}`; - }; - }; -} - -export class NeuItem { - public itemId: McItemId; - public displayName: string; - public nbtTag: NbtTag; - public internalName: SbItemId; - public lore: string[]; - - public constructor(raw: RawNeuItem) { - this.itemId = raw.itemid; - this.nbtTag = <NbtTag>parse(raw.nbttag); - this.displayName = raw.displayname; - this.internalName = raw.internalname; - this.lore = raw.lore; - - this.petLoreReplacements(); - } - - private petLoreReplacements(level = -1) { - if (/.*?;[0-5]$/.test(this.internalName) && this.displayName.includes('LVL')) { - const maxLevel = neuPets?.custom_pet_leveling?.[this.internalName]?.max_level ?? 100; - this.displayName = this.displayName.replace('LVL', `1➡${maxLevel}`); - - const nums = neuPetNums[this.internalName]; - if (!nums) throw new Error(`Pet (${this.internalName}) has no pet nums.`); - - const teir = ['COMMON', 'UNCOMMON', 'RARE', 'EPIC', 'LEGENDARY', 'MYTHIC'][+this.internalName.at(-1)!]; - const petInfoTier = nums[teir]; - if (!petInfoTier) throw new Error(`Pet (${this.internalName}) has no pet nums for ${teir} rarity.`); - - const curve = petInfoTier?.stats_levelling_curve?.split(';'); - - // todo: finish copying from neu - - const minStatsLevel = parseInt(curve?.[0] ?? '0'); - const maxStatsLevel = parseInt(curve?.[0] ?? '100'); - - const lore = ''; - } - } -} - -export function mcToAnsi(str: string) { - for (const format in formattingInfo) { - str = str.replaceAll(format, formattingInfo[format as keyof typeof formattingInfo].ansi); - } - return `${str}\u001b[0m`; -} diff --git a/src/lib/common/util/Minecraft_Test.ts b/src/lib/common/util/Minecraft_Test.ts deleted file mode 100644 index 26ca648..0000000 --- a/src/lib/common/util/Minecraft_Test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import fs from 'fs/promises'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { mcToAnsi, RawNeuItem } from './Minecraft.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const repo = path.join(__dirname, '..', '..', '..', '..', '..', 'neu-item-repo-dangerous'); -const itemPath = path.join(repo, 'items'); -const items = await fs.readdir(itemPath); - -// for (let i = 0; i < 5; i++) { -for (const path_ of items) { - // const randomItem = items[Math.floor(Math.random() * items.length)]; - // console.log(randomItem); - const item = (await import(path.join(itemPath, /* randomItem */ path_), { assert: { type: 'json' } })).default as RawNeuItem; - if (/.*?((_MONSTER)|(_NPC)|(_ANIMAL)|(_MINIBOSS)|(_BOSS)|(_SC))$/.test(item.internalname)) continue; - if (!/.*?;[0-5]$/.test(item.internalname)) continue; - /* console.log(path_); - console.dir(item, { depth: Infinity }); */ - - /* console.log('==========='); */ - // const nbt = parse(item.nbttag) as NbtTag; - - // if (nbt?.SkullOwner?.Properties?.textures?.[0]?.Value) { - // nbt.SkullOwner.Properties.textures[0].Value = parse( - // Buffer.from(nbt.SkullOwner.Properties.textures[0].Value, 'base64').toString('utf-8') - // ) as string; - // } - - // if (nbt.ExtraAttributes?.petInfo) { - // nbt.ExtraAttributes.petInfo = JSON.parse(nbt.ExtraAttributes.petInfo as any as string); - // } - - // delete nbt.display?.Lore; - - // console.dir(nbt, { depth: Infinity }); - // console.log('==========='); - - /* if (nbt?.display && nbt.display.Name !== item.displayname) - console.log(`${path_} display name mismatch: ${mcToAnsi(nbt.display.Name)} != ${mcToAnsi(item.displayname)}`); - - if (nbt?.ExtraAttributes && nbt?.ExtraAttributes.id !== item.internalname) - console.log(`${path_} internal name mismatch: ${mcToAnsi(nbt?.ExtraAttributes.id)} != ${mcToAnsi(item.internalname)}`); */ - - // console.log('==========='); - - console.log(mcToAnsi(item.displayname)); - console.log(item.lore.map((l) => mcToAnsi(l)).join('\n')); - - /* const keys = [ - 'itemid', - 'displayname', - 'nbttag', - 'damage', - 'lore', - 'recipe', - 'internalname', - 'modver', - 'infoType', - 'info', - 'crafttext', - 'vanilla', - 'useneucraft', - 'slayer_req', - 'clickcommand', - 'x', - 'y', - 'z', - 'island', - 'recipes', - 'parent', - 'noseal' - ]; - - Object.keys(item).forEach((k) => { - if (!keys.includes(k)) throw new Error(`Unknown key: ${k}`); - }); - - if ( - 'slayer_req' in item && - !new Array(10).flatMap((_, i) => ['WOLF', 'BLAZE', 'EMAN'].map((e) => e + (i + 1)).includes(item.slayer_req!)) - ) - throw new Error(`Unknown slayer req: ${item.slayer_req!}`); */ - - /* console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); */ -} diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts deleted file mode 100644 index 60e32c0..0000000 --- a/src/lib/common/util/Moderation.ts +++ /dev/null @@ -1,556 +0,0 @@ -import { - ActivePunishment, - ActivePunishmentType, - baseMuteResponse, - colors, - emojis, - format, - Guild as GuildDB, - humanizeDuration, - ModLog, - permissionsResponse, - type ModLogType, - type ValueOf -} from '#lib'; -import assert from 'assert/strict'; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - Client, - EmbedBuilder, - PermissionFlagsBits, - type Guild, - type GuildMember, - type GuildMemberResolvable, - type GuildResolvable, - type Snowflake, - type UserResolvable -} from 'discord.js'; - -enum punishMap { - 'warned' = 'warn', - 'muted' = 'mute', - 'unmuted' = 'unmute', - 'kicked' = 'kick', - 'banned' = 'ban', - 'unbanned' = 'unban', - 'timedout' = 'timeout', - 'untimedout' = 'untimeout', - 'blocked' = 'block', - 'unblocked' = 'unblock' -} -enum reversedPunishMap { - 'warn' = 'warned', - 'mute' = 'muted', - 'unmute' = 'unmuted', - 'kick' = 'kicked', - 'ban' = 'banned', - 'unban' = 'unbanned', - 'timeout' = 'timedout', - 'untimeout' = 'untimedout', - 'block' = 'blocked', - 'unblock' = 'unblocked' -} - -/** - * Checks if a moderator can perform a moderation action on another user. - * @param moderator The person trying to perform the action. - * @param victim The person getting punished. - * @param type The type of punishment - used to format the response. - * @param checkModerator Whether or not to check if the victim is a moderator. - * @param force Override permissions checks. - * @returns `true` if the moderator can perform the action otherwise a reason why they can't. - */ -export async function permissionCheck( - moderator: GuildMember, - victim: GuildMember, - type: - | 'mute' - | 'unmute' - | 'warn' - | 'kick' - | 'ban' - | 'unban' - | 'add a punishment role to' - | 'remove a punishment role from' - | 'block' - | 'unblock' - | 'timeout' - | 'untimeout', - checkModerator = true, - force = false -): Promise<true | string> { - if (force) return true; - - // If the victim is not in the guild anymore it will be undefined - if ((!victim || !victim.guild) && !['ban', 'unban'].includes(type)) return true; - - if (moderator.guild.id !== victim.guild.id) { - throw new Error('moderator and victim not in same guild'); - } - - const isOwner = moderator.guild.ownerId === moderator.id; - if (moderator.id === victim.id && !type.startsWith('un')) { - return `${emojis.error} You cannot ${type} yourself.`; - } - if ( - moderator.roles.highest.position <= victim.roles.highest.position && - !isOwner && - !(type.startsWith('un') && moderator.id === victim.id) - ) { - return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as you do.`; - } - if ( - victim.roles.highest.position >= victim.guild.members.me!.roles.highest.position && - !(type.startsWith('un') && moderator.id === victim.id) - ) { - return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as I do.`; - } - if ( - checkModerator && - victim.permissions.has(PermissionFlagsBits.ManageMessages) && - !(type.startsWith('un') && moderator.id === victim.id) - ) { - if (await moderator.guild.hasFeature('modsCanPunishMods')) { - return true; - } else { - return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they are a moderator.`; - } - } - return true; -} - -/** - * Performs permission checks that are required in order to (un)mute a member. - * @param guild The guild to check the mute permissions in. - * @returns A {@link MuteResponse} or true if nothing failed. - */ -export async function checkMutePermissions( - guild: Guild -): Promise<ValueOf<typeof baseMuteResponse> | ValueOf<typeof permissionsResponse> | true> { - if (!guild.members.me!.permissions.has('ManageRoles')) return permissionsResponse.MISSING_PERMISSIONS; - const muteRoleID = await guild.getSetting('muteRole'); - if (!muteRoleID) return baseMuteResponse.NO_MUTE_ROLE; - const muteRole = guild.roles.cache.get(muteRoleID); - if (!muteRole) return baseMuteResponse.MUTE_ROLE_INVALID; - if (muteRole.position >= guild.members.me!.roles.highest.position || muteRole.managed) - return baseMuteResponse.MUTE_ROLE_NOT_MANAGEABLE; - - return true; -} - -/** - * Creates a modlog entry for a punishment. - * @param options Options for creating a modlog entry. - * @param getCaseNumber Whether or not to get the case number of the entry. - * @returns An object with the modlog and the case number. - */ -export async function createModLogEntry( - options: CreateModLogEntryOptions, - getCaseNumber = false -): Promise<{ log: ModLog | null; caseNum: number | null }> { - const user = (await options.client.utils.resolveNonCachedUser(options.user))!.id; - const moderator = (await options.client.utils.resolveNonCachedUser(options.moderator))!.id; - const guild = options.client.guilds.resolveId(options.guild)!; - - return createModLogEntrySimple( - { - ...options, - user: user, - moderator: moderator, - guild: guild - }, - getCaseNumber - ); -} - -/** - * Creates a modlog entry with already resolved ids. - * @param options Options for creating a modlog entry. - * @param getCaseNumber Whether or not to get the case number of the entry. - * @returns An object with the modlog and the case number. - */ -export async function createModLogEntrySimple( - options: SimpleCreateModLogEntryOptions, - getCaseNumber = false -): Promise<{ log: ModLog | null; caseNum: number | null }> { - // If guild does not exist create it so the modlog can reference a guild. - await GuildDB.findOrCreate({ - where: { id: options.guild }, - defaults: { id: options.guild } - }); - - const modLogEntry = ModLog.build({ - type: options.type, - user: options.user, - moderator: options.moderator, - reason: options.reason, - duration: options.duration ? options.duration : undefined, - guild: options.guild, - pseudo: options.pseudo ?? false, - evidence: options.evidence, - hidden: options.hidden ?? false - }); - const saveResult: ModLog | null = await modLogEntry.save().catch(async (e) => { - await options.client.utils.handleError('createModLogEntry', e); - return null; - }); - - if (!getCaseNumber) return { log: saveResult, caseNum: null }; - - const caseNum = ( - await ModLog.findAll({ where: { type: options.type, user: options.user, guild: options.guild, hidden: false } }) - )?.length; - return { log: saveResult, caseNum }; -} - -/** - * Creates a punishment entry. - * @param options Options for creating the punishment entry. - * @returns The database entry, or null if no entry is created. - */ -export async function createPunishmentEntry(options: CreatePunishmentEntryOptions): Promise<ActivePunishment | null> { - const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined; - const user = (await options.client.utils.resolveNonCachedUser(options.user))!.id; - const guild = options.client.guilds.resolveId(options.guild)!; - const type = findTypeEnum(options.type)!; - - const entry = ActivePunishment.build( - options.extraInfo - ? { user, type, guild, expires, modlog: options.modlog, extraInfo: options.extraInfo } - : { user, type, guild, expires, modlog: options.modlog } - ); - return await entry.save().catch(async (e) => { - await options.client.utils.handleError('createPunishmentEntry', e); - return null; - }); -} - -/** - * Destroys a punishment entry. - * @param options Options for destroying the punishment entry. - * @returns Whether or not the entry was destroyed. - */ -export async function removePunishmentEntry(options: RemovePunishmentEntryOptions): Promise<boolean> { - const user = await options.client.utils.resolveNonCachedUser(options.user); - const guild = options.client.guilds.resolveId(options.guild); - const type = findTypeEnum(options.type); - - if (!user || !guild) return false; - - let success = true; - - const entries = await ActivePunishment.findAll({ - // finding all cases of a certain type incase there were duplicates or something - where: options.extraInfo - ? { user: user.id, guild: guild, type, extraInfo: options.extraInfo } - : { user: user.id, guild: guild, type } - }).catch(async (e) => { - await options.client.utils.handleError('removePunishmentEntry', e); - success = false; - }); - if (entries) { - const promises = entries.map(async (entry) => - entry.destroy().catch(async (e) => { - await options.client.utils.handleError('removePunishmentEntry', e); - success = false; - }) - ); - - await Promise.all(promises); - } - return success; -} - -/** - * Returns the punishment type enum for the given type. - * @param type The type of the punishment. - * @returns The punishment type enum. - */ -function findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') { - const typeMap = { - ['mute']: ActivePunishmentType.MUTE, - ['ban']: ActivePunishmentType.BAN, - ['role']: ActivePunishmentType.ROLE, - ['block']: ActivePunishmentType.BLOCK - }; - return typeMap[type]; -} - -export function punishmentToPresentTense(punishment: PunishmentTypeDM): PunishmentTypePresent { - return punishMap[punishment]; -} - -export function punishmentToPastTense(punishment: PunishmentTypePresent): PunishmentTypeDM { - return reversedPunishMap[punishment]; -} - -/** - * Notifies the specified user of their punishment. - * @param options Options for notifying the user. - * @returns Whether or not the dm was successfully sent. - */ -export async function punishDM(options: PunishDMOptions): Promise<boolean> { - const ending = await options.guild.getSetting('punishmentEnding'); - const dmEmbed = - ending && ending.length && options.sendFooter - ? new EmbedBuilder().setDescription(ending).setColor(colors.newBlurple) - : undefined; - - const appealsEnabled = !!( - (await options.guild.hasFeature('punishmentAppeals')) && (await options.guild.getLogChannel('appeals')) - ); - - let content = `You have been ${options.punishment} `; - if (options.punishment.includes('blocked')) { - assert(options.channel); - content += `from <#${options.channel}> `; - } - content += `in ${format.input(options.guild.name)} `; - if (options.duration !== null && options.duration !== undefined) - content += options.duration ? `for ${humanizeDuration(options.duration)} ` : 'permanently '; - const reason = options.reason?.trim() ? options.reason?.trim() : 'No reason provided'; - content += `for ${format.input(reason)}.`; - - let components; - if (appealsEnabled && options.modlog) - components = [ - new ActionRowBuilder<ButtonBuilder>({ - components: [ - new ButtonBuilder({ - customId: `appeal;${punishmentToPresentTense(options.punishment)};${ - options.guild.id - };${options.client.users.resolveId(options.user)};${options.modlog}`, - style: ButtonStyle.Primary, - label: 'Appeal' - }).toJSON() - ] - }) - ]; - - const dmSuccess = await options.client.users - .send(options.user, { - content, - embeds: dmEmbed ? [dmEmbed] : undefined, - components - }) - .catch(() => false); - return !!dmSuccess; -} - -interface BaseCreateModLogEntryOptions extends BaseOptions { - /** - * The type of modlog entry. - */ - type: ModLogType; - - /** - * The reason for the punishment. - */ - reason: string | undefined | null; - - /** - * The duration of the punishment. - */ - duration?: number; - - /** - * Whether the punishment is a pseudo punishment. - */ - pseudo?: boolean; - - /** - * The evidence for the punishment. - */ - evidence?: string; - - /** - * Makes the modlog entry hidden. - */ - hidden?: boolean; -} - -/** - * Options for creating a modlog entry. - */ -export interface CreateModLogEntryOptions extends BaseCreateModLogEntryOptions { - /** - * The client. - */ - client: Client; - - /** - * The user that a modlog entry is created for. - */ - user: GuildMemberResolvable; - - /** - * The moderator that created the modlog entry. - */ - moderator: GuildMemberResolvable; - - /** - * The guild that the punishment is created for. - */ - guild: GuildResolvable; -} - -/** - * Simple options for creating a modlog entry. - */ -export interface SimpleCreateModLogEntryOptions extends BaseCreateModLogEntryOptions { - /** - * The user that a modlog entry is created for. - */ - user: Snowflake; - - /** - * The moderator that created the modlog entry. - */ - moderator: Snowflake; - - /** - * The guild that the punishment is created for. - */ - guild: Snowflake; -} - -/** - * Options for creating a punishment entry. - */ -export interface CreatePunishmentEntryOptions extends BaseOptions { - /** - * The type of punishment. - */ - type: 'mute' | 'ban' | 'role' | 'block'; - - /** - * The user that the punishment is created for. - */ - user: GuildMemberResolvable; - - /** - * The length of time the punishment lasts for. - */ - duration: number | undefined; - - /** - * The guild that the punishment is created for. - */ - guild: GuildResolvable; - - /** - * The id of the modlog that is linked to the punishment entry. - */ - modlog: string; - - /** - * Extra information for the punishment. The role for role punishments and the channel for blocks. - */ - extraInfo?: Snowflake; -} - -/** - * Options for removing a punishment entry. - */ -export interface RemovePunishmentEntryOptions extends BaseOptions { - /** - * The type of punishment. - */ - type: 'mute' | 'ban' | 'role' | 'block'; - - /** - * The user that the punishment is destroyed for. - */ - user: GuildMemberResolvable; - - /** - * The guild that the punishment was in. - */ - guild: GuildResolvable; - - /** - * Extra information for the punishment. The role for role punishments and the channel for blocks. - */ - extraInfo?: Snowflake; -} - -/** - * Options for sending a user a punishment dm. - */ -export interface PunishDMOptions extends BaseOptions { - /** - * The modlog case id so the user can make an appeal. - */ - modlog?: string; - - /** - * The guild that the punishment is taking place in. - */ - guild: Guild; - - /** - * The user that is being punished. - */ - user: UserResolvable; - - /** - * The punishment that the user has received. - */ - punishment: PunishmentTypeDM; - - /** - * The reason the user's punishment. - */ - reason?: string; - - /** - * The duration of the punishment. - */ - duration?: number; - - /** - * Whether or not to send the guild's punishment footer with the dm. - * @default true - */ - sendFooter: boolean; - - /** - * The channel that the user was (un)blocked from. - */ - channel?: Snowflake; -} - -interface BaseOptions { - /** - * The client. - */ - client: Client; -} - -export type PunishmentTypeDM = - | 'warned' - | 'muted' - | 'unmuted' - | 'kicked' - | 'banned' - | 'unbanned' - | 'timedout' - | 'untimedout' - | 'blocked' - | 'unblocked'; - -export type PunishmentTypePresent = - | 'warn' - | 'mute' - | 'unmute' - | 'kick' - | 'ban' - | 'unban' - | 'timeout' - | 'untimeout' - | 'block' - | 'unblock'; - -export type AppealButtonId = `appeal;${PunishmentTypePresent};${Snowflake};${Snowflake};${string}`; |