commit 5da3b3de02e13a9d591967fbf42f074ae0725e3a Author: Brazman Date: Sat Sep 10 14:56:21 2022 -0500 Initial Commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfcfd56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,350 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8b4c81 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +
+
+ + Logo + + +

SuperMachoBot

+ +

+ A Discord bot I made for fun with DSharpPlus! Includes D&D stat rolling, an economy system, and other cool stuff! +

+
+ +# Usage +Specify your bot token and discord User ID in config.json and run! diff --git a/SuperMachoBot.sln b/SuperMachoBot.sln new file mode 100644 index 0000000..40a3855 --- /dev/null +++ b/SuperMachoBot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperMachoBot", "SuperMachoBot\SuperMachoBot.csproj", "{6C66DD74-8188-4568-A9B6-A16A4A47F9E4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6C66DD74-8188-4568-A9B6-A16A4A47F9E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C66DD74-8188-4568-A9B6-A16A4A47F9E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C66DD74-8188-4568-A9B6-A16A4A47F9E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C66DD74-8188-4568-A9B6-A16A4A47F9E4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4D51C0D9-B4D1-4F4B-82E0-2B4EF0817B0C} + EndGlobalSection +EndGlobal diff --git a/SuperMachoBot/Commands/GeneralCommands.cs b/SuperMachoBot/Commands/GeneralCommands.cs new file mode 100644 index 0000000..50c43b6 --- /dev/null +++ b/SuperMachoBot/Commands/GeneralCommands.cs @@ -0,0 +1,191 @@ +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus.Entities; +using Newtonsoft.Json.Linq; + +namespace SuperMachoBot.Commands +{ + public class GeneralCommands : BaseCommandModule + { + public bool playMode = false; + + public static Random random = new Random(); + [Description("Sends a wholesome personalized greeting.")] + [Command("greet")] + public async Task GreetCommand(CommandContext ctx, [RemainingText] string name) + { + await ctx.RespondAsync($"Greetings, {name}! test"); + } + [Description("Generate number between a specified minimum and maximum.")] + [Command("random")] + public async Task RandomCommand(CommandContext ctx, int min, int max) + { + var random = new Random(); + await ctx.RespondAsync($"Your number is: {random.Next(min, max)}"); + } + [Description("Ping specified user after a specified amount of time with a specified message.... specifically.")] + [Command("reminder")] + public async Task WaitCommand(CommandContext ctx, [Description("How long to wait")] float waittime, [Description("Unit of time")] string unit, [Description("User to ping")] DiscordUser user, [Description("Reminder message")][RemainingText] string remindmessage) + { + float delay = 10; //have to assign a dummy number at first because compiler is stupid + bool unitValid = true; + switch (unit) + { + case "seconds": + case "second": + delay = 1000 * waittime; + break; + case "minutes": + case "minute": + delay = 60000 * waittime; + break; + case "hours": + case "hour": + delay = 3600000 * waittime; + break; + default: + await ctx.RespondAsync("THATS NOT A VALID UNIT OF TIME!! https://tenor.com/view/soyjak-gif-23540164"); + unitValid = false; + break; + + } + if (unitValid == true) + { + int sleepDelay = (int)delay; + await ctx.RespondAsync($"Will ping in {waittime} {unit}"); + Thread.Sleep(sleepDelay); + await ctx.RespondAsync($"{remindmessage}{user.Mention}"); + } + } + [Description("Rolls dice with modifier one at a time")] + [Command("sroll")] + public async Task DebugCommand(CommandContext ctx, string dice, float modifier = 0) + { + var splitdice = dice.Split('d'); + + if (Int32.TryParse(splitdice[0], out int amount)) + { + + } + + for (int i = 0; i < amount; i++) + { + Random rnd = new Random(); + if (Int32.TryParse(splitdice[1], out int sides)) + { + float diceroll = rnd.Next(1, sides + 1); + await ctx.RespondAsync($"rolled {diceroll} + {modifier} = {diceroll + modifier}"); + } + else + { + await ctx.RespondAsync("Invalid dice!"); + } + } + } + Random rnd = new Random(); + [Description("Rolls dice the specified amount of times, then adds the modifier onto the total")] + [Command("mroll")] + public async Task MultipleRollCommand(CommandContext ctx, string dice, float modifier = 0) + { + //seperate the letter 'd' from dice + var splitdice = dice.Split('d'); + //check if the first part is a number + if (Int32.TryParse(splitdice[0], out int amount)) + { + //check if the second part is a number + if (Int32.TryParse(splitdice[1], out int sides)) + { + //roll the dice + float total = 0; + for (int i = 0; i < amount; i++) + { + float diceroll = rnd.Next(1, sides + 1); + total += diceroll; + } + //add the modifier + total += modifier; + //send the result + await ctx.RespondAsync($"{total}"); + } + else + { + await ctx.RespondAsync("Invalid dice!"); + } + } + else + { + await ctx.RespondAsync("Invalid dice!"); + } + } + [Description("Rolls character stats for d&d 5e using the 4d6k3 calculation")] + [Command("statroller")] + public async Task StatRollerCommand(CommandContext ctx) + { + int[] stats = { StatRoller(), StatRoller(), StatRoller(), StatRoller(), StatRoller(), StatRoller() }; + await ctx.RespondAsync($"{stats[0]}\n{stats[1]}\n{stats[2]}\n{stats[3]}\n{stats[4]}\n{stats[5]}"); + } + + public int StatRoller() + { + Console.WriteLine("Stat is being rolled!"); + int[] rolls = { rnd.Next(1, 6), rnd.Next(1, 6), rnd.Next(1, 6), rnd.Next(1, 6) }; + int result = rolls[0] + rolls[1] + rolls[2] + rolls[3] - rolls.Min(); + return result; + } + + [Command("betflip")] + + public async Task BetflipCommand(CommandContext ctx, string choice) + { + int flip = rnd.Next(1, 3); // 1 = heads, 2 = tails + + if (choice.ToLower() == "heads") + { + switch (flip) + { + case 1: + await ctx.RespondAsync(embed: new DiscordEmbedBuilder { Title = "Heads! You win!", ImageUrl = "https://cdn.discordapp.com/attachments/897548763021864970/972648591107698698/domcoinheads.png" }.Build()); + break; + case 2: + await ctx.RespondAsync(embed: new DiscordEmbedBuilder { Title = "Tails! You lose!", ImageUrl = "https://cdn.discordapp.com/attachments/897548763021864970/972648580185718834/domcointails.png" }.Build()); + break; + } + } + if (choice.ToLower() == "tails") + { + switch (flip) + { + case 1: + await ctx.RespondAsync("Heads! You lose!"); + await ctx.RespondAsync(embed: new DiscordEmbedBuilder { Title = "Heads! You lose!", ImageUrl = "https://cdn.discordapp.com/attachments/897548763021864970/972648591107698698/domcoinheads.png" }.Build()); + break; + case 2: + await ctx.RespondAsync(embed: new DiscordEmbedBuilder { Title = "Tails! You win!", ImageUrl = "https://cdn.discordapp.com/attachments/897548763021864970/972648580185718834/domcointails.png" }.Build()); + break; + } + } + } + [Hidden] + [Command("setactivity")] + private async Task SetActivityCommand(CommandContext ctx) + { + ulong channel = ctx.Channel.Id; + ulong messageid = ctx.Message.Id; + ulong ownerid = Program.configItems[0].OwnerID; + if (ctx.User.Id == ownerid) //Set this to use the user ID of owner in config file. + { + DiscordActivity activity = new DiscordActivity(); + DiscordClient discord = ctx.Client; + string input = Console.ReadLine(); + activity.Name = input; + await discord.UpdateStatusAsync(activity); + return; + } + else + { + Console.WriteLine($"Secret setactivity command run by {ctx.User} in https://discord.com/channels/{ctx.Channel.GuildId}/{channel}/{messageid}!"); + } + } + } +} \ No newline at end of file diff --git a/SuperMachoBot/Commands/SlashCommands.cs b/SuperMachoBot/Commands/SlashCommands.cs new file mode 100644 index 0000000..29ad036 --- /dev/null +++ b/SuperMachoBot/Commands/SlashCommands.cs @@ -0,0 +1,388 @@ +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; +using SuperMachoBot; + +namespace SuperMachoBot.Commands +{ + public class SlashCommands : ApplicationCommandModule + { + public static string rootPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + #region General Commands + [SlashCommand("Avatar", "Gets high resolution avatar of specified user.")] + public async Task AvatarCommand(InteractionContext ctx, [Option("user", "Discord user to grab avatar from")] DiscordUser du) + { + var color = new DiscordColor(2, 200, 2); + var embed = new DiscordEmbedBuilder + { + Title = du.Username, + Color = color, + ImageUrl = Tools.General.AvatarParser(du) + }; + await ctx.CreateResponseAsync(embed); + } + + Random rnd = new Random(); + [SlashCommand("StatRoller", "Rolls character stats for d&d 5e using the 4d6k3 calculation")] + public async Task StatRollerCommand(InteractionContext ctx) + { + int[] stats = { StatRoller(), StatRoller(), StatRoller(), StatRoller(), StatRoller(), StatRoller() }; + await ctx.CreateResponseAsync($"{stats[0]}\n{stats[1]}\n{stats[2]}\n{stats[3]}\n{stats[4]}\n{stats[5]}"); + } + public int StatRoller() + { + int[] rolls = { rnd.Next(1, 7), rnd.Next(1, 7), rnd.Next(1, 7), rnd.Next(1, 7) }; + int result = rolls[0] + rolls[1] + rolls[2] + rolls[3] - rolls.Min(); + return result; + } + + [SlashCommand("EmbedTest", "Tests discord embed feature lol")] + public async Task DebugEmbedCommand(InteractionContext ctx) + { + var color = new DiscordColor(200, 2, 2); + var embed = new DiscordEmbedBuilder + { + Title = "bruh", + Description = "bruh", + Color = color + }; + await ctx.CreateResponseAsync(embed); + } + [SlashCommand("Banner", "Gets the banner of the current server.")] + public async Task GuildBannerCommand(InteractionContext ctx) + { + var bannerUrl = ctx.Guild.BannerUrl; + if (bannerUrl == null) + { + await ctx.CreateResponseAsync("Error! Current server does not have a banner!"); + } + else + { + var embed = new DiscordEmbedBuilder + { + ImageUrl = bannerUrl + }; + await ctx.CreateResponseAsync(embed); + } + } + + [SlashCommand("UserInfo", "Gets info from user")] + public async Task UserInfoCommand(InteractionContext ctx, [Option("user", "Discord user to grab info from")] DiscordUser du) + { + var hashcode = du.GetHashCode(); + string[] Info = + { + $"**Account Creation Date:** {du.CreationTimestamp}", + $"**Account ID:** {du.Id}", + $"**User Language:** (Currently not functional)", + $"**Is Bot?** {du.IsBot}", + $"**Is Discord Admin?:** {du.IsSystem}" + }; + var embed = new DiscordEmbedBuilder + { + Title = $"{du.Username}#{du.Discriminator}", + Description = $"{Info[0]} \n {Info[1]} \n {Info[2]} \n {Info[3]} \n {Info[4]}", + ImageUrl = Tools.General.AvatarParser(du) + }; + await ctx.CreateResponseAsync(embed); + } + #endregion + #region Economy Commands + [SlashCommand("Balance", "Checks your balance")] + public async Task BalanceCommand(InteractionContext ctx, [Option("User", "User to check balance of")] DiscordUser du) + { + var entry = EconDatabaseChecker(du.Id, ctx.Guild.Id); + var entryParsed = entry[0].Split('|'); + if (entry[0] == "noentry") + { + await ctx.CreateResponseAsync("No entry found! Generating one, please try again."); + } + await ctx.CreateResponseAsync($"{du.Username}: ${entryParsed[1]}"); + } + + + [ContextMenu(ApplicationCommandType.UserContextMenu, "Check balance")] + public async Task BalanceMenuCommand(ContextMenuContext ctx) + { + var entry = EconDatabaseChecker(ctx.TargetUser.Id, ctx.Guild.Id); + var entryParsed = entry[0].Split('|'); + if (entry[0] == "noentry") + { + await ctx.CreateResponseAsync("No entry found! Generating one, please try again."); + } + await ctx.CreateResponseAsync($"{ctx.TargetUser.Username}: ${entryParsed[1]}"); + } + + [SlashCommand("Daily", "Adds $100 to your balance")] + public async Task DailyCommand(InteractionContext ctx) + { + var path = $@"{rootPath}\EconomyDatabase\{ctx.Guild.Id}.csv"; + var amount = 100; + var entry = EconDatabaseChecker(ctx.User.Id, ctx.Guild.Id); + var entryParsed = entry[0].Split('|'); + var entryNumber = Int32.Parse(entry[1]); + Int32 unixTimestamp = (Int32)(DateTime.Now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; + if (entryParsed[2] == "none") + { + string[] lines = File.ReadAllLines(path); + lines[entryNumber - 1] = $"{entryParsed[0]}|{entryParsed[1]}|{unixTimestamp}|"; + WriteAllLinesBetter(path, lines); + AddSubtractUserMoney(ctx.User.Id, ctx.Guild.Id, amount); + await ctx.CreateResponseAsync("First daily! Come back in 24 hours!"); + } + else + { + Int32 secondsSinceLastDaily = unixTimestamp - Convert.ToInt32(entryParsed[2]); + if (secondsSinceLastDaily > 86400) //Check if a day has passed + { + string[] lines = File.ReadAllLines(path); + lines[entryNumber - 1] = $"{entryParsed[0]}|{entryParsed[1]}|{unixTimestamp}|"; + WriteAllLinesBetter(path, lines); + AddSubtractUserMoney(ctx.User.Id, ctx.Guild.Id, amount); + await ctx.CreateResponseAsync("Daily claimed! Come back in 24 hours!"); + } + else if (secondsSinceLastDaily < 86400) + { + var secondsUntilClaim = 86400 - secondsSinceLastDaily; + await ctx.CreateResponseAsync($"Daily already claimed! Come back in {secondsUntilClaim / 3600} hours!"); + } + } + } + + [SlashCommand("Transfer", "Transfer your money to another user.")] + public async Task TransferCommand(InteractionContext ctx, [Option("Amount", "Amount to transfer")] long amount, [Option("User", "User to transfer money to")] DiscordUser du) + { + if(amount < 0) + { + await ctx.CreateResponseAsync("Negative amount detected! Sorry, robbery has not been implemented yet!"); + } + else + { + AddSubtractUserMoney(ctx.User.Id, ctx.Guild.Id, -amount); + AddSubtractUserMoney(du.Id, ctx.Guild.Id, amount); + await ctx.CreateResponseAsync($"${amount} transferred from {ctx.User.Mention} to {du.Mention}"); + } + } + + [SlashCommand("Betflip", "Heads or Tails coinflip!")] + public async Task BetflipCommand(InteractionContext ctx, [Option("Choice", "Heads or Tails? H or T? Choose your path wisely.")] string choice, [Option("Amount", "Real: (typing 'All' currently doesn't work, do it manually.)")] long betAmount) + { + var uid = ctx.User.Id; + var gid = ctx.Guild.Id; + var entry = EconDatabaseChecker(ctx.User.Id, ctx.Guild.Id); + var entryParsed = entry[0].Split('|'); + var playerMoney = Convert.ToInt64(entryParsed[1]); + var moneyEarned = 0; + if (betAmount > playerMoney) + { + await ctx.CreateResponseAsync("You do not have enough money!"); + } + else + { + int flip = rnd.Next(1, 3); // 1 = heads, 2 = tails + string decision = ""; + string headsURL = "https://cdn.discordapp.com/attachments/978411926222684220/1006493578186469376/domcoinheads.png"; + string tailsURL = "https://cdn.discordapp.com/attachments/978411926222684220/1006493587342622730/domcointails.png"; + switch (choice.ToLower()) + { + case "h": + case "head": + case "heads": + decision = "heads"; + break; + case "t": + case "tail": + case "tails": + decision = "tails"; + break; + } + if (decision.ToLower() == "heads") + { + switch (flip) + { + case 1: + await ctx.CreateResponseAsync(embed: new DiscordEmbedBuilder { Title = $"Heads! You win ${betAmount}!", ImageUrl = headsURL }.Build()); + AddSubtractUserMoney(uid, gid, betAmount); + break; + case 2: + await ctx.CreateResponseAsync(embed: new DiscordEmbedBuilder { Title = $"Tails! You lose ${betAmount}!", ImageUrl = tailsURL }.Build()); + AddSubtractUserMoney(uid, gid, -betAmount); + break; + } + } + if (decision.ToLower() == "tails") + { + switch (flip) + { + case 1: + await ctx.CreateResponseAsync("Heads! You lose!"); + await ctx.CreateResponseAsync(embed: new DiscordEmbedBuilder { Title = $"Heads! You lose ${betAmount}!", ImageUrl = headsURL }.Build()); + AddSubtractUserMoney(uid, gid, -betAmount); + break; + case 2: + await ctx.CreateResponseAsync(embed: new DiscordEmbedBuilder { Title = $"Tails! You win ${betAmount}!", ImageUrl = tailsURL }.Build()); + AddSubtractUserMoney(uid, gid, betAmount); + break; + } + } + } + } + + + [SlashCommand("Wheel", "Roll the wheel of Macho Fortune!")] + public async Task WheelCommand(InteractionContext ctx, [Option("Amount", "Real: (typing 'All' currently doesn't work, do it manually.)")] long betAmount) + { + if(betAmount < 0) + { + await ctx.CreateResponseAsync("Negative numbers are not allowed!"); + } + else{ + var entry = EconDatabaseChecker(ctx.User.Id, ctx.Guild.Id); + var entryParsed = entry[0].Split('|'); + double playerMoney = Convert.ToDouble(entryParsed[1]); + double moneyEarned = 0; + if (betAmount > playerMoney) + { + await ctx.CreateResponseAsync("You do not have enough money!"); + } else + { + var roll = rnd.Next(1, 8); + double multiplier = 1; + bool shit = false; + switch (roll) + { + case 1: + multiplier = 2.4; + shit = true; + break; + case 2: + multiplier = 1.8; + shit = true; + break; + case 3: + multiplier = 1.4; + shit = true; + break; + case 4: + multiplier = 0; + break; + case 5: + multiplier = 1.4; + break; + case 6: + multiplier = 1.8; + break; + case 7: + multiplier = 2.4; + break; + } + if(shit == true) + { + moneyEarned = betAmount * multiplier; + AddSubtractUserMoney(ctx.User.Id, ctx.Guild.Id, -Convert.ToInt64(moneyEarned)); + await ctx.CreateResponseAsync($"Money multiplied by -{multiplier}x, lost ${moneyEarned}! Sad!"); + } else + { + moneyEarned = betAmount * multiplier; + AddSubtractUserMoney(ctx.User.Id, ctx.Guild.Id, Convert.ToInt64(moneyEarned)); + await ctx.CreateResponseAsync($"Money multiplied by {multiplier}x, ${moneyEarned}!"); + } + } + } + } + #endregion + #region Economy Tools (I really need to stuff this into the library) + public void AddSubtractUserMoney(ulong userID, ulong guildID, long amount) + { + var entry = EconDatabaseChecker(userID, guildID); + var path = $@"{rootPath}\EconomyDatabase\{guildID}.csv"; + var entryNumber = Int32.Parse(entry[1]); + var entryParsed = entry[0].Split('|'); + var currentAmount = entryParsed[1]; + long finalAmount = Convert.ToInt64(currentAmount) + amount; + string[] lines = File.ReadAllLines(path); + lines[entryNumber - 1] = $"{userID}|{finalAmount.ToString()}|{entryParsed[2]}|"; + WriteAllLinesBetter(path, lines); + } + //Thank you microsoft for requiring a rewrite of your entire method just to not have it add an extra new line at the end of a file. :tf: + public static void WriteAllLinesBetter(string path, params string[] lines) + { + if (path == null) + throw new ArgumentNullException("path"); + if (lines == null) + throw new ArgumentNullException("lines"); + + using (var stream = File.OpenWrite(path)) + using (StreamWriter writer = new StreamWriter(stream)) + { + if (lines.Length > 0) + { + for (int i = 0; i < lines.Length - 1; i++) + { + writer.WriteLine(lines[i]); + } + writer.Write(lines[lines.Length - 1]); + } + } + } + + + + + public void MultiplyUserMoney(ulong userID, ulong guildID, float multiplier) + { + var entry = EconDatabaseChecker(userID, guildID); + var path = $@"{rootPath}\EconomyDatabase\{guildID}.csv"; + var entryNumber = Int32.Parse(entry[1]); + var entryParsed = entry[0].Split('|'); + var currentAmount = entryParsed[1]; + float finalAmount = float.Parse(currentAmount) * multiplier; + string[] lines = File.ReadAllLines(path); + lines[entryNumber - 1] = $"{userID}|{finalAmount.ToString()}"; + WriteAllLinesBetter(path, lines); + } + + + + + + /// + /// Finds the economy database entry for the specified UserID, or creates a new entry for the server/user if an entry for one is missing. + /// + /// + /// The contents, and line number of the entry if found, in a string array. + /// + public static string[] EconDatabaseChecker(ulong userID, ulong guildID) + { + int lineCount = 0; + string[] entry = { "noentry", "bingus" }; + var path = $@"{rootPath}\EconomyDatabase\{guildID}.csv"; + if (File.Exists(path) == false) + { + string entryToCreate = $"{userID}|100|none"; + File.AppendAllText(path, entryToCreate); + } + + foreach (var line in File.ReadAllLines(path)) + { + var entryparsed = line.Split('|'); + lineCount++; + if (entryparsed[0] == userID.ToString()) + { + entry[0] = line; //Contents of entry line + entry[1] = $"{lineCount}"; //Number of line in .csv file + break; + } + } + if (entry[0] == "noentry") //If after the file has been searched, no entry has been found, create a new one and stuff it in the 'entry' variable + { + string entryToCreate = $"{userID}|100|none"; + File.AppendAllText(path, Environment.NewLine + entryToCreate); + return entry; + } + return entry; + } + #endregion + + } +} \ No newline at end of file diff --git a/SuperMachoBot/Config/config.json b/SuperMachoBot/Config/config.json new file mode 100644 index 0000000..887b6b9 --- /dev/null +++ b/SuperMachoBot/Config/config.json @@ -0,0 +1,6 @@ +[ + { + "token": "", + "OwnerID": 0 + } +] \ No newline at end of file diff --git a/SuperMachoBot/EconomyDatabase/Template.csv b/SuperMachoBot/EconomyDatabase/Template.csv new file mode 100644 index 0000000..b934ecd --- /dev/null +++ b/SuperMachoBot/EconomyDatabase/Template.csv @@ -0,0 +1 @@ +userid|money|timesincelastdailyunixtimestamp \ No newline at end of file diff --git a/SuperMachoBot/Images/Logo.png b/SuperMachoBot/Images/Logo.png new file mode 100644 index 0000000..d8ce448 Binary files /dev/null and b/SuperMachoBot/Images/Logo.png differ diff --git a/SuperMachoBot/Program.cs b/SuperMachoBot/Program.cs new file mode 100644 index 0000000..68a25f3 --- /dev/null +++ b/SuperMachoBot/Program.cs @@ -0,0 +1,66 @@ +using DSharpPlus; +using DSharpPlus.CommandsNext; +using DSharpPlus.SlashCommands; +using SuperMachoBot.Commands; +using Newtonsoft.Json; + +namespace SuperMachoBot +{ + class Program + { + public static string rootPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + public static bool moneyCooldown = true; + public static List configItems = new List(); + static void Main(string[] args) + { + MainAsync().GetAwaiter().GetResult(); + } + + internal static async Task MainAsync() + { + using (StreamReader r = new StreamReader(@$"{rootPath}\config\config.json")) + { + string json = r.ReadToEnd(); + configItems = JsonConvert.DeserializeObject>(json); + } + var discord = new DiscordClient(new DiscordConfiguration() + { + Token = configItems[0].Token, + TokenType = TokenType.Bot + }); + + var slash = discord.UseSlashCommands(); + + discord.MessageCreated += async (s, e) => + { + if (e.Message.Content.Contains("money") && e.Message.Content.Contains("tenor") == false && moneyCooldown == false) + { + await e.Message.RespondAsync("https://tenor.com/view/money-breaking-bad-sleep-on-money-lay-on-money-money-pile-gif-5382667"); + cooldown(); + } + }; + + var commands = discord.UseCommandsNext(new CommandsNextConfiguration() + { + StringPrefixes = new[] { "tf" } + }); + + commands.RegisterCommands(); + slash.RegisterCommands(); + + await discord.ConnectAsync(); + await Task.Delay(-1); + } + static void cooldown() + { + moneyCooldown = true; + Thread.Sleep(10000); + moneyCooldown = false; + } + } + public class Config + { + public string Token; + public ulong OwnerID; + } +} \ No newline at end of file diff --git a/SuperMachoBot/SuperMachoBot.csproj b/SuperMachoBot/SuperMachoBot.csproj new file mode 100644 index 0000000..b41a78e --- /dev/null +++ b/SuperMachoBot/SuperMachoBot.csproj @@ -0,0 +1,30 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/SuperMachoBot/Tools/Tools.cs b/SuperMachoBot/Tools/Tools.cs new file mode 100644 index 0000000..5ccf1a4 --- /dev/null +++ b/SuperMachoBot/Tools/Tools.cs @@ -0,0 +1,51 @@ +using DSharpPlus.Entities; +using DSharpPlus; +using System.Net; + +namespace SuperMachoBot.Tools +{ + class General + { + /// + /// The status code of the specified url. + /// + public static int GetPage(String url) + { + try { + HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); + HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); + + return (int)myHttpWebResponse.StatusCode; + } + catch (WebException e) + { + return 0; + } + } + + + + /// + /// Checks the HTTP status code of the specified user's avatar url to determine if it's a gif or image. + /// + /// + /// The appropriate avatar url for the user. + /// + public static string AvatarParser(DiscordUser du) + { + var avatarUrl = du.GetAvatarUrl(ImageFormat.Gif); + if (GetPage(avatarUrl) == 200) + { + return du.GetAvatarUrl(ImageFormat.Gif); + } + else + { + return du.GetAvatarUrl(ImageFormat.Png); + } + } + } + class Economy + { + + } +} \ No newline at end of file