linked counters
This commit is contained in:
parent
4351e6dfad
commit
a0c9b12da0
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema
|
||||
.alterTable('counters', table => {
|
||||
table.integer('linkedItem');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema
|
||||
.alterTable('counters', table => {
|
||||
table.dropColumn('linkedItem');
|
||||
});
|
||||
};
|
|
@ -2,6 +2,7 @@ import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'disco
|
|||
import { Counter, CounterUserLink, db } from '../lib/db';
|
||||
import { counterAutocomplete, counterConfigs, findCounter, getCounterConfigRaw, getOptions, parseConfig, setCounterConfig, toStringConfig, updateCounter } from '../lib/counter';
|
||||
import { outdent } from 'outdent';
|
||||
import { formatItem, formatItems, getItem, itemAutocomplete } from '../lib/items';
|
||||
|
||||
function extendOption(t: string) {
|
||||
return {name: t, value: t};
|
||||
|
@ -9,7 +10,7 @@ function extendOption(t: string) {
|
|||
|
||||
const help = new Map([
|
||||
['message templates', outdent`
|
||||
When using \`messageTemplate\`, \`messageTemplateIncrease\` or \`messageTemplateDecrease\`, you are providing a **template string**.
|
||||
When using \`messageTemplate\`, \`messageTemplateIncrease\`, \`messageTemplateDecrease\`, \`messageTemplatePut\` or \`messageTemplateTake\`, you are providing a **template string**.
|
||||
A template string is a **specially-formatted** string with placeholder values. For instance, a template string like so:
|
||||
|
||||
> **%user** has %action the counter by **%amt**.
|
||||
|
@ -215,6 +216,25 @@ module.exports = {
|
|||
.setChoices(...[...help.keys()].map(extendOption))
|
||||
)
|
||||
)
|
||||
.addSubcommand(sub =>
|
||||
sub
|
||||
.setName('link')
|
||||
.setDescription('[ADMIN] THIS IS IRREVERSIBLE! Attach an item to this counter, letting you take or put items in.')
|
||||
.addStringOption(opt =>
|
||||
opt
|
||||
.setName('type')
|
||||
.setDescription('The counter to operate on')
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
)
|
||||
.addStringOption(opt =>
|
||||
opt
|
||||
.setName('item')
|
||||
.setDescription('The item')
|
||||
.setAutocomplete(true)
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.setDefaultMemberPermissions('0')
|
||||
.setDMPermission(false),
|
||||
|
||||
|
@ -233,10 +253,7 @@ module.exports = {
|
|||
try {
|
||||
counter = await findCounter(type, interaction.guildId!);
|
||||
} catch(err) {
|
||||
await interaction.followUp({
|
||||
content: 'No such counter!'
|
||||
});
|
||||
return;
|
||||
return interaction.followUp('No such counter!');
|
||||
}
|
||||
|
||||
if (subcommand === 'add') {
|
||||
|
@ -276,12 +293,7 @@ module.exports = {
|
|||
.where('producer', userType === 'producer')
|
||||
.first();
|
||||
|
||||
if (!link) {
|
||||
await interaction.followUp({
|
||||
content: `<@${user.id}> is not in the ${counter.emoji} **${userType}** allowlist!`
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!link) return interaction.followUp(`<@${user.id}> is not in the ${counter.emoji} **${userType}** allowlist!`);
|
||||
|
||||
await interaction.followUp({
|
||||
content: `<@${user.id}> has been removed from the ${counter.emoji} **${userType}** allowlist.`
|
||||
|
@ -350,15 +362,14 @@ module.exports = {
|
|||
try {
|
||||
counter = await findCounter(type, interaction.guildId!);
|
||||
} catch(err) {
|
||||
await interaction.followUp({
|
||||
content: 'No such counter!'
|
||||
});
|
||||
return;
|
||||
return interaction.followUp('No such counter!');
|
||||
}
|
||||
|
||||
const config = await getCounterConfigRaw(counter);
|
||||
const key = interaction.options.getString('key', true);
|
||||
const value = interaction.options.getString('value', true);
|
||||
|
||||
if (key === 'emoji' && counter.linkedItem) return interaction.followUp(`Cannot modify emoji - this counter is linked to ${formatItem(await getItem(counter.linkedItem))}`);
|
||||
|
||||
const defaultConfig = counterConfigs.get(key);
|
||||
if (!defaultConfig) return interaction.followUp(`No config named \`${key}\` exists!`);
|
||||
|
@ -366,7 +377,7 @@ module.exports = {
|
|||
const parsedValue = parseConfig(value, defaultConfig.type);
|
||||
const restringedValue = toStringConfig(parsedValue, defaultConfig.type);
|
||||
|
||||
await setCounterConfig(counter.id, key, restringedValue);
|
||||
await setCounterConfig(counter, key, restringedValue);
|
||||
|
||||
await interaction.followUp(`${counter.emoji} \`${key}\` is now \`${restringedValue}\`. (was \`${config.get(key) || toStringConfig(defaultConfig.default, defaultConfig.type)}\`)`);
|
||||
} else if (subcommand === 'delete') {
|
||||
|
@ -376,10 +387,7 @@ module.exports = {
|
|||
try {
|
||||
counter = await findCounter(type, interaction.guildId!);
|
||||
} catch(err) {
|
||||
await interaction.followUp({
|
||||
content: 'No such counter!'
|
||||
});
|
||||
return;
|
||||
return interaction.followUp('No such counter!');
|
||||
}
|
||||
|
||||
await db<Counter>('counters')
|
||||
|
@ -400,6 +408,32 @@ module.exports = {
|
|||
await interaction.followUp(counters.map(c => `${c.emoji} **${c.value}** <#${c.channel}>`).join('\n'));
|
||||
} else if (subcommand === 'help') {
|
||||
await interaction.followUp(help.get(interaction.options.getString('topic', true))!);
|
||||
} else if (subcommand === 'link') {
|
||||
const type = interaction.options.getString('type', true);
|
||||
const itemID = parseInt(interaction.options.getString('item', true));
|
||||
|
||||
let counter;
|
||||
try {
|
||||
counter = await findCounter(type, interaction.guildId!);
|
||||
} catch(err) {
|
||||
return interaction.followUp('No such counter!');
|
||||
}
|
||||
|
||||
const item = await getItem(itemID);
|
||||
if (!item) return interaction.followUp('No such item exists!');
|
||||
|
||||
await db<Counter>('counters')
|
||||
.where('id', counter.id)
|
||||
.update({
|
||||
'linkedItem': item.id,
|
||||
'emoji': item.emoji,
|
||||
'value': 0
|
||||
});
|
||||
|
||||
await setCounterConfig(counter, 'canIncrement', 'false');
|
||||
await setCounterConfig(counter, 'canDecrement', 'false');
|
||||
|
||||
await interaction.followUp(`Done. **The counter has been reset** to ${formatItems(item, 0)}. Users will not be able to take out or put in items until you enable this with \`canTake\` or \`canPut\`.\n\`canIncrement\` and \`canDecrement\` have also been **automatically disabled**, and you are recommended to keep them as such if you want to maintain balance in the universe.`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -409,6 +443,8 @@ module.exports = {
|
|||
|
||||
if (focused.name === 'type') {
|
||||
return counterAutocomplete(interaction);
|
||||
} else if (focused.name === 'item') {
|
||||
return itemAutocomplete(interaction);
|
||||
} else if (focused.name === 'value') {
|
||||
const type = interaction.options.getString('type', true);
|
||||
const counter = await findCounter(type, interaction.guildId!);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||
import { CustomItem, ItemInventory, db } from '../lib/db';
|
||||
import { behaviors, defaultItems, formatItems, getItem, getMaxStack } from '../lib/items';
|
||||
import { behaviors, formatItems, getItem, getMaxStack, giveItem, itemAutocomplete } from '../lib/items';
|
||||
|
||||
//function extendOption(t: string) {
|
||||
// return {name: t, value: t};
|
||||
|
@ -204,32 +204,9 @@ module.exports = {
|
|||
const item = await getItem(itemID);
|
||||
if (!item) return interaction.followUp('No such item exists!');
|
||||
|
||||
const storedItem = await db<ItemInventory>('itemInventories')
|
||||
.where('user', user.id)
|
||||
.where('item', itemID)
|
||||
.first();
|
||||
const inv = await giveItem(user.id, item, quantity);
|
||||
|
||||
let inv;
|
||||
if (storedItem) {
|
||||
inv = await db<ItemInventory>('itemInventories')
|
||||
.update({
|
||||
'quantity': db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)])
|
||||
})
|
||||
.limit(1)
|
||||
.where('user', user.id)
|
||||
.where('item', itemID)
|
||||
.returning('*');
|
||||
} else {
|
||||
inv = await db<ItemInventory>('itemInventories')
|
||||
.insert({
|
||||
'user': user.id,
|
||||
'item': Math.min(itemID, getMaxStack(item)),
|
||||
'quantity': quantity
|
||||
})
|
||||
.returning('*');
|
||||
}
|
||||
|
||||
await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv[0].quantity)}.`);
|
||||
await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv.quantity)}.`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -238,20 +215,7 @@ module.exports = {
|
|||
const focused = interaction.options.getFocused(true);
|
||||
|
||||
if (focused.name === 'item') {
|
||||
const customItems = await db<CustomItem>('customItems')
|
||||
.select('emoji', 'name', 'id')
|
||||
// @ts-expect-error this LITERALLY works
|
||||
.whereLike(db.raw('UPPER(name)'), `%${focused.value.toUpperCase()}%`)
|
||||
.where('guild', interaction.guildId!)
|
||||
.limit(25);
|
||||
|
||||
const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.value.toUpperCase()));
|
||||
|
||||
const items = [...foundDefaultItems, ...customItems];
|
||||
|
||||
await interaction.respond(
|
||||
items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() }))
|
||||
);
|
||||
return itemAutocomplete(interaction);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||
import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/counter';
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('put')
|
||||
.setDescription('Put an item from your inventory into the counter')
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('type')
|
||||
.setAutocomplete(true)
|
||||
.setDescription('The name of the counter')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('amount')
|
||||
.setRequired(false)
|
||||
.setDescription('Amount of items to put in')
|
||||
.setMinValue(1)
|
||||
)
|
||||
.setDMPermission(false),
|
||||
|
||||
execute: async (interaction: Interaction, member: GuildMember) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const amount = Math.trunc(interaction.options.getInteger('amount') || 1);
|
||||
const type = interaction.options.getString('type')!;
|
||||
|
||||
await interaction.deferReply({ephemeral: true});
|
||||
|
||||
changeLinkedCounterInteraction(interaction, member, amount, type);
|
||||
},
|
||||
|
||||
autocomplete: linkedCounterAutocomplete
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||
import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/counter';
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('take')
|
||||
.setDescription('Take an item from a counter')
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('type')
|
||||
.setAutocomplete(true)
|
||||
.setDescription('The name of the counter')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('amount')
|
||||
.setRequired(false)
|
||||
.setDescription('Amount of items to take')
|
||||
.setMinValue(1)
|
||||
)
|
||||
.setDMPermission(false),
|
||||
|
||||
execute: async (interaction: Interaction, member: GuildMember) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const amount = Math.trunc(interaction.options.getInteger('amount') || 1);
|
||||
const type = interaction.options.getString('type')!;
|
||||
|
||||
await interaction.deferReply({ephemeral: true});
|
||||
|
||||
changeLinkedCounterInteraction(interaction, member, -amount, type);
|
||||
},
|
||||
|
||||
autocomplete: linkedCounterAutocomplete
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction } from 'discord.js';
|
||||
import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction, User } from 'discord.js';
|
||||
import { getSign } from './util';
|
||||
import { Counter, CounterConfiguration, CounterUserLink, db } from './db';
|
||||
import { formatItems, getItem, getItemQuantity, getMaxStack, giveItem } from './items';
|
||||
|
||||
export async function getCounter(id: number) {
|
||||
const counter = await db<Counter>('counters')
|
||||
|
@ -83,11 +84,11 @@ export async function getCounterConfig(id: number, key: string) {
|
|||
return value;
|
||||
}
|
||||
|
||||
export async function setCounterConfig(id: number, option: string, value: string) {
|
||||
export async function setCounterConfig(counter: Counter, option: string, value: string) {
|
||||
// just the ugly way of life
|
||||
if (option === 'emoji') {
|
||||
if (option === 'emoji' && !counter.linkedItem) {
|
||||
await db<Counter>('counters')
|
||||
.where('id', id)
|
||||
.where('id', counter.id)
|
||||
.update({
|
||||
'emoji': value
|
||||
});
|
||||
|
@ -98,13 +99,13 @@ export async function setCounterConfig(id: number, option: string, value: string
|
|||
.update({
|
||||
value: value
|
||||
})
|
||||
.where('id', id)
|
||||
.where('id', counter.id)
|
||||
.where('configName', option);
|
||||
|
||||
if (updated === 0) {
|
||||
await db<CounterConfiguration>('counterConfigurations')
|
||||
.insert({
|
||||
'id': id,
|
||||
'id': counter.id,
|
||||
'configName': option,
|
||||
'value': value
|
||||
});
|
||||
|
@ -174,6 +175,30 @@ export const counterConfigs = new Map([
|
|||
type: ConfigType.String,
|
||||
default: 'null'
|
||||
}],
|
||||
['messageTemplateTake', {
|
||||
type: ConfigType.String,
|
||||
default: '**%user** has taken **%amt** from the counter.'
|
||||
}],
|
||||
['messageTemplatePut', {
|
||||
type: ConfigType.String,
|
||||
default: '**%user** has put **%amt** into the counter.'
|
||||
}],
|
||||
['canIncrement', {
|
||||
type: ConfigType.Bool,
|
||||
default: true
|
||||
}],
|
||||
['canDecrement', {
|
||||
type: ConfigType.Bool,
|
||||
default: true
|
||||
}],
|
||||
['canPut', {
|
||||
type: ConfigType.Bool,
|
||||
default: false
|
||||
}],
|
||||
['canTake', {
|
||||
type: ConfigType.Bool,
|
||||
default: false
|
||||
}],
|
||||
|
||||
// these ones are fake and are just stand-ins for values defined inside the actual counters table
|
||||
['emoji', {
|
||||
|
@ -210,7 +235,7 @@ export async function updateCounter(bot: Client, counter: Counter, value: number
|
|||
}
|
||||
}
|
||||
|
||||
export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, counter: Counter, value: number) {
|
||||
export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, counter: Counter, value: number, linked: boolean = false) {
|
||||
const channel = await bot.channels.fetch(counter.channel) as TextChannel;
|
||||
|
||||
let template = await getCounterConfig(counter.id, 'messageTemplate') as string;
|
||||
|
@ -218,6 +243,10 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de
|
|||
if (templateIncrease !== 'null' && delta > 0) template = templateIncrease;
|
||||
const templateDecrease = await getCounterConfig(counter.id, 'messageTemplateDecrease') as string;
|
||||
if (templateDecrease !== 'null' && delta < 0) template = templateDecrease;
|
||||
const templatePut = await getCounterConfig(counter.id, 'messageTemplatePut') as string;
|
||||
if (templatePut !== 'null' && delta > 0 && linked) template = templatePut;
|
||||
const templateTake = await getCounterConfig(counter.id, 'messageTemplateTake') as string;
|
||||
if (templateTake !== 'null' && delta < 0 && linked) template = templateTake;
|
||||
|
||||
const anonymous = await getCounterConfig(counter.id, 'anonymous') as boolean;
|
||||
|
||||
|
@ -226,7 +255,7 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de
|
|||
.setDescription(
|
||||
template
|
||||
.replaceAll('%user', anonymous ? 'someone' : member.toString())
|
||||
.replaceAll('%action', delta > 0 ? 'increased' : 'decreased')
|
||||
.replaceAll('%action', delta > 0 ? (linked ? 'put into' : 'increased') : (linked ? 'taken from' : 'decreased'))
|
||||
.replaceAll('%amt', Math.abs(delta).toString())
|
||||
.replaceAll('%total', value.toString())
|
||||
)
|
||||
|
@ -249,66 +278,107 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de
|
|||
});
|
||||
}
|
||||
|
||||
export async function changeCounterInteraction(interaction: CommandInteraction, member: GuildMember, amount: number, type: string) {
|
||||
try {
|
||||
const counter = await findCounter(type, member.guild.id);
|
||||
async function canUseCounter(user: User, counter: Counter, amount: number, isLinkedAction = false): Promise<boolean> {
|
||||
if (amount > 0 && !(await getCounterConfig(counter.id, isLinkedAction ? 'canPut' : 'canIncrement') as boolean)) return false;
|
||||
if (amount > 0 && counter.allowlistProducer) {
|
||||
const userLink = await db<CounterUserLink>('counterUserLink')
|
||||
.where('id', counter.id)
|
||||
.where('user', user.id)
|
||||
.where('producer', true)
|
||||
.first();
|
||||
|
||||
let canUse = true;
|
||||
if (amount > 0 && counter.allowlistProducer) {
|
||||
const userLink = await db<CounterUserLink>('counterUserLink')
|
||||
.where('id', counter.id)
|
||||
.where('user', member.id)
|
||||
.where('producer', true)
|
||||
.first();
|
||||
|
||||
if (!userLink) canUse = false;
|
||||
}
|
||||
if (amount < 0 && counter.allowlistConsumer) {
|
||||
const userLink = await db<CounterUserLink>('counterUserLink')
|
||||
.where('id', counter.id)
|
||||
.where('user', member.id)
|
||||
.where('producer', false)
|
||||
.first();
|
||||
|
||||
if (!userLink) canUse = false;
|
||||
}
|
||||
|
||||
if (!canUse) {
|
||||
await interaction.followUp({
|
||||
content: `You cannot **${amount > 0 ? 'produce' : 'consume'}** ${counter.emoji}.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newCount = await changeCounter(counter.id, amount);
|
||||
await updateCounter(interaction.client, counter, newCount);
|
||||
await announceCounterUpdate(interaction.client, member, amount, counter, newCount);
|
||||
await interaction.followUp({
|
||||
content: `${counter.emoji} **You have ${amount > 0 ? 'increased' : 'decreased'} the counter.**\n\`\`\`diff\n ${newCount - amount}\n${getSign(amount)}${Math.abs(amount)}\n ${newCount}\`\`\``
|
||||
});
|
||||
} catch(err) {
|
||||
await interaction.followUp({
|
||||
content: (err as Error).toString()
|
||||
});
|
||||
if (!userLink) return false;
|
||||
}
|
||||
if (amount < 0 && !(await getCounterConfig(counter.id, isLinkedAction ? 'canTake' : 'canDecrement') as boolean)) return false;
|
||||
if (amount < 0 && counter.allowlistConsumer) {
|
||||
const userLink = await db<CounterUserLink>('counterUserLink')
|
||||
.where('id', counter.id)
|
||||
.where('user', user.id)
|
||||
.where('producer', false)
|
||||
.first();
|
||||
|
||||
if (!userLink) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function counterAutocomplete(interaction: AutocompleteInteraction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const guild = interaction.guildId;
|
||||
function changeCounterInteractionBuilder(linked: boolean) {
|
||||
return async (interaction: CommandInteraction, member: GuildMember, amount: number, type: string) => {
|
||||
try {
|
||||
const counter = await findCounter(type, member.guild.id);
|
||||
if (linked && !counter.linkedItem) return interaction.followUp('There is no such linked counter!');
|
||||
|
||||
const query = db<Counter>('counters')
|
||||
.select('emoji', 'key')
|
||||
.whereLike('key', `%${focusedValue.toLowerCase()}%`)
|
||||
.limit(25);
|
||||
const canUse = await canUseCounter(member.user, counter, amount, linked);
|
||||
|
||||
if (guild) {
|
||||
query.where('guild', guild);
|
||||
}
|
||||
if (!canUse) {
|
||||
return interaction.followUp(`You cannot **${amount > 0 ? (linked ? 'put' : 'produce') : (linked ? 'take' : 'consume')}** ${counter.emoji}.`);
|
||||
}
|
||||
|
||||
const foundCounters = await query;
|
||||
let item;
|
||||
let newInv;
|
||||
if (linked) {
|
||||
const inv = await getItemQuantity(member.id, counter.linkedItem!);
|
||||
item = (await getItem(counter.linkedItem!))!;
|
||||
|
||||
await interaction.respond(
|
||||
foundCounters.map(choice => ({ name: choice.emoji, value: choice.key }))
|
||||
);
|
||||
}
|
||||
// change counter by -10 = increment own counter by 10
|
||||
const amtInv = -amount;
|
||||
const amtAbs = Math.abs(amtInv);
|
||||
|
||||
if (amtInv > getMaxStack(item)) {
|
||||
return interaction.followUp(`You cannot take ${formatItems(item, amtAbs)}, because the max stack size is ${getMaxStack(item)}x!`);
|
||||
}
|
||||
if ((inv.quantity + amtInv) > getMaxStack(item)) {
|
||||
return interaction.followUp(`You cannot take ${formatItems(item, amtAbs)}, because the max stack size is ${getMaxStack(item)}x and you already have ${inv.quantity}x!`);
|
||||
}
|
||||
if ((inv.quantity + amtInv) < 0) {
|
||||
return interaction.followUp(`You cannot put in ${formatItems(item, amtAbs)}, as you only have ${formatItems(item, inv.quantity)}!`);
|
||||
}
|
||||
|
||||
newInv = await giveItem(member.id, item, amtInv);
|
||||
}
|
||||
|
||||
const newCount = await changeCounter(counter.id, amount);
|
||||
await updateCounter(interaction.client, counter, newCount);
|
||||
await announceCounterUpdate(interaction.client, member, amount, counter, newCount, linked);
|
||||
await interaction.followUp({
|
||||
content: `${counter.emoji} **You have ${amount > 0 ? (linked ? 'put into' : 'increased') : (linked ? 'taken from' : 'decreased')} the counter.**\n\`\`\`diff\n ${newCount - amount}\n${getSign(amount)}${Math.abs(amount)}\n ${newCount}\`\`\`${newInv ? `\nYou now have ${formatItems(item, newInv.quantity)}.` : ''}`
|
||||
});
|
||||
} catch(err) {
|
||||
await interaction.followUp({
|
||||
content: (err as Error).toString()
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const changeCounterInteraction = changeCounterInteractionBuilder(false);
|
||||
export const changeLinkedCounterInteraction = changeCounterInteractionBuilder(true);
|
||||
|
||||
function counterAutocompleteBuilder(linked: boolean) {
|
||||
return async (interaction: AutocompleteInteraction) => {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const guild = interaction.guildId;
|
||||
|
||||
const query = db<Counter>('counters')
|
||||
.select('emoji', 'key')
|
||||
.whereLike('key', `%${focusedValue.toLowerCase()}%`)
|
||||
.limit(25);
|
||||
|
||||
if (guild) {
|
||||
query.where('guild', guild);
|
||||
}
|
||||
if (linked) {
|
||||
query.whereNotNull('linkedItem');
|
||||
}
|
||||
|
||||
const foundCounters = await query;
|
||||
|
||||
await interaction.respond(
|
||||
foundCounters.map(choice => ({ name: choice.emoji, value: choice.key }))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const counterAutocomplete = counterAutocompleteBuilder(false);
|
||||
export const linkedCounterAutocomplete = counterAutocompleteBuilder(true);
|
|
@ -33,7 +33,8 @@ export interface Counter {
|
|||
guild: string,
|
||||
message?: string,
|
||||
allowlistConsumer: boolean,
|
||||
allowlistProducer: boolean
|
||||
allowlistProducer: boolean,
|
||||
linkedItem?: number
|
||||
}
|
||||
export interface CounterUserLink {
|
||||
id: number,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { User } from 'discord.js';
|
||||
import { CustomItem, db } from './db';
|
||||
import { AutocompleteInteraction, User } from 'discord.js';
|
||||
import { CustomItem, ItemInventory, db } from './db';
|
||||
|
||||
type DefaultItem = Omit<CustomItem, 'guild'>; // uses negative IDs
|
||||
type Item = DefaultItem | CustomItem;
|
||||
|
@ -53,13 +53,74 @@ export async function getItem(id: number): Promise<Item | undefined> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getItemQuantity(user: string, itemID: number) {
|
||||
return (await db<ItemInventory>('itemInventories')
|
||||
.where('item', itemID)
|
||||
.where('user', user)
|
||||
.first())
|
||||
|| {
|
||||
'user': user,
|
||||
'item': itemID,
|
||||
'quantity': 0
|
||||
};
|
||||
}
|
||||
|
||||
export async function giveItem(user: string, item: Item, quantity = 1) {
|
||||
const storedItem = await db<ItemInventory>('itemInventories')
|
||||
.where('user', user)
|
||||
.where('item', item.id)
|
||||
.first();
|
||||
|
||||
let inv;
|
||||
if (storedItem) {
|
||||
inv = await db<ItemInventory>('itemInventories')
|
||||
.update({
|
||||
'quantity': db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)])
|
||||
})
|
||||
.limit(1)
|
||||
.where('user', user)
|
||||
.where('item', item.id)
|
||||
.returning('*');
|
||||
} else {
|
||||
inv = await db<ItemInventory>('itemInventories')
|
||||
.insert({
|
||||
'user': user,
|
||||
'item': Math.min(item.id, getMaxStack(item)),
|
||||
'quantity': quantity
|
||||
})
|
||||
.returning('*');
|
||||
}
|
||||
|
||||
return inv[0];
|
||||
}
|
||||
|
||||
export function getMaxStack(item: Item) {
|
||||
return item.type === 'weapon' ? 1 : item.maxStack;
|
||||
}
|
||||
|
||||
export function formatItem(item: Item) {
|
||||
export function formatItem(item: Item | undefined) {
|
||||
if (!item) return '? **MISSINGNO**';
|
||||
return `${item.emoji} **${item.name}**`;
|
||||
}
|
||||
export function formatItems(item: Item, quantity: number) {
|
||||
export function formatItems(item: Item | undefined, quantity: number) {
|
||||
return `${quantity}x ${formatItem(item)}`;
|
||||
}
|
||||
|
||||
export async function itemAutocomplete(interaction: AutocompleteInteraction) {
|
||||
const focused = interaction.options.getFocused();
|
||||
|
||||
const customItems = await db<CustomItem>('customItems')
|
||||
.select('emoji', 'name', 'id')
|
||||
// @ts-expect-error this LITERALLY works
|
||||
.whereLike(db.raw('UPPER(name)'), `%${focused.toUpperCase()}%`)
|
||||
.where('guild', interaction.guildId!)
|
||||
.limit(25);
|
||||
|
||||
const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase()));
|
||||
|
||||
const items = [...foundDefaultItems, ...customItems];
|
||||
|
||||
await interaction.respond(
|
||||
items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() }))
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue