diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bot.ts | 46 | ||||
-rw-r--r-- | src/commands/moulberry-bush/neuRepo.ts | 47 | ||||
-rw-r--r-- | src/lib/common/util/Minecraft.ts | 303 | ||||
-rw-r--r-- | src/tasks/cache/updateNeuItemCache.ts | 12 |
4 files changed, 388 insertions, 20 deletions
@@ -1,23 +1,29 @@ -import { init } from './lib/utils/BushLogger.js'; -// creates proxies on console.log and console.warn -// also starts a REPL session -init(); +// import { init } from './lib/utils/BushLogger.js'; +// // creates proxies on console.log and console.warn +// // also starts a REPL session +// init(); -const { dirname } = await import('path'); -const { fileURLToPath } = await import('url'); -const { default: config } = await import('../config/options.js'); -const { Sentry } = await import('./lib/common/Sentry.js'); -const { BushClient } = await import('./lib/index.js'); - -const isDry = process.argv.includes('dry'); -if (!isDry && config.credentials.sentryDsn !== null) new Sentry(dirname(fileURLToPath(import.meta.url)) || process.cwd(), config); -BushClient.extendStructures(); -const client = new BushClient(config); -if (!isDry) await client.dbPreInit(); -await client.init(); -if (isDry) { - await client.destroy(); - process.exit(0); +// eslint-disable-next-line no-constant-condition +if (false) { + // const { dirname } = await import('path'); + // const { fileURLToPath } = await import('url'); + // const { default: config } = await import('../config/options.js'); + // const { Sentry } = await import('./lib/common/Sentry.js'); + // const { BushClient } = await import('./lib/index.js'); + // + // const isDry = process.argv.includes('dry'); + // if (!isDry && config.credentials.sentryDsn !== null) + // new Sentry(dirname(fileURLToPath(import.meta.url)) || process.cwd(), config); + // BushClient.extendStructures(); + // const client = new BushClient(config); + // if (!isDry) await client.dbPreInit(); + // await client.init(); + // if (isDry) { + // await client.destroy(); + // process.exit(0); + // } else { + // await client.start(); + // } } else { - await client.start(); + import('./lib/common/util/Minecraft.js'); } diff --git a/src/commands/moulberry-bush/neuRepo.ts b/src/commands/moulberry-bush/neuRepo.ts new file mode 100644 index 0000000..24f83ad --- /dev/null +++ b/src/commands/moulberry-bush/neuRepo.ts @@ -0,0 +1,47 @@ +import { BushCommand, clientSendAndPermCheck, type ArgType, type CommandMessage, type SlashMessage } from '#lib'; +import { ApplicationCommandOptionType, AutocompleteInteraction, CacheType, PermissionFlagsBits } from 'discord.js'; + +export default class NeuRepoCommand extends BushCommand { + public static items: { name: string; id: string }[] = []; + + public constructor() { + super('neuRepo', { + aliases: ['neu-repo', 'repo-item', 'neu-item', 'item-repo'], + category: "Moulberry's Bush", + description: 'Get information about an item from the NEU item repo.', + usage: ['neu-repo <item>'], + examples: ['neu-repo BARRIER'], + args: [ + { + id: 'item', + description: 'The item id that you would like to find neu item information about.', + type: 'string', + prompt: 'What SkyBlock item would you like to get information about?', + retry: '{error} Pick a valid SkyBlock item ID. Try using the slash command for a better experience.', + slashType: ApplicationCommandOptionType.String, + autocomplete: true + } + /* { + id: 'dangerous', + description: 'Whether or not to use the dangerous branch.', + prompt: 'Would you like to use the dangerous branch instead of the master branch?', + match: 'flag', + flag: ['--dangerous', '-d'], + default: false, + optional: true, + slashType: ApplicationCommandOptionType.Boolean + } */ + ], + slash: true, + clientPermissions: (m) => clientSendAndPermCheck(m, [PermissionFlagsBits.EmbedLinks], true), + userPermissions: [] + }); + } + + public override async exec( + message: CommandMessage | SlashMessage, + args: { item: ArgType<'string'> /* dangerous: ArgType<'flag'> */ } + ) {} + + public override async autocomplete(interaction: AutocompleteInteraction<CacheType>) {} +} diff --git a/src/lib/common/util/Minecraft.ts b/src/lib/common/util/Minecraft.ts new file mode 100644 index 0000000..fbacbf0 --- /dev/null +++ b/src/lib/common/util/Minecraft.ts @@ -0,0 +1,303 @@ +import { Byte, Int, parse } from '@ironm00n/nbt-ts'; +import { BitField } from 'discord.js'; +import fs from 'fs/promises'; +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: '#000000', background: '#000000', ansi: '\u001b[0;30m' }, + [FormattingCodes.DarkBlue]: { foreground: '#0000AA', background: '#00002A', ansi: '\u001b[0;34m' }, + [FormattingCodes.DarkGreen]: { foreground: '#00AA00', background: '#002A00', ansi: '\u001b[0;32m' }, + [FormattingCodes.DarkAqua]: { foreground: '#00AAAA', background: '#002A2A', ansi: '\u001b[0;36m' }, + [FormattingCodes.DarkRed]: { foreground: '#AA0000', background: '#2A0000', ansi: '\u001b[0;31m' }, + [FormattingCodes.DarkPurple]: { foreground: '#AA00AA', background: '#2A002A', ansi: '\u001b[0;35m' }, + [FormattingCodes.Gold]: { foreground: '#FFAA00', background: '#2A2A00', ansi: '\u001b[0;33m' }, + [FormattingCodes.Gray]: { foreground: '#AAAAAA', background: '#2A2A2A', ansi: '\u001b[0;37m' }, + [FormattingCodes.DarkGray]: { foreground: '#555555', background: '#151515', ansi: '\u001b[0;90m' }, + [FormattingCodes.Blue]: { foreground: '#5555FF', background: '#15153F', ansi: '\u001b[0;94m' }, + [FormattingCodes.Green]: { foreground: '#55FF55', background: '#153F15', ansi: '\u001b[0;92m' }, + [FormattingCodes.Aqua]: { foreground: '#55FFFF', background: '#153F3F', ansi: '\u001b[0;96m' }, + [FormattingCodes.Red]: { foreground: '#FF5555', background: '#3F1515', ansi: '\u001b[0;91m' }, + [FormattingCodes.LightPurple]: { foreground: '#FF55FF', background: '#3F153F', ansi: '\u001b[0;95m' }, + [FormattingCodes.Yellow]: { foreground: '#FFFF55', background: '#3F3F15', ansi: '\u001b[0;93m' }, + [FormattingCodes.White]: { foreground: '#FFFFFF', background: '#3F3F3F', 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' | ''; + +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'); +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 (item.displayname.includes('(Monster)') || item.displayname.includes('(NPC)')) continue; + /* console.log(path_); + console.dir(item, { depth: Infinity }); */ + + /* console.log('==========='); */ + const nbt = /* fn( */ parse(item.nbttag /* .replaceAll(/([0-9]{1,3}:)(["{])/g, '$2'), { useMaps: true } */) as any; /*); */ + console.dir(nbt, { depth: Infinity }); + console.log('==========='); + // console.dir((nbt.(item.nbttag))); + + /* // eslint-disable-next-line no-inner-declarations + function fn(map: TagMap) { + const ret = {} as any; + map.forEach((val, key) => { + function fn2(val: any): any { + if (val instanceof Map) return fn(val); + else if (Array.isArray(val)) return val.map((v) => fn2(v)); + else return val.valueOf(); + } + ret[key] = fn2(val); + }); + return ret; + } */ + + /* 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=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); */ +} + +interface NbtTag { + overrideMeta?: Byte; + Unbreakable?: Int; + ench?: string[]; + HideFlags?: HideFlags; + SkullOwner?: SkullOwner; + display?: NbtTagDisplay; + ExtraAttributes?: ExtraAttributes; +} + +interface SkullOwner { + Id?: string; + Properties?: { + textures?: { Value?: string }[]; + }; +} + +interface NbtTagDisplay { + Lore?: string[]; + Name?: string; +} + +type RuneId = string; + +interface ExtraAttributes { + originTag?: Origin; + id?: string; + generator_tier?: Int; + boss_tier?: Int; + enchantments?: { hardened_mana?: Int }; + dungeon_item_level?: Int; + runes?: { [key: RuneId]: Int }; +} + +type Origin = 'SHOP_PURCHASE'; + +class NeuItem { + public itemId: string; + public nbtTag: NbtTag; + + public constructor(raw: RawNeuItem) { + this.itemId = raw.itemid; + this.nbtTag = <NbtTag>parse(raw.nbttag); + } + + public get lore(): string { + return ''; + } + + private pet() {} +} + +function mcToAnsi(str: string) { + for (const format in formattingInfo) { + str = str.replaceAll(format, formattingInfo[format as keyof typeof formattingInfo].ansi); + } + return `${str}\u001b[0m`; +} + +const neuConstantsPath = path.join(repo, 'constants'); +const neuPetsPath = path.join(neuConstantsPath, 'pets.json'); +const neuPets = await import(neuPetsPath, { assert: { type: 'json' } }); + +// console.dir(await nbt.parse(buffer)); +/* console.dir( + await nbt.parse( + Buffer.from( + '{overrideMeta:1b,HideFlags:254,SkullOwner:{Id:"4173bc61-9e2f-3c84-8d31-4517e64062ab",Properties:{textures:[0:{Value:"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjNhYWY3YjFhNzc4OTQ5Njk2Y2I5OWQ0ZjA0YWQxYWE1MThjZWVlMjU2YzcyZTVlZDY1YmZhNWMyZDg4ZDllIn19fQ=="}]}},display:{Lore:[0:"§8Combat Pet",1:"",2:"§7Intelligence: §a{INTELLIGENCE}",3:"§7Strength: §a{STRENGTH}",4:"",5:"§6Rekindle",6:"§7§7Before death, become §eimmune",7:"§e§7and gain §c{0} §c❁ Strength",8:"§c§7for §a{1} §7seconds",9:"§83 minutes cooldown",10:"",11:"§6Fourth Flare",12:"§7§7On 4th melee strike, §6ignite",13:"§6§7mobs, dealing §c{4}x §7your",14:"§7§9☠ Crit Damage §7each second",15:"§7for §a{5} §7seconds",16:"",17:"§7§eRight-click to add this pet to",18:"§eyour pet menu!",19:"",20:"§5§lEPIC"],Name:"§f§f§7[Lvl {LVL}] §5Phoenix"},ExtraAttributes:{petInfo:"{\\"type\\":\\"PHOENIX\\",\\"active\\":false,\\"exp\\":0.0,\\"tier\\":\\"EPIC\\",\\"hideInfo\\":false}",id:"PHOENIX;3"},AttributeModifiers:[]}', + 'utf8' + ) + ) +); */ + +// import _ from 'lodash'; + +// const str = +// '{overrideMeta:1b,HideFlags:254,SkullOwner:{Id:"4173bc61-9e2f-3c84-8d31-4517e64062ab",Properties:{textures:[0:{Value:"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjNhYWY3YjFhNzc4OTQ5Njk2Y2I5OWQ0ZjA0YWQxYWE1MThjZWVlMjU2YzcyZTVlZDY1YmZhNWMyZDg4ZDllIn19fQ=="}]}},display:{Lore:[0:"§8Combat Pet",1:"",2:"§7Intelligence: §a{INTELLIGENCE}",3:"§7Strength: §a{STRENGTH}",4:"",5:"§6Rekindle",6:"§7§7Before death, become §eimmune",7:"§e§7and gain §c{0} §c❁ Strength",8:"§c§7for §a{1} §7seconds",9:"§83 minutes cooldown",10:"",11:"§6Fourth Flare",12:"§7§7On 4th melee strike, §6ignite",13:"§6§7mobs, dealing §c{4}x §7your",14:"§7§9☠ Crit Damage §7each second",15:"§7for §a{5} §7seconds",16:"",17:"§7§eRight-click to add this pet to",18:"§eyour pet menu!",19:"",20:"§5§lEPIC"],Name:"§f§f§7[Lvl {LVL}] §5Phoenix"},ExtraAttributes:{petInfo:"{\\"type\\":\\"PHOENIX\\",\\"active\\":false,\\"exp\\":0.0,\\"tier\\":\\"EPIC\\",\\"hideInfo\\":false}",id:"PHOENIX;3"},AttributeModifiers:[]}'.replaceAll( +// /([0-9]{1,3}:)(["{])/g, +// '$2' +// ); + +// console.log(str); + +// console.dir( +// _.chunk( +// str.split('').map((v, i) => [i, v]), +// 10 +// ).map((v) => [v[0][0], v.map((v) => v[1]).join('')]), +// { maxArrayLength: Infinity, compact: 100 } +// ); + +// console.dir(parse(str)); diff --git a/src/tasks/cache/updateNeuItemCache.ts b/src/tasks/cache/updateNeuItemCache.ts new file mode 100644 index 0000000..fff9e08 --- /dev/null +++ b/src/tasks/cache/updateNeuItemCache.ts @@ -0,0 +1,12 @@ +import { BushTask, Time } from '#lib'; + +export default class UpdatePriceItemCache extends BushTask { + public constructor() { + super('updatePriceItemCache', { + delay: 1 * Time.Hour, + runOnStart: true + }); + } + + public async exec() {} +} |