commit 784ece36cdaa68108f1b445f26944e2cbe8eb489 Author: Dmitrii Kollerov Date: Fri Jul 14 20:19:56 2023 +0700 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3552188 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.vs +bin/ +obj/ \ No newline at end of file diff --git a/Budet-cho-bot.sln b/Budet-cho-bot.sln new file mode 100644 index 0000000..d8d6fa0 --- /dev/null +++ b/Budet-cho-bot.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Budet-cho-bot", "Budet-cho-bot\Budet-cho-bot.csproj", "{9BF9C0C5-C025-425B-AB1C-6153C3827A76}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9BF9C0C5-C025-425B-AB1C-6153C3827A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BF9C0C5-C025-425B-AB1C-6153C3827A76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BF9C0C5-C025-425B-AB1C-6153C3827A76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BF9C0C5-C025-425B-AB1C-6153C3827A76}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Budet-cho-bot.sln.DotSettings.user b/Budet-cho-bot.sln.DotSettings.user new file mode 100644 index 0000000..d97cd0c --- /dev/null +++ b/Budet-cho-bot.sln.DotSettings.user @@ -0,0 +1,4 @@ + + <AssemblyExplorer> + <Assembly Path="C:\Users\Admin\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\5.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll" /> +</AssemblyExplorer> \ No newline at end of file diff --git a/Budet-cho-bot/Budet-cho-bot.csproj b/Budet-cho-bot/Budet-cho-bot.csproj new file mode 100644 index 0000000..ac47556 --- /dev/null +++ b/Budet-cho-bot/Budet-cho-bot.csproj @@ -0,0 +1,16 @@ + + + + Exe + net7.0 + Budet_cho_bot + enable + enable + + + + + + + + diff --git a/Budet-cho-bot/CommandHandler.cs b/Budet-cho-bot/CommandHandler.cs new file mode 100644 index 0000000..2b50b88 --- /dev/null +++ b/Budet-cho-bot/CommandHandler.cs @@ -0,0 +1,90 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Discord.Commands; +using Discord.WebSocket; +using Newtonsoft.Json.Linq; +using Discord; +using System.Linq; +using Newtonsoft.Json; + +namespace Discord_Bot +{ + public class CommandHandlingService + { + private readonly CommandService _commands; + private readonly DiscordSocketClient _client; + private readonly IServiceProvider _services; + + public CommandHandlingService(IServiceProvider services) + { + + _commands = services.GetRequiredService(); + _client = services.GetRequiredService(); + _services = services; + + // Event handlers + _client.Ready += ClientReadyAsync; + _client.MessageReceived += HandleCommandAsync; + _client.JoinedGuild += SendJoinMessageAsync; + } + + private async Task HandleCommandAsync(SocketMessage rawMessage) + { + if (rawMessage.Author.IsBot || !(rawMessage is SocketUserMessage message) || message.Channel is IDMChannel) + return; + + var context = new SocketCommandContext(_client, message); + + int argPos = 0; + + JObject config = Functions.GetConfig(); + string[] prefixes = JsonConvert.DeserializeObject(config["prefixes"].ToString()); + + // Check if message has any of the prefixes or mentiones the bot. + if (prefixes.Any(x => message.HasStringPrefix(x, ref argPos)) || message.HasMentionPrefix(_client.CurrentUser, ref argPos)) + { + // Execute the command. + var result = await _commands.ExecuteAsync(context, argPos, _services); + + if (!result.IsSuccess && result.Error.HasValue) + await context.Channel.SendMessageAsync($":x: {result.ErrorReason}"); + } + } + + private async Task SendJoinMessageAsync(SocketGuild guild) + { + JObject config = Functions.GetConfig(); + string joinMessage = config["join_message"]?.Value(); + + if (string.IsNullOrEmpty(joinMessage)) + return; + + // Send the join message in the first channel where the bot can send messsages. + foreach (var channel in guild.TextChannels.OrderBy(x => x.Position)) + { + var botPerms = channel.GetPermissionOverwrite(_client.CurrentUser).GetValueOrDefault(); + + if (botPerms.SendMessages == PermValue.Deny) + continue; + + try + { + await channel.SendMessageAsync(joinMessage); + return; + } + catch + { + continue; + } + } + } + + private async Task ClientReadyAsync() + => await Functions.SetBotStatusAsync(_client); + + public async Task InitializeAsync() + => await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } +} \ No newline at end of file diff --git a/Budet-cho-bot/Functions/Functions.cs b/Budet-cho-bot/Functions/Functions.cs new file mode 100644 index 0000000..13e1ebd --- /dev/null +++ b/Budet-cho-bot/Functions/Functions.cs @@ -0,0 +1,73 @@ +using Discord; +using Discord.WebSocket; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Discord_Bot +{ + public static class Functions + { + public static async Task SetBotStatusAsync(DiscordSocketClient client) + { + JObject config = GetConfig(); + + string currently = config["currently"]?.Value().ToLower(); + string statusText = config["playing_status"]?.Value(); + string onlineStatus = config["status"]?.Value().ToLower(); + + // Set the online status + if (!string.IsNullOrEmpty(onlineStatus)) + { + UserStatus userStatus = onlineStatus switch + { + "dnd" => UserStatus.DoNotDisturb, + "idle" => UserStatus.Idle, + "offline" => UserStatus.Invisible, + _ => UserStatus.Online + }; + + await client.SetStatusAsync(userStatus); + Console.WriteLine($"{DateTime.Now.TimeOfDay:hh\\:mm\\:ss} | Online status set | {userStatus}"); + } + + // Set the playing status + if (!string.IsNullOrEmpty(currently) && !string.IsNullOrEmpty(statusText)) + { + ActivityType activity = currently switch + { + "listening" => ActivityType.Listening, + "watching" => ActivityType.Watching, + "streaming" => ActivityType.Streaming, + _ => ActivityType.Playing + }; + + await client.SetGameAsync(statusText, type: activity); + Console.WriteLine($"{DateTime.Now.TimeOfDay:hh\\:mm\\:ss} | Playing status set | {activity}: {statusText}"); + } + } + + public static JObject GetConfig() + { + using StreamReader configJson = new StreamReader(Directory.GetCurrentDirectory() + @"/Config.json"); + return (JObject)JsonConvert.DeserializeObject(configJson.ReadToEnd()); + } + + public static void SaveConfig(JObject config) + { + using (StreamWriter file = File.CreateText(Directory.GetCurrentDirectory() + @"/Config.json")) + using (JsonTextWriter writer = new JsonTextWriter(file)) + { + config.WriteTo(writer); + } + } + + public static string GetAvatarUrl(SocketUser user, ushort size = 1024) + { + // Get user avatar and resize it. If the user has no avatar, get the default Discord avatar. + return user.GetAvatarUrl(size: size) ?? user.GetDefaultAvatarUrl(); + } + } +} diff --git a/Budet-cho-bot/Modules/FunSample.cs b/Budet-cho-bot/Modules/FunSample.cs new file mode 100644 index 0000000..4032e03 --- /dev/null +++ b/Budet-cho-bot/Modules/FunSample.cs @@ -0,0 +1,48 @@ +using Discord.Commands; +using Discord.WebSocket; +using System; +using System.Threading.Tasks; + +namespace Discord_Bot +{ + public class FunSample : ModuleBase + { + + [Command("hello")] // Command name. + [Summary("Say hello to the bot.")] // Command summary. + public async Task Hello() + => await ReplyAsync($"Hello there, **{Context.User.Username}**!"); + + [Command("pick")] + [Alias("choose")] // Aliases that will also trigger the command. + [Summary("Pick something.")] + public async Task Pick([Remainder]string message = "") + { + string[] options = message.Split(new string[] { " or " }, StringSplitOptions.RemoveEmptyEntries); + string selection = options[new Random().Next(options.Length)]; + + // ReplyAsync() is a shortcut for Context.Channel.SendMessageAsync() + await ReplyAsync($"I choose **{selection}**"); + } + + [Command("cookie")] + [Summary("Give someone a cookie.")] + public async Task Cookie(SocketGuildUser user) + { + if (Context.Message.Author.Id == user.Id) + await ReplyAsync($"{Context.User.Mention} doesn't have anyone to share a cookie with... :("); + else + await ReplyAsync($"{Context.User.Mention} shared a cookie with **{user.Username}** :cookie:"); + } + + [Command("amiadmin")] + [Summary("Check your administrator status")] + public async Task AmIAdmin() + { + if ((Context.User as SocketGuildUser).GuildPermissions.Administrator) + await ReplyAsync($"Yes, **{Context.User.Username}**, you're an admin!"); + else + await ReplyAsync($"No, **{Context.User.Username}**, you're **not** an admin!"); + } + } +} diff --git a/Budet-cho-bot/Modules/InfoModule.cs b/Budet-cho-bot/Modules/InfoModule.cs new file mode 100644 index 0000000..93d687e --- /dev/null +++ b/Budet-cho-bot/Modules/InfoModule.cs @@ -0,0 +1,14 @@ +using Discord.Commands; + +namespace Budet_cho_bot.Modules; + +public class InfoModule : ModuleBase +{ + // ~say hello world -> hello world + [Command("say")] + [Summary("Echoes a message.")] + public Task SayAsync([Remainder] [Summary("The text to echo")] string echo) + => ReplyAsync(echo); + + // ReplyAsync is a method on ModuleBase +} \ No newline at end of file diff --git a/Budet-cho-bot/Modules/ModSample.cs b/Budet-cho-bot/Modules/ModSample.cs new file mode 100644 index 0000000..285439d --- /dev/null +++ b/Budet-cho-bot/Modules/ModSample.cs @@ -0,0 +1,60 @@ +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using Discord.WebSocket; + +namespace Discord_Bot +{ + public class ModSample : ModuleBase + { + [Command("kick")] + [Summary("Kick a user from the server.")] + [RequireBotPermission(GuildPermission.KickMembers)] + [RequireUserPermission(GuildPermission.KickMembers)] + public async Task Kick(SocketGuildUser targetUser, [Remainder]string reason = "No reason provided.") + { + await targetUser.KickAsync(reason); + await ReplyAsync($"**{targetUser}** has been kicked. Bye bye :wave:"); + } + + [Command("ban")] + [Summary("Ban a user from the server")] + [RequireUserPermission(GuildPermission.BanMembers)] + [RequireBotPermission(GuildPermission.BanMembers)] + public async Task Ban(SocketGuildUser targetUser, [Remainder]string reason = "No reason provided.") + { + await Context.Guild.AddBanAsync(targetUser.Id, 0, reason); + await ReplyAsync($"**{targetUser}** has been banned. Bye bye :wave:"); + } + + [Command("unban")] + [Summary("Unban a user from the server")] + [RequireBotPermission(GuildPermission.BanMembers)] + [RequireUserPermission(GuildPermission.BanMembers)] + public async Task Unban(ulong targetUser) + { + await Context.Guild.RemoveBanAsync(targetUser); + await Context.Channel.SendMessageAsync($"The user has been unbanned :clap:"); + } + + [Command("purge")] + [Summary("Bulk deletes messages in chat")] + [RequireBotPermission(GuildPermission.ManageMessages)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public async Task Purge(int delNumber) + { + var channel = Context.Channel as SocketTextChannel; + var items = await channel.GetMessagesAsync(delNumber + 1).FlattenAsync(); + await channel.DeleteMessagesAsync(items); + } + + [Command("reloadconfig")] + [Summary("Reloads the config and applies changes")] + [RequireOwner] // Require the bot owner to execute the command successfully. + public async Task ReloadConfig() + { + await Functions.SetBotStatusAsync(Context.Client); + await ReplyAsync("Reloaded!"); + } + } +} diff --git a/Budet-cho-bot/Modules/ReminderModule.cs b/Budet-cho-bot/Modules/ReminderModule.cs new file mode 100644 index 0000000..c2af22d --- /dev/null +++ b/Budet-cho-bot/Modules/ReminderModule.cs @@ -0,0 +1,82 @@ +using Discord.Commands; + +namespace Discord_Bot +{ + public class ReminderModule : ModuleBase + { + private long dayPeriod = 24 * 60 * 60 * 1000; + private Timer reminderTimer; + + [Command("remindertime")] + public async Task ReminderTime([Remainder]string message = "") + { + SaveToConfig("reminderTime", message); + + var time = message.Split(":"); + SetReminderTime(int.Parse(time[0]), int.Parse(time[1])); + await ReplyAsync($"Заебись"); + } + + [Command("calltime")] + public async Task CallTime([Remainder]string message = "") + { + SaveToConfig("callTime", message); + + var time = message.Split(":"); + SetReminderTime(int.Parse(time[0]), int.Parse(time[1])); + await ReplyAsync($"Заебись"); + } + + [Command("users")] + public async Task Users([Remainder]string message = "") + { + SaveToConfig("users", message); + + var time = message.Split(":"); + SetReminderTime(int.Parse(time[0]), int.Parse(time[1])); + await ReplyAsync($"Заебись"); + } + + public void SetReminderTime(int hours, int minutes) + { + var now = DateTime.Now; + var nextReminder = new DateTime(now.Year, now.Month, now.Day, hours, minutes, 0); + var span = (nextReminder - now).TotalMilliseconds; + reminderTimer = new Timer(SendReminder, "sd", (long)span, dayPeriod); + + ReloadTimers(); + } + + public void SetCallTime(int hours, int minutes) + { + var now = DateTime.Now; + var nextReminder = new DateTime(now.Year, now.Month, now.Day, hours, minutes, 0); + var span = (nextReminder - now).TotalMilliseconds; + reminderTimer = new Timer(SendReminder, "sd", (long)span, dayPeriod); + + ReloadTimers(); + } + + private async void SendReminder(object? state) + { + var users = Functions.GetConfig()["users"].ToString(); + await ReplyAsync($"{users}\nБудет чо?"); + } + private async void SendCall(object? state) + { + await ReplyAsync($"?"); + } + + private void SaveToConfig(string key, string data) + { + var config = Functions.GetConfig(); + config[key] = data; + Functions.SaveConfig(config); + } + + private void ReloadTimers() + { + + } + } +} \ No newline at end of file diff --git a/Budet-cho-bot/Modules/UtilitySample.cs b/Budet-cho-bot/Modules/UtilitySample.cs new file mode 100644 index 0000000..44eca2e --- /dev/null +++ b/Budet-cho-bot/Modules/UtilitySample.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using System.Globalization; + +namespace Discord_Bot +{ + public class UtilitySample : ModuleBase + { + [Command("ping")] + [Summary("Show current latency.")] + public async Task Ping() + => await ReplyAsync($"Latency: {Context.Client.Latency} ms"); + + [Command("avatar")] + [Alias("getavatar")] + [Summary("Get a user's avatar.")] + public async Task GetAvatar([Remainder]SocketGuildUser user = null) + => await ReplyAsync($":frame_photo: **{(user ?? Context.User as SocketGuildUser).Username}**'s avatar\n{Functions.GetAvatarUrl(user)}"); + + + [Command("info")] + [Alias("server", "serverinfo")] + [Summary("Show server information.")] + [RequireBotPermission(GuildPermission.EmbedLinks)] // Require the bot the have the 'Embed Links' permissions to execute this command. + public async Task ServerEmbed() + { + double botPercentage = Math.Round(Context.Guild.Users.Count(x => x.IsBot) / Context.Guild.MemberCount * 100d, 2); + + EmbedBuilder embed = new EmbedBuilder() + .WithColor(0, 225, 225) + .WithDescription( + $"🏷️\n**Guild name:** {Context.Guild.Name}\n" + + $"**Guild ID:** {Context.Guild.Id}\n" + + $"**Created at:** {Context.Guild.CreatedAt:dd/M/yyyy}\n" + + $"**Owner:** {Context.Guild.Owner}\n\n" + + $"💬\n" + + $"**Users:** {Context.Guild.MemberCount - Context.Guild.Users.Count(x => x.IsBot)}\n" + + $"**Bots:** {Context.Guild.Users.Count(x => x.IsBot)} [ {botPercentage}% ]\n" + + $"**Channels:** {Context.Guild.Channels.Count}\n" + + $"**Roles:** {Context.Guild.Roles.Count}\n" + + $"**Emotes: ** {Context.Guild.Emotes.Count}\n\n" + + $"🌎 **Region:** {Context.Guild.VoiceRegionId}\n\n" + + $"🔒 **Security level:** {Context.Guild.VerificationLevel}") + .WithImageUrl(Context.Guild.IconUrl); + + await ReplyAsync($":information_source: Server info for **{Context.Guild.Name}**", embed: embed.Build()); + } + + [Command("role")] + [Alias("roleinfo")] + [Summary("Show information about a role.")] + public async Task RoleInfo([Remainder]SocketRole role) + { + // Just in case someone tries to be funny. + if (role.Id == Context.Guild.EveryoneRole.Id) + return; + + await ReplyAsync( + $":flower_playing_cards: **{role.Name}** information```ini" + + $"\n[Members] {role.Members.Count()}" + + $"\n[Role ID] {role.Id}" + + $"\n[Hoisted status] {role.IsHoisted}" + + $"\n[Created at] {role.CreatedAt:dd/M/yyyy}" + + $"\n[Hierarchy position] {role.Position}" + + $"\n[Color Hex] {role.Color}```"); + } + + // Please don't remove this command. I will appreciate it a lot <3 + [Command("source")] + [Alias("sourcecode", "src")] + [Summary("Link the source code used for this bot.")] + public async Task Source() + => await ReplyAsync($":heart: **{Context.Client.CurrentUser}** is based on this source code:\nhttps://github.com/VACEfron/Discord-Bot-Csharp"); + } +} diff --git a/Budet-cho-bot/Program.cs b/Budet-cho-bot/Program.cs new file mode 100644 index 0000000..5137b49 --- /dev/null +++ b/Budet-cho-bot/Program.cs @@ -0,0 +1,90 @@ +using Discord; +using Discord.Commands; +using Discord.Net; +using Discord.WebSocket; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Reflection; +using System.Threading.Tasks; +using Discord_Bot; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +public class Program +{ + private DiscordSocketClient _client; + private readonly CommandService _commands; + private readonly IServiceProvider _services; + + public static Task Main(string[] args) => new Program().MainAsync(); + + public async Task MainAsync() + { + using var services = ConfigureServices(); + + Console.WriteLine("Ready for takeoff..."); + var client = services.GetRequiredService(); + + client.Log += Log; + services.GetRequiredService().Log += Log; + + // Get the bot token from the Config.json file. + JObject config = Functions.GetConfig(); + string token = config["token"].Value(); + + // Log in to Discord and start the bot. + await client.LoginAsync(TokenType.Bot, token); + await client.StartAsync(); + + await services.GetRequiredService().InitializeAsync(); + + + + // Run the bot forever. + await Task.Delay(-1); + } + + public ServiceProvider ConfigureServices() + { + return new ServiceCollection() + .AddSingleton(new DiscordSocketClient(new DiscordSocketConfig + { + MessageCacheSize = 500, + LogLevel = LogSeverity.Info, + GatewayIntents = GatewayIntents.All, + })) + .AddSingleton(new CommandService(new CommandServiceConfig + { + LogLevel = LogSeverity.Info, + DefaultRunMode = RunMode.Async, + CaseSensitiveCommands = false + })) + .AddSingleton() + .BuildServiceProvider(); + } + + private Task Log(LogMessage message) + { + switch (message.Severity) + { + case LogSeverity.Critical: + case LogSeverity.Error: + Console.ForegroundColor = ConsoleColor.Red; + break; + case LogSeverity.Warning: + Console.ForegroundColor = ConsoleColor.Yellow; + break; + case LogSeverity.Info: + Console.ForegroundColor = ConsoleColor.White; + break; + case LogSeverity.Verbose: + case LogSeverity.Debug: + Console.ForegroundColor = ConsoleColor.DarkGray; + break; + } + Console.WriteLine($"{DateTime.Now,-19} [{message.Severity,8}] {message.Source}: {message.Message} {message.Exception}"); + Console.ResetColor(); + + return Task.CompletedTask; + } +}