mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Merge pull request #30 from Hades785/eco-bet
This commit is contained in:
		
						commit
						667389340d
					
				
					 4 changed files with 208 additions and 2 deletions
				
			
		|  | @ -3,6 +3,7 @@ import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils"; | ||||||
| import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; | import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./subcommands/eco-core"; | ||||||
| import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; | import {BuyCommand, ShopCommand} from "./subcommands/eco-shop"; | ||||||
| import {MondayCommand} from "./subcommands/eco-extras"; | import {MondayCommand} from "./subcommands/eco-extras"; | ||||||
|  | import {BetCommand} from "./subcommands/eco-bet"; | ||||||
| 
 | 
 | ||||||
| export default new Command({ | export default new Command({ | ||||||
|     description: "Economy command for Monika.", |     description: "Economy command for Monika.", | ||||||
|  | @ -16,7 +17,8 @@ export default new Command({ | ||||||
|         leaderboard: LeaderboardCommand, |         leaderboard: LeaderboardCommand, | ||||||
|         buy: BuyCommand, |         buy: BuyCommand, | ||||||
|         shop: ShopCommand, |         shop: ShopCommand, | ||||||
|         monday: MondayCommand |         monday: MondayCommand, | ||||||
|  |         bet: BetCommand | ||||||
|     }, |     }, | ||||||
|     user: new Command({ |     user: new Command({ | ||||||
|         description: "See how much money someone else has by using their user ID or pinging them.", |         description: "See how much money someone else has by using their user ID or pinging them.", | ||||||
|  |  | ||||||
							
								
								
									
										190
									
								
								src/commands/fun/subcommands/eco-bet.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/commands/fun/subcommands/eco-bet.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,190 @@ | ||||||
|  | import Command from "../../../core/command"; | ||||||
|  | import $ from "../../../core/lib"; | ||||||
|  | import {Storage} from "../../../core/structures"; | ||||||
|  | import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; | ||||||
|  | import {User} from "discord.js"; | ||||||
|  | 
 | ||||||
|  | export const BetCommand = new Command({ | ||||||
|  |     description: "Bet your Mons with other people.", | ||||||
|  |     usage: "<user> <amount> <duration>", | ||||||
|  |     run: "Who are you betting with?", | ||||||
|  |     user: new Command({ | ||||||
|  |         description: "User to bet with.", | ||||||
|  |         // handles missing amount argument
 | ||||||
|  |         async run({args, author, channel, guild}): Promise<any> { | ||||||
|  |             if (isAuthorized(guild, channel)) { | ||||||
|  |                 const target = args[0]; | ||||||
|  | 
 | ||||||
|  |                 // handle invalid target
 | ||||||
|  |                 if (target.id == author.id) | ||||||
|  |                     return channel.send("You can't bet Mons with yourself!"); | ||||||
|  |                 else if (target.bot && process.argv[2] !== "dev") | ||||||
|  |                     return channel.send("You can't bet Mons with a bot!"); | ||||||
|  | 
 | ||||||
|  |                 return channel.send("How much are you betting?"); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         number: new Command({ | ||||||
|  |             description: "Amount of Mons to bet.", | ||||||
|  |             // handles missing duration argument
 | ||||||
|  |             async run({args, author, channel, guild}): Promise<any> { | ||||||
|  |                 if (isAuthorized(guild, channel)) { | ||||||
|  |                     const sender = Storage.getUser(author.id); | ||||||
|  |                     const target = args[0] as User; | ||||||
|  |                     const receiver = Storage.getUser(target.id); | ||||||
|  |                     const amount = Math.floor(args[1]); | ||||||
|  | 
 | ||||||
|  |                     // handle invalid target
 | ||||||
|  |                     if (target.id == author.id) | ||||||
|  |                         return channel.send("You can't bet Mons with yourself!"); | ||||||
|  |                     else if (target.bot && process.argv[2] !== "dev") | ||||||
|  |                         return channel.send("You can't bet Mons with a bot!"); | ||||||
|  | 
 | ||||||
|  |                     // handle invalid amount
 | ||||||
|  |                     if (amount <= 0) | ||||||
|  |                         return channel.send("You must bet at least one Mon!"); | ||||||
|  |                     else if (sender.money < amount) | ||||||
|  |                         return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); | ||||||
|  |                     else if (receiver.money < amount) | ||||||
|  |                         return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); | ||||||
|  | 
 | ||||||
|  |                     return channel.send("How long until the bet ends?"); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             any: new Command({ | ||||||
|  |                 description: "Duration of the bet.", | ||||||
|  |                 async run({client, args, author, message, channel, guild, askYesOrNo}): Promise<any> { | ||||||
|  |                     if (isAuthorized(guild, channel)) { | ||||||
|  |                         // [Pertinence to make configurable on the fly.]
 | ||||||
|  |                         // Lower and upper bounds for bet
 | ||||||
|  |                         const durationBounds = { min:"1m", max:"1d" }; | ||||||
|  | 
 | ||||||
|  |                         const sender = Storage.getUser(author.id); | ||||||
|  |                         const target = args[0] as User; | ||||||
|  |                         const receiver = Storage.getUser(target.id); | ||||||
|  |                         const amount = Math.floor(args[1]); | ||||||
|  |                         const duration = parseDuration(args[2].trim()); | ||||||
|  | 
 | ||||||
|  |                         // handle invalid target
 | ||||||
|  |                         if (target.id == author.id) | ||||||
|  |                             return channel.send("You can't bet Mons with yourself!"); | ||||||
|  |                         else if (target.bot && process.argv[2] !== "dev") | ||||||
|  |                             return channel.send("You can't bet Mons with a bot!"); | ||||||
|  | 
 | ||||||
|  |                         // handle invalid amount
 | ||||||
|  |                         if (amount <= 0) | ||||||
|  |                             return channel.send("You must bet at least one Mon!"); | ||||||
|  |                         else if (sender.money < amount) | ||||||
|  |                             return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); | ||||||
|  |                         else if (receiver.money < amount) | ||||||
|  |                             return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); | ||||||
|  | 
 | ||||||
|  |                         // handle invalid duration
 | ||||||
|  |                         if (duration <= 0) | ||||||
|  |                             return channel.send("Invalid bet duration"); | ||||||
|  |                         else if (duration <= parseDuration(durationBounds.min)) | ||||||
|  |                             return channel.send(`Bet duration is too short, maximum duration is ${durationBounds.min}`); | ||||||
|  |                         else if (duration >= parseDuration(durationBounds.max)) | ||||||
|  |                             return channel.send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); | ||||||
|  | 
 | ||||||
|  |                         // Ask target whether or not they want to take the bet.
 | ||||||
|  |                         const takeBet = await askYesOrNo( | ||||||
|  |                             await channel.send(`<@${target.id}>, do you want to take this bet of ${$(amount).pluralise("Mon", "s")}`), | ||||||
|  |                             target.id | ||||||
|  |                         ); | ||||||
|  | 
 | ||||||
|  |                         if (takeBet) { | ||||||
|  |                             // [MEDIUM PRIORITY: bet persistence to prevent losses in case of shutdown.]
 | ||||||
|  |                             // Remove amount money from both parts at the start to avoid duplication of money.
 | ||||||
|  |                             sender.money -= amount; | ||||||
|  |                             receiver.money -= amount; | ||||||
|  |                             // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code.
 | ||||||
|  |                             sender.ecoBetInsurance += amount; | ||||||
|  |                             receiver.ecoBetInsurance += amount; | ||||||
|  |                             Storage.save(); | ||||||
|  | 
 | ||||||
|  |                             // Notify both users.
 | ||||||
|  |                             await channel.send(`<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${$(amount).pluralise("Mon", "s")} has been deducted from each of them.`); | ||||||
|  | 
 | ||||||
|  |                             // Wait for the duration of the bet.
 | ||||||
|  |                             client.setTimeout(async () => { | ||||||
|  |                                 // In debug mode, saving the storage will break the references, so you have to redeclare sender and receiver for it to actually save.
 | ||||||
|  |                                 const sender = Storage.getUser(author.id); | ||||||
|  |                                 const receiver = Storage.getUser(target.id); | ||||||
|  |                                 // [TODO: when D.JSv13 comes out, inline reply to clean up.]
 | ||||||
|  |                                 // When bet is over, give a vote to ask people their thoughts.
 | ||||||
|  |                                 const voteMsg = await channel.send(`VOTE: do you think that <@${target.id}> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${message.id}`); | ||||||
|  |                                 await voteMsg.react("✅"); | ||||||
|  |                                 await voteMsg.react("❌"); | ||||||
|  | 
 | ||||||
|  |                                 // Filter reactions to only collect the pertinent ones.
 | ||||||
|  |                                 voteMsg.awaitReactions( | ||||||
|  |                                     (reaction, user) => { | ||||||
|  |                                         return ["✅", "❌"].includes(reaction.emoji.name); | ||||||
|  |                                     }, | ||||||
|  |                                     // [Pertinence to make configurable on the fly.]
 | ||||||
|  |                                     { time: parseDuration("2m") } | ||||||
|  |                                 ).then(reactions => { | ||||||
|  |                                     // Count votes
 | ||||||
|  |                                     const okReaction = reactions.get("✅"); | ||||||
|  |                                     const noReaction = reactions.get("❌"); | ||||||
|  |                                     const ok = okReaction ? (okReaction.count ?? 1) - 1 : 0; | ||||||
|  |                                     const no = noReaction ? (noReaction.count ?? 1) - 1 : 0; | ||||||
|  | 
 | ||||||
|  |                                     if (ok > no) { | ||||||
|  |                                         receiver.money += amount * 2; | ||||||
|  |                                         channel.send(`By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.`); | ||||||
|  |                                     } | ||||||
|  |                                     else if (ok < no) { | ||||||
|  |                                         sender.money += amount * 2; | ||||||
|  |                                         channel.send(`By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.`); | ||||||
|  |                                     } | ||||||
|  |                                     else { | ||||||
|  |                                         sender.money += amount; | ||||||
|  |                                         receiver.money += amount; | ||||||
|  |                                         channel.send(`By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.`); | ||||||
|  |                                     } | ||||||
|  |                                     sender.ecoBetInsurance -= amount; | ||||||
|  |                                     receiver.ecoBetInsurance -= amount; | ||||||
|  |                                     Storage.save(); | ||||||
|  |                                 }); | ||||||
|  |                             }, duration); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                             await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }) | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Parses a duration string into milliseconds | ||||||
|  |  * Examples: | ||||||
|  |  * - 3d -> 3 days    -> 259200000ms | ||||||
|  |  * - 2h -> 2 hours   -> 7200000ms | ||||||
|  |  * - 7m -> 7 minutes -> 420000ms | ||||||
|  |  * - 3s -> 3 seconds -> 3000ms | ||||||
|  |  */ | ||||||
|  | function parseDuration(duration: string): number { | ||||||
|  |     // extract last char as unit
 | ||||||
|  |     const unit = duration[duration.length - 1].toLowerCase(); | ||||||
|  |     // get the rest as value
 | ||||||
|  |     let value: number = +duration.substring(0, duration.length - 1); | ||||||
|  | 
 | ||||||
|  |     if (!["d","h","m","s"].includes(unit) || isNaN(value)) | ||||||
|  |         return 0; | ||||||
|  | 
 | ||||||
|  |     if (unit === "d") | ||||||
|  |         value *= 86400000; // 1000ms * 60s * 60m * 24h
 | ||||||
|  |     else if (unit === "h") | ||||||
|  |         value *= 3600000; // 1000ms * 60s * 60m
 | ||||||
|  |     else if (unit === "m") | ||||||
|  |         value *= 60000; // 1000ms * 60s
 | ||||||
|  |     else if (unit === "s") | ||||||
|  |         value *= 1000; // 1000ms
 | ||||||
|  | 
 | ||||||
|  |     return value; | ||||||
|  | } | ||||||
|  | @ -31,6 +31,7 @@ class User { | ||||||
|     public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone
 |     public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone
 | ||||||
|     public daylightSavingsRegion: "na" | "eu" | "sh" | null; |     public daylightSavingsRegion: "na" | "eu" | "sh" | null; | ||||||
|     public todoList: {[timestamp: string]: string}; |     public todoList: {[timestamp: string]: string}; | ||||||
|  |     public ecoBetInsurance: number; | ||||||
| 
 | 
 | ||||||
|     constructor(data?: GenericJSON) { |     constructor(data?: GenericJSON) { | ||||||
|         this.money = select(data?.money, 0, Number); |         this.money = select(data?.money, 0, Number); | ||||||
|  | @ -50,6 +51,7 @@ class User { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         this.ecoBetInsurance = select(data?.ecoBetInsurance, 0, Number); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import Event from "../core/event"; | import Event from "../core/event"; | ||||||
| import {client} from "../index"; | import {client} from "../index"; | ||||||
| import $ from "../core/lib"; | import $ from "../core/lib"; | ||||||
| import {Config} from "../core/structures"; | import {Config, Storage} from "../core/structures"; | ||||||
| import {updateGlobalEmoteRegistry} from "../core/lib"; | import {updateGlobalEmoteRegistry} from "../core/lib"; | ||||||
| 
 | 
 | ||||||
| export default new Event<"ready">({ | export default new Event<"ready">({ | ||||||
|  | @ -16,5 +16,17 @@ export default new Event<"ready">({ | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|         updateGlobalEmoteRegistry(); |         updateGlobalEmoteRegistry(); | ||||||
|  | 
 | ||||||
|  |         // Run this setup block once to restore eco bet money in case the bot went down. (And I guess search the client for those users to let them know too.)
 | ||||||
|  |         for (const id in Storage.users) { | ||||||
|  |             const user = Storage.users[id]; | ||||||
|  | 
 | ||||||
|  |             if(user.ecoBetInsurance > 0) { | ||||||
|  |                 client.users.cache.get(id)?.send(`Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.ecoBetInsurance}\`, has been restored.`); | ||||||
|  |                 user.money += user.ecoBetInsurance; | ||||||
|  |                 user.ecoBetInsurance = 0; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Storage.save(); | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue