Nimbi

potato.ts

May 21st, 2022 (edited)
214
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const POTATO_LOTTERY_CHANNEL = 'xxxxxxxxxxxxxxxxxxxx'; // channel id
  2. const group = new discord.command.CommandGroup({
  3.     defaultPrefix: '!',
  4.     mentionPrefix: true,
  5.     additionalPrefixes: ['+', '-', '.'],
  6.   }),
  7.  
  8. const POTATO_LOTTERY_TIME_MINUTES = 5;
  9. const ALLOW_DAILY = true;
  10. const SHOP_ITEMS = {
  11.   'potato farmer': {
  12.     price: 1,
  13.     description: 'gives you the potato farmer role for 24h',
  14.     async onPurchase(user: discord.User) {
  15.       const guild = await discord.getGuild();
  16.       const member = await guild.getMember(user.id);
  17.       const role = await guild
  18.         .getRoles()
  19.         .then((roles) => roles.find((role) => role.name === 'potato farmer'));
  20.       if (!member || !role) throw new Error('invalid role or member');
  21.       await member.addRole(role.id);
  22.     },
  23.     async onExpire(user: discord.User) {
  24.       const guild = await discord.getGuild();
  25.       const member = await guild.getMember(user.id);
  26.       const role = await guild
  27.         .getRoles()
  28.         .then((roles) => roles.find((role) => role.name === 'potato farmer'));
  29.       if (!member || !role || !member.roles.includes(role.id)) return;
  30.  
  31.       await member.removeRole(role.id);
  32.     },
  33.     enabled: true,
  34.     duration: 24 * 60 * 60 * 1000, // 24 hours, checked in 5 minute intervals
  35.   },
  36. } as {
  37.   [key: string]: {
  38.     price: number;
  39.     duration: number | undefined;
  40.     description: string;
  41.     enabled: boolean;
  42.     onPurchase: Function;
  43.     onExpire: Function;
  44.   };
  45. };
  46. const MEDALS = [
  47.   discord.decor.Emojis.FIRST_PLACE_MEDAL,
  48.   discord.decor.Emojis.SECOND_PLACE_MEDAL,
  49.   discord.decor.Emojis.THIRD_PLACE_MEDAL,
  50. ];
  51.  
  52. const potatoKV = new pylon.KVNamespace('potato');
  53. const randomTimeBetween = (min: number, max: number) =>
  54.   Math.round(Math.random() * (max - min) + min);
  55. const nextDrawText = () => {
  56.   const nextDraw =
  57.     (Math.ceil(Date.now() / 1000 / 60 / POTATO_LOTTERY_TIME_MINUTES) *
  58.       1000 *
  59.       60 *
  60.       POTATO_LOTTERY_TIME_MINUTES -
  61.       Date.now()) /
  62.     1000 /
  63.     60;
  64.  
  65.   const minutes = Math.floor(nextDraw);
  66.   const seconds = Math.floor((nextDraw % 1) * 60);
  67.   return `next draw is in ${minutes} ${
  68.     minutes === 1 ? 'minute' : 'minutes'
  69.   } and ${seconds} ${seconds === 1 ? 'second' : 'seconds'}`;
  70. };
  71. const setDefaultReply = (commandGroup: discord.command.CommandGroup) => {
  72.   commandGroup.default(
  73.     () => ({}),
  74.     async (message) =>
  75.       await message.reply(
  76.         `${discord.decor.Emojis.NO_ENTRY} unknown potato command, try \`potato help\``
  77.       )
  78.   );
  79. };
  80.  
  81. discord.on(discord.Event.MESSAGE_CREATE, async (message: discord.Message) => {
  82.   if (!message.author || message.author.bot) return;
  83.  
  84.   if (await potatoKV.get<boolean>('cooldown')) {
  85.     if (message.content === discord.decor.Emojis.POTATO) {
  86.       const [lastChannel, potatoId] =
  87.         (await potatoKV.get<string>('lastPotato'))?.split('-') || [];
  88.       if (lastChannel !== message.channelId) return;
  89.  
  90.       await message
  91.         .getChannel()
  92.         .then((c) => c.getMessage(potatoId))
  93.         .then((m) => m?.delete())
  94.         .catch(() => {});
  95.  
  96.       await message.delete().catch(() => {});
  97.  
  98.       const poisonous = Math.random() < 0.01;
  99.  
  100.       const oldCount = (await potatoKV.get<number>(message.author.id)) || 0;
  101.       const newCount = Math.max(
  102.         0,
  103.         oldCount +
  104.           (poisonous
  105.             ? -Math.max(
  106.                 1,
  107.                 Math.min(10, Math.floor((Math.random() * oldCount) / 4))
  108.               )
  109.             : 1)
  110.       );
  111.  
  112.       await potatoKV.put(message.author.id, newCount);
  113.       await potatoKV.delete('lastPotato');
  114.       await message.reply(
  115.         new discord.Embed({
  116.           title: `${
  117.             poisonous ? discord.decor.Emojis.SKULL : discord.decor.Emojis.POTATO
  118.           } potato claimed ${discord.decor.Emojis.POTATO}`,
  119.           description: `${message.author.getTag()} ${
  120.             poisonous
  121.               ? `tried to pick up a poisonous potato, poisoning ${
  122.                   oldCount - newCount
  123.                 } potatos in the process`
  124.               : 'has claimed a potato'
  125.           }, and now holds onto ${newCount} potato${
  126.             newCount === 1 ? '' : 's'
  127.           }.`,
  128.           color: 0x11111c,
  129.           thumbnail: { url: message.author.getAvatarUrl() },
  130.           footer: {
  131.             text: poisonous
  132.               ? ''
  133.               : "to the rest of you, can't catch em all, right?",
  134.           },
  135.         })
  136.       );
  137.     }
  138.  
  139.     return;
  140.   } else {
  141.     const [lastChannel, potatoId] =
  142.       (await potatoKV.get<string>('lastPotato'))?.split('-') || [];
  143.  
  144.     await discord
  145.       .getGuild()
  146.       .then(
  147.         (g) =>
  148.           g.getChannel(lastChannel) as Promise<
  149.             discord.GuildTextChannel | undefined
  150.           >
  151.       )
  152.       .then((c) => c?.getMessage(potatoId))
  153.       .then((m) => m?.delete())
  154.       .catch(() => {});
  155.   }
  156.  
  157.   if (Math.random() > 0.3) return;
  158.  
  159.   const reply = await message.reply(discord.decor.Emojis.POTATO);
  160.  
  161.   const cooldown = randomTimeBetween(3 * 60 * 1000, 20 * 60 * 1000);
  162.  
  163.   await potatoKV.put('cooldown', true, { ttl: cooldown });
  164.   await potatoKV.put('lastPotato', `${message.channelId}-${reply.id}`);
  165. });
  166.  
  167. group.subcommand('potato', (subCommands) => {
  168.   setDefaultReply(subCommands);
  169.  
  170.   subCommands.on(
  171.     { name: 'help', description: 'potato help' },
  172.     () => ({}),
  173.     async (message) => {
  174.       await message.reply(
  175.         new discord.Embed({
  176.           title: `${discord.decor.Emojis.POTATO} help ${discord.decor.Emojis.POTATO}`,
  177.           description: [
  178.             `when a ${discord.decor.Emojis.POTATO} is dropped, be the first to pick it up by posting a ${discord.decor.Emojis.POTATO} too.`,
  179.             '',
  180.             '**commands**:',
  181.             '- `potato help` - shows this help message',
  182.             '- `potato` - show off your potato balance',
  183.             '- `potato inspect [user]` - inspect another [user]s potato balance',
  184.             '- `potato top [count]` - top n potato collectors',
  185.             '- `potato gamble <count>` - gamble <count> potatoes',
  186.             '- `potato steal <who> <count>` - steal potatoes from other people',
  187.             "- `potato give <who> <count>` - give your potatoes to other people - if you're feeling kind.",
  188.             '- `potato drop` - drop one of your potatoes. the fastest to pick it up gets it',
  189.             '- `potato daily` - claim your daily potato',
  190.             '- `potato don` - bet double or nothing',
  191.             '',
  192.             '- `potato lottery` - info about the current lottery pool',
  193.             '- `potato lottery deposit <count>` - deposit <count> potatoes into the lottery pool',
  194.             '',
  195.             '- `potato shop list` - list all available shop items',
  196.             '- `potato shop buy <item>` - buy <item> from the shop',
  197.           ].join('\n'),
  198.         })
  199.       );
  200.     }
  201.   );
  202.  
  203.   subCommands.on(
  204.     { name: '', description: 'potato count' },
  205.     (args) => ({}),
  206.     async (message, {}) => {
  207.       const target = message.author;
  208.  
  209.       const currentCount = (await potatoKV.get<number>(target.id)) || 0;
  210.       await message.reply(
  211.         new discord.Embed({
  212.           title: `${discord.decor.Emojis.POTATO} potato count ${discord.decor.Emojis.POTATO}`,
  213.           description: `${message.author.getTag()} has ${currentCount} potato${
  214.             currentCount === 1 ? '' : 's'
  215.           }. ${discord.decor.Emojis.POTATO.repeat(
  216.             Math.min(currentCount, 100)
  217.           )}`,
  218.           color: 0x11111c,
  219.           thumbnail: { url: message.author.getAvatarUrl() },
  220.         })
  221.       );
  222.     }
  223.   );
  224.  
  225.   subCommands.on(
  226.     { name: 'inspect', description: 'potato count' },
  227.     (args) => ({ who: args.user() }),
  228.     async (message, { who }) => {
  229.       const currentCount = (await potatoKV.get<number>(who.id)) || 0;
  230.       await message.reply(
  231.         new discord.Embed({
  232.           title: `${discord.decor.Emojis.POTATO} potato count ${discord.decor.Emojis.POTATO}`,
  233.           description: `${who.getTag()} has ${currentCount} potato${
  234.             currentCount === 1 ? '' : 's'
  235.           }. ${discord.decor.Emojis.POTATO.repeat(
  236.             Math.min(currentCount, 100)
  237.           )}`,
  238.           color: 0x11111c,
  239.           thumbnail: { url: who.getAvatarUrl() },
  240.         })
  241.       );
  242.     }
  243.   );
  244.  
  245.   subCommands.on(
  246.     {
  247.       name: 'don',
  248.       description: 'Double or Nothing!',
  249.       aliases: ['doubleornothing'],
  250.     },
  251.     () => ({}),
  252.     async (message) => {
  253.       if (await potatoKV.get<boolean>(`do-${message.author.id}`))
  254.         return await message.reply(
  255.           `You cant gamble (DoN) for atleast another 55-60 seconds ${discord.decor.Emojis.POTATO}${discord.decor.Emojis.FISHING_POLE_AND_FISH}`
  256.         );
  257.       (await potatoKV.get<number>(message.author?.id)) || 0;
  258.  
  259.       await potatoKV.put(`do-${message.author?.id}`, true, {
  260.         ttl: randomTimeBetween(55000, 60000),
  261.       });
  262.       const CurrentCount = (await potatoKV.get<number>(message.author.id)) || 0;
  263.       if (CurrentCount <= 0) {
  264.         message.reply(
  265.           'Your balance is lower than 0. Therefore you cant play DoN! :sob:'
  266.         );
  267.       } else {
  268.         const DoN = Math.ceil(Math.random() * 10);
  269.         if (DoN >= 5) {
  270.           const newCount = CurrentCount * 2;
  271.           message.reply(
  272.             new discord.Embed({
  273.               title: `You got Double! ${message.author.username}`,
  274.               description: `Old Balance: ${CurrentCount}\nNew Balance ${newCount}`,
  275.             })
  276.           );
  277.           await potatoKV.put(message.author?.id, newCount);
  278.         } else if (DoN < 5) {
  279.           const newCount = 0;
  280.           message.reply(
  281.             new discord.Embed({
  282.               title: `You got Nothing! ${message.author.username}`,
  283.               description: `Old Balance: ${CurrentCount}\nNew Balance ${newCount}`,
  284.             })
  285.           );
  286.           await potatoKV.put(message.author?.id, newCount);
  287.         }
  288.       }
  289.     }
  290.   );
  291.  
  292.   subCommands.on(
  293.     {
  294.       name: 'gamble',
  295.       description: 'gamble potatos',
  296.     },
  297.     (args) => ({ count: args.integer() }),
  298.     async (message, { count }) => {
  299.       if (await potatoKV.get<boolean>(`gamble-${message.author?.id}`))
  300.         return await message.reply(
  301.           `${discord.decor.Emojis.NO_ENTRY_SIGN} ${discord.decor.Emojis.POTATO} gambling addiction is a serious problem. Regulations require a wait.`
  302.         );
  303.  
  304.       const currentCount =
  305.         (await potatoKV.get<number>(message.author?.id)) || 0;
  306.  
  307.       if (count > currentCount)
  308.         return await message.reply(
  309.           'You can only gamble as many potatos as you have!'
  310.         );
  311.  
  312.       if (count > 10 || count < 1)
  313.         return await message.reply(
  314.           'You can only gamble between 1 and 10 potatos.'
  315.         );
  316.  
  317.       await potatoKV.put(`gamble-${message.author?.id}`, true, {
  318.         ttl: randomTimeBetween(2 * 60 * 1000, 5 * 60 * 1000),
  319.       });
  320.  
  321.       const won = Math.random() > 0.5;
  322.       const newCount = currentCount + count * (won ? 1 : -1);
  323.       await potatoKV.put(message.author?.id, newCount);
  324.  
  325.       await message.reply(
  326.         new discord.Embed({
  327.           title: `${discord.decor.Emojis.GAME_DIE} ${discord.decor.Emojis.POTATO} ${discord.decor.Emojis.GAME_DIE}`,
  328.           description: `Your gambling ${won ? 'paid off' : 'sucked'}, you ${
  329.             won ? 'won' : 'lost'
  330.           } ${count} potato${count === 1 ? '' : 's'}, ${
  331.             won ? 'giving you' : 'leaving you with'
  332.           } a total of ${newCount} potato${
  333.             newCount === 1 ? '' : 's'
  334.           }. ${discord.decor.Emojis.POTATO.repeat(newCount)} ${
  335.             won
  336.               ? discord.decor.Emojis.CHART_WITH_UPWARDS_TREND
  337.               : discord.decor.Emojis.CHART_WITH_DOWNWARDS_TREND
  338.           }`,
  339.           color: 0x11111c,
  340.         })
  341.       );
  342.     }
  343.   );
  344.  
  345.   subCommands.on(
  346.     { name: 'steal', description: 'steal potatos' },
  347.     (args) => ({ who: args.user(), count: args.integer() }),
  348.     async (message, { who, count }) => {
  349.       if (message.author?.id === who.id)
  350.         return await message.reply("You can't steal from yourself!");
  351.       if (await potatoKV.get<boolean>(`steal-${message.author?.id}`))
  352.         return await message.reply(
  353.           `${discord.decor.Emojis.POLICE_OFFICER} Your potato thief actions are being currently scrutinized. Lay low for a while.`
  354.         );
  355.       const success = Math.random() < 0.25;
  356.       const userPotatos = (await potatoKV.get<number>(message.author?.id)) || 0;
  357.       const targetPotatos = (await potatoKV.get<number>(who.id)) || 0;
  358.  
  359.       if (count > userPotatos)
  360.         return await message.reply(
  361.           'You can only steal as many potatos as you have!'
  362.         );
  363.  
  364.       if (count > targetPotatos)
  365.         return await message.reply('That user doesnt have that many potatos!');
  366.  
  367.       if (count < 1)
  368.         return await message.reply('You need to steal at least one potato.');
  369.  
  370.       if (count > 5)
  371.         return await message.reply(
  372.           'Your small hands can only carry 5 potatos!'
  373.         );
  374.  
  375.       await potatoKV.put(`steal-${message.author?.id}`, true, {
  376.         ttl: randomTimeBetween(3 * 60 * 1000, 10 * 60 * 1000),
  377.       });
  378.  
  379.       const newUserPotatos = userPotatos + count * (success ? 1 : -1);
  380.       const newTargetPotatos = targetPotatos + count * (success ? -1 : 1);
  381.  
  382.       await potatoKV.put(message.author?.id, newUserPotatos);
  383.       await potatoKV.put(who.id, newTargetPotatos);
  384.  
  385.       await message.reply(
  386.         new discord.Embed({
  387.           title: `${discord.decor.Emojis.GLOVES} ${discord.decor.Emojis.POTATO} ${discord.decor.Emojis.GLOVES}`,
  388.           description: `Your thievery ${success ? 'paid off' : 'sucked'}, you ${
  389.             success ? 'stole' : 'gave'
  390.           } ${count} potato${count === 1 ? '' : 's'} ${
  391.             success ? 'from' : 'to'
  392.           } ${who.getTag()}, ${
  393.             success ? 'giving you a total of' : 'leaving you with'
  394.           } ${newUserPotatos} potato${
  395.             newUserPotatos === 1 ? '' : 's'
  396.           }. ${discord.decor.Emojis.POTATO.repeat(newUserPotatos)} ${
  397.             success
  398.               ? discord.decor.Emojis.CHART_WITH_UPWARDS_TREND
  399.               : discord.decor.Emojis.CHART_WITH_DOWNWARDS_TREND
  400.           }`,
  401.           color: 0x11111c,
  402.         })
  403.       );
  404.     }
  405.   );
  406.  
  407.   subCommands.on(
  408.     { name: 'give', description: 'give potatos to other people' },
  409.     (args) => ({ who: args.user(), count: args.integerOptional() }),
  410.     async (message, { who, count }) => {
  411.       if (message.author?.id === who.id)
  412.         return await message.reply("You can't give potatos to yourself!");
  413.       if (who.bot)
  414.         return await message.reply("You can't give potatos to bots!");
  415.       const userPotatos = (await potatoKV.get<number>(message.author?.id)) || 0;
  416.       const targetPotatos = (await potatoKV.get<number>(who.id)) || 0;
  417.  
  418.       if (!count && count !== 0) count = 1;
  419.  
  420.       if (count > userPotatos)
  421.         return await message.reply(
  422.           'You can only give as many potatos as you have!'
  423.         );
  424.  
  425.       if (count < 1)
  426.         return await message.reply('You need to send at least one potato.');
  427.  
  428.       const newUserPotatos = userPotatos - count;
  429.       const newTargetPotatos = targetPotatos + count;
  430.  
  431.       await potatoKV.put(message.author?.id, newUserPotatos);
  432.       await potatoKV.put(who.id, newTargetPotatos);
  433.  
  434.       await message.reply(
  435.         `you gave ${count} potato${
  436.           count === 1 ? '' : 's'
  437.         } to ${who.getTag()}, how nice of you.`
  438.       );
  439.     }
  440.   );
  441.  
  442.   subCommands.on(
  443.     { name: 'top', description: 'top potatos' },
  444.     (args) => ({ count: args.integerOptional() }),
  445.     async (message, { count }) => {
  446.       count = Math.min(Math.max(3, count || 10), 20);
  447.       const items = await potatoKV.items();
  448.       const filtered = items.filter(
  449.         (entry) =>
  450.           !isNaN(entry.key as unknown as number) &&
  451.           (entry.value as unknown as number) > 0
  452.       );
  453.       const sorted = filtered.sort(
  454.         (a, b) => (b.value as number) - (a.value as number)
  455.       );
  456.       const top = sorted.slice(0, count);
  457.       count = top.length;
  458.       const userMap = await Promise.all(
  459.         top.map((entry) =>
  460.           discord
  461.             .getUser(entry.key)
  462.             .then((user) => ({ user, potatos: entry.value as number }))
  463.         )
  464.       );
  465.  
  466.       let description = `${discord.decor.Emojis.POTATO} **${filtered
  467.         .reduce((a, b) => a + (b.value as number), 0)
  468.         .toLocaleString()}**\n`;
  469.       description += `${discord.decor.Emojis.MAN_FARMER} **${filtered.length}**\n\n`;
  470.       description += `${discord.decor.Emojis.CHART_WITH_UPWARDS_TREND} **Ranks** ${discord.decor.Emojis.MUSCLE}\n`;
  471.  
  472.       for (const entry of userMap.slice(0, Math.max(3, count - 1))) {
  473.         const { user, potatos } = entry;
  474.         const place = userMap.indexOf(entry);
  475.         description += `\` ${
  476.           MEDALS[place] || `${(place + 1).toString().padStart(2, ' ')} `
  477.         } \` **${user?.username}**#${
  478.           user?.discriminator
  479.         } - ${potatos.toLocaleString()} potatos\n`;
  480.       }
  481.  
  482.       const ownIndex = sorted.findIndex(
  483.         (item) => item.key === message.author.id
  484.       );
  485.  
  486.       if (ownIndex >= count) {
  487.         description += `\` ... \` *${ownIndex - count + 1}* other farmers\n`;
  488.         description += `\` ${(ownIndex + 1).toString().padStart(2, ' ')} \` **${
  489.           message.author.username
  490.         }**#${message.author.discriminator} - ${sorted[ownIndex].value} potato${
  491.           sorted[ownIndex].value === 1 ? '' : 's'
  492.         }`;
  493.       } else if (count > 3) {
  494.         const { user, potatos } = userMap[count - 1];
  495.         description += `\` ${count.toString().padStart(2, ' ')}  \` **${
  496.           user?.username
  497.         }**#${user?.discriminator} - ${potatos.toLocaleString()} potatos\n`;
  498.       }
  499.  
  500.       await message.reply(
  501.         new discord.Embed({
  502.           title: `${discord.decor.Emojis.TROPHY} Leaderboard​ ${discord.decor.Emojis.CROWN}`,
  503.           description,
  504.         })
  505.       );
  506.     }
  507.   );
  508.  
  509.   subCommands.on(
  510.     { name: 'drop', description: 'drop a potato in the chat' },
  511.     () => ({}),
  512.     async (message) => {
  513.       const userPotatos = (await potatoKV.get<number>(message.author?.id)) || 0;
  514.  
  515.       if (!userPotatos)
  516.         return await message.reply("you don't have any potatos!");
  517.  
  518.       const lastPotato = await potatoKV.get<string>('lastPotato');
  519.       if (lastPotato)
  520.         return await message.reply(
  521.           `there is already an active potato waiting to be picked up in <#${
  522.             lastPotato.split('-')[0]
  523.           }>!`
  524.         );
  525.  
  526.       await potatoKV.put(message.author?.id, userPotatos - 1);
  527.  
  528.       const reply = await message.reply(discord.decor.Emojis.POTATO);
  529.  
  530.       const cooldown = randomTimeBetween(3 * 60 * 1000, 20 * 60 * 1000);
  531.  
  532.       await potatoKV.put('cooldown', true, { ttl: cooldown });
  533.       await potatoKV.put('lastPotato', `${message.channelId}-${reply.id}`, {
  534.         ttl: cooldown,
  535.       });
  536.     }
  537.   );
  538.  
  539.   subCommands.on(
  540.     {
  541.       name: 'modify',
  542.       description: 'modify a users potatos',
  543.     },
  544.     (args) => ({ who: args.user(), count: args.string() }),
  545.     async (message, { who, count }) => {
  546.       if (!(await discord.command.filters.isAdministrator().filter(message)))
  547.         return await message.reply('missing permissions');
  548.       if (who.bot)
  549.         return await message.reply(
  550.           'thats a.. bot. you wanna modify a bots potatos??'
  551.         );
  552.       const oldCount = (await potatoKV.get<number>(who.id)) || 0;
  553.  
  554.       let newCount = oldCount;
  555.       if (count.startsWith('+')) newCount += parseInt(count.replace('+', ''));
  556.       else if (count.startsWith('-'))
  557.         newCount -= parseInt(count.replace('-', ''));
  558.       else newCount = parseInt(count);
  559.  
  560.       if (isNaN(newCount as number))
  561.         return await message.reply('invalid count');
  562.  
  563.       await potatoKV.put(who.id, newCount as number);
  564.       await message.reply(
  565.         `Ok, updated ${who.getTag()}'s potatos to ${newCount}`
  566.      );
  567.    }
  568.  );
  569.  
  570.  if (ALLOW_DAILY)
  571.    subCommands.on(
  572.      { name: 'daily', description: 'daily potato' },
  573.      () => ({}),
  574.      async (message) => {
  575.        if (await potatoKV.get<boolean>(`daily-${message.author.id}`))
  576.          return await message.reply('you already claimed your daily potato!');
  577.  
  578.        await potatoKV.put(`daily-${message.author.id}`, true, {
  579.          ttl:
  580.            Math.ceil(Date.now() / 1000 / 60 / 60 / 24) * 24 * 60 * 60 * 1000 -
  581.            Date.now(),
  582.        });
  583.        const newCount = await potatoKV.transact(
  584.          message.author.id,
  585.          (prev: number | undefined) => (prev || 0) + 1
  586.        );
  587.        await message.reply(
  588.          `you claimed your daily potato, and now hold onto ${newCount} potatos.`
  589.        );
  590.      }
  591.    );
  592.  
  593.  const lottery = subCommands.subcommandGroup({
  594.    name: 'lottery',
  595.    description: 'potato lottery commands',
  596.  });
  597.  
  598.  setDefaultReply(lottery);
  599.  
  600.  lottery.on(
  601.    { name: '', description: 'pool info' },
  602.    () => ({}),
  603.    async (message) => {
  604.      const channel = await discord.getGuildTextChannel(POTATO_LOTTERY_CHANNEL);
  605.      if (!channel)
  606.        return await message.reply(
  607.          `${discord.decor.Emojis.X} sorry, the lottery has been prohibited by the authorities.`
  608.        );
  609.  
  610.      const lotteryData = ((await potatoKV.get('lottery')) || {}) as {
  611.        [key: string]: number;
  612.      };
  613.      await message.reply(
  614.        `${
  615.          Object.keys(lotteryData).length
  616.        } people are currently bidding a total of ${Object.values(
  617.          lotteryData
  618.        ).reduce((a, b) => a + b, 0)} potatos${
  619.          lotteryData[message.author.id]
  620.            ? `. you are in the pool with ${lotteryData[message.author.id]} ${
  621.                lotteryData[message.author.id] === 1 ? 'potato' : 'potatos'
  622.              }`
  623.            : ''
  624.        }. ${nextDrawText()}`
  625.      );
  626.    }
  627.  );
  628.  
  629.  lottery.on(
  630.    { name: 'deposit', description: 'deposits potatos into the lottery pool' },
  631.    (args) => ({ count: args.integer() }),
  632.    async (message, { count }) => {
  633.      const channel = await discord.getGuildTextChannel(POTATO_LOTTERY_CHANNEL);
  634.      if (!channel)
  635.        return await message.reply(
  636.          `${discord.decor.Emojis.X} sorry, the lottery has been prohibited by the authorities.`
  637.        );
  638.  
  639.      const currentCount =
  640.        (await potatoKV.get<number>(message.author?.id)) || 0;
  641.  
  642.      if (count > currentCount)
  643.        return await message.reply(
  644.          'You can only deposit as many potatos as you have!'
  645.        );
  646.  
  647.      if (count < 1)
  648.        return await message.reply('You need to deposit at least 1 potato.');
  649.  
  650.      await potatoKV.put(message.author?.id, currentCount - count);
  651.  
  652.      const lotteryData = await potatoKV.transact(
  653.        'lottery',
  654.        (prev: pylon.JsonObject | undefined) => {
  655.          const next = {} as { [key: string]: number };
  656.          if (prev)
  657.            for (const [key, value] of Object.entries(prev as object))
  658.              next[key] = value;
  659.  
  660.          next[message.author.id] =
  661.            (((prev && prev[message.author.id]) || 0) as number) + count;
  662.  
  663.          return next;
  664.        }
  665.      );
  666.  
  667.      const totalCount = Object.values(lotteryData as object).reduce(
  668.        (a, b) => a + b,
  669.        0
  670.      );
  671.      const gamblerCount = Object.keys(lotteryData as object).length;
  672.  
  673.      await message.reply(
  674.        `you deposited ${count} ${
  675.          count === 1 ? 'potato' : 'potatos'
  676.        }, there are ${totalCount} ${
  677.          totalCount === 1 ? 'potato' : 'potatos'
  678.        } from ${gamblerCount} ${
  679.          gamblerCount === 1 ? 'gambler' : 'gamblers'
  680.        } in the pool${
  681.          lotteryData![message.author.id] !== count
  682.            ? ` (${lotteryData![message.author.id]} of those are yours)`
  683.            : ''
  684.        }. ${nextDrawText()}`
  685.      );
  686.    }
  687.  );
  688.  
  689.  const shop = subCommands.subcommandGroup({
  690.    name: 'shop',
  691.    description: 'potato shop commands',
  692.  });
  693.  
  694.  setDefaultReply(shop);
  695.  
  696.  shop.on(
  697.    { name: 'list', aliases: [''], description: 'list all potato shop items' },
  698.    () => ({}),
  699.    async (message) => {
  700.      if (!Object.keys(SHOP_ITEMS).length)
  701.        return await message.reply('no items currently available, sorry!');
  702.  
  703.      const fields = await Promise.all(
  704.        Object.entries(SHOP_ITEMS)
  705.          .filter(([, item]) => item.enabled)
  706.          .map(async ([name, item]) => ({
  707.            name: `${name} - ${item.price} ${discord.decor.Emojis.POTATO}`,
  708.            value: item.description,
  709.            inline: true,
  710.          }))
  711.      );
  712.  
  713.      await message.reply(
  714.        new discord.Embed({
  715.          title: 'Potato Shop',
  716.          description: `**Available Items**\nuse \`potato shop buy <item>\` to purchase an item listed here`,
  717.          fields,
  718.        })
  719.      );
  720.    }
  721.  );
  722.  
  723.  shop.on(
  724.    {
  725.      name: 'buy',
  726.      aliases: ['purchase'],
  727.      description: 'purchase a potato shop item',
  728.    },
  729.    (args) => ({ item: args.text() }),
  730.    async (message, { item }) => {
  731.      const itemObj = SHOP_ITEMS[item];
  732.      if (!itemObj || !itemObj.enabled)
  733.        return await message.reply(
  734.          `invalid potato item. use \`potato shop list\` to get a list of all available items`
  735.        );
  736.  
  737.      const purchases = ((await potatoKV.get<pylon.JsonArray>('shop')) ||
  738.        []) as { user: string; item: string; expiresAt: number | undefined }[];
  739.      const purchase = purchases.find(
  740.        (purchase) =>
  741.          purchase.user === message.author.id && purchase.item === item
  742.      );
  743.      if (purchase)
  744.        return message.reply(
  745.          `You already bought this item!${
  746.            purchase.expiresAt
  747.              ? ` You can buy it again on ${new Date(
  748.                  purchase.expiresAt
  749.                ).toUTCString()}`
  750.              : ''
  751.          }`
  752.        );
  753.  
  754.      const userPotatos = (await potatoKV.get<number>(message.author.id)) || 0;
  755.      if (userPotatos < itemObj.price)
  756.        return await message.reply(
  757.          "you don't have enough potatos for that item!"
  758.        );
  759.  
  760.      try {
  761.        await itemObj.onPurchase(message.author);
  762.      } catch (err) {
  763.        return await message.reply(
  764.          `There was an error processing your order: ${err.message}`
  765.        );
  766.      }
  767.  
  768.      await potatoKV.transact(
  769.        message.author.id,
  770.        (prev: number | undefined) => (prev || 0) - itemObj.price
  771.      );
  772.  
  773.      await potatoKV.transact(
  774.        'shop',
  775.        (prev: pylon.JsonArray | undefined) =>
  776.          [
  777.            ...(prev || []),
  778.            {
  779.              user: message.author.id,
  780.              item,
  781.              expiresAt: itemObj.duration
  782.                ? Date.now() + itemObj.duration
  783.                : undefined,
  784.            },
  785.          ] as pylon.JsonArray
  786.      );
  787.  
  788.      await message.reply(`You successfully bought \`${item}\`!`);
  789.    }
  790.  );
  791. });
  792.  
  793. pylon.tasks.cron(
  794.  'lottery',
  795.  `0 0/${POTATO_LOTTERY_TIME_MINUTES} * * * * *`,
  796.  async () => {
  797.    const channel = await discord.getGuildTextChannel(
  798.      POTATO_LOTTERY_CHANNEL
  799.    );
  800.    if (!channel) return;
  801.  
  802.    let lotteryData = (await potatoKV.get('lottery')) as
  803.      | { [key: string]: number }
  804.      | undefined;
  805.    if (!lotteryData || Object.keys(lotteryData).length < 2) return;
  806.  
  807.    const msg = await channel.sendMessage(
  808.      `the potato gods are choosing a lottery winner...`
  809.    );
  810.    await sleep(Math.random() * 10000 + 5000);
  811.    await msg.delete().catch(() => {});
  812.  
  813.    lotteryData = (await potatoKV.get('lottery')) as
  814.      | { [key: string]: number }
  815.      | undefined;
  816.    if (!lotteryData || Object.keys(lotteryData).length < 2) return;
  817.  
  818.    const idList = [] as string[];
  819.    for (const [key, value] of Object.entries(lotteryData))
  820.      idList.push(...(new Array(value).fill(key) as string[]));
  821.    const randomID = idList[Math.floor(Math.random() * idList.length)];
  822.  
  823.    const newCount = await potatoKV.transact(
  824.      randomID,
  825.      (prev: number | undefined) => (prev || 0) + idList.length
  826.    );
  827.    await potatoKV.delete('lottery');
  828.    await channel.sendMessage(
  829.      `the potato gods have chosen <@${randomID}> as a lottery winner (${
  830.        Math.floor((lotteryData[randomID] / idList.length) * 1000) / 10
  831.      }% chance)! they won ${
  832.        idList.length
  833.      } potatos, giving them a total of ${newCount}.`
  834.    );
  835.  }
  836. );
  837.  
  838. pylon.tasks.cron('shop', '0 0/5 * * * * *', async () => {
  839.  const purchases = ((await potatoKV.get<pylon.JsonArray>('shop')) || []) as {
  840.    user: string;
  841.    item: string;
  842.    expiresAt: number | undefined;
  843.  }[];
  844.  
  845.  const newPurchases = [];
  846.  
  847.  for (const purchase of purchases) {
  848.    if (purchase.expiresAt && purchase.expiresAt <= Date.now()) {
  849.      const item = SHOP_ITEMS[purchase.item];
  850.      if (!item) continue;
  851.  
  852.      discord
  853.        .getUser(purchase.user)
  854.        .then((user) => item.onExpire(user))
  855.        .catch((err) => console.error(err));
  856.    } else newPurchases.push(purchase);
  857.  }
  858.  
  859.  await potatoKV.put('shop', newPurchases as pylon.JsonArray);
  860. });
  861.  
Add Comment
Please, Sign In to add comment