Split Geekbot.net into src/Bot, src/Core, and src/Web

This commit is contained in:
runebaas 2020-08-08 22:24:01 +02:00
parent 7b6dd2d2f9
commit fc0af492ad
No known key found for this signature in database
GPG key ID: 2677AF508D0300D6
197 changed files with 542 additions and 498 deletions

43
src/Bot/Bot.csproj Normal file
View file

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<ApplicationIcon>derp.ico</ApplicationIcon>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<RootNamespace>Geekbot.Bot</RootNamespace>
<AssemblyName>Geekbot</AssemblyName>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.0-DEV</Version>
<Company>Pizza and Coffee Studios</Company>
<Authors>Pizza and Coffee Studios</Authors>
<Description>A Discord bot</Description>
<RepositoryUrl>https://github.com/pizzaandcoffee/Geekbot.net</RepositoryUrl>
<NoWarn>NU1701</NoWarn>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://geekbot.pizzaandcoffee.rocks</PackageProjectUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Discord.Net" Version="2.2.0" />
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.45.0.1929" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.24" />
<PackageReference Include="MtgApiManager.Lib" Version="1.2.2" />
<PackageReference Include="MyAnimeListSharp" Version="1.3.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="PokeApi.NET" Version="1.1.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<Content Include="Storage\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Web\Web.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,234 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Admin
{
[Group("admin")]
[RequireUserPermission(GuildPermission.Administrator)]
[DisableInDirectMessage]
public class Admin : ModuleBase
{
private readonly DiscordSocketClient _client;
private readonly IErrorHandler _errorHandler;
private readonly IGuildSettingsManager _guildSettingsManager;
private readonly ITranslationHandler _translation;
public Admin(DiscordSocketClient client, IErrorHandler errorHandler, IGuildSettingsManager guildSettingsManager, ITranslationHandler translationHandler)
{
_client = client;
_errorHandler = errorHandler;
_guildSettingsManager = guildSettingsManager;
_translation = translationHandler;
}
[Command("welcome", RunMode = RunMode.Async)]
[Summary("Set a Welcome Message (use '$user' to mention the new joined user).")]
public async Task SetWelcomeMessage([Remainder, Summary("message")] string welcomeMessage)
{
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
guild.WelcomeMessage = welcomeMessage;
await _guildSettingsManager.UpdateSettings(guild);
var formatedMessage = welcomeMessage.Replace("$user", Context.User.Mention);
await ReplyAsync($"Welcome message has been changed\r\nHere is an example of how it would look:\r\n{formatedMessage}");
}
[Command("welcomechannel", RunMode = RunMode.Async)]
[Summary("Set a channel for the welcome messages (by default it uses the top most channel)")]
public async Task SelectWelcomeChannel([Summary("#Channel")] ISocketMessageChannel channel)
{
try
{
var m = await channel.SendMessageAsync("...");
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
guild.WelcomeChannel = channel.Id.AsLong();
await _guildSettingsManager.UpdateSettings(guild);
await m.DeleteAsync();
await ReplyAsync("Successfully saved the welcome channel");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context, "That channel doesn't seem to exist or i don't have write permissions");
}
}
[Command("modchannel", RunMode = RunMode.Async)]
[Summary("Set a channel for moderation purposes")]
public async Task SelectModChannel([Summary("#Channel")] ISocketMessageChannel channel)
{
try
{
var m = await channel.SendMessageAsync("verifying...");
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
guild.ModChannel = channel.Id.AsLong();
await _guildSettingsManager.UpdateSettings(guild);
var sb = new StringBuilder();
sb.AppendLine("Successfully saved mod channel, you can now do the following");
sb.AppendLine("- `!admin showleave` - send message to mod channel when someone leaves");
sb.AppendLine("- `!admin showdel` - send message to mod channel when someone deletes a message");
await m.ModifyAsync(e => e.Content = sb.ToString());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context, "That channel doesn't seem to exist or i don't have write permissions");
}
}
[Command("showleave", RunMode = RunMode.Async)]
[Summary("Toggle - notify modchannel when someone leaves")]
public async Task ShowLeave()
{
try
{
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
var modChannel = await GetModChannel(guild.ModChannel.AsUlong());
if (modChannel == null) return;
guild.ShowLeave = !guild.ShowLeave;
await _guildSettingsManager.UpdateSettings(guild);
await modChannel.SendMessageAsync(guild.ShowLeave
? "Saved - now sending messages here when someone leaves"
: "Saved - stopping sending messages here when someone leaves"
);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("showdel", RunMode = RunMode.Async)]
[Summary("Toggle - notify modchannel when someone deletes a message")]
public async Task ShowDelete()
{
try
{
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
var modChannel = await GetModChannel(guild.ModChannel.AsUlong());
if (modChannel == null) return;
guild.ShowDelete = !guild.ShowDelete;
await _guildSettingsManager.UpdateSettings(guild);
await modChannel.SendMessageAsync(guild.ShowDelete
? "Saved - now sending messages here when someone deletes a message"
: "Saved - stopping sending messages here when someone deletes a message"
);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("setlang", RunMode = RunMode.Async)]
[Summary("Change the bots language")]
public async Task SetLanguage([Summary("language")] string languageRaw)
{
try
{
var language = languageRaw.ToUpper();
var success = await _translation.SetLanguage(Context.Guild.Id, language);
if (success)
{
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
guild.Language = language;
await _guildSettingsManager.UpdateSettings(guild);
var transContext = await _translation.GetGuildContext(Context);
await ReplyAsync(transContext.GetString("NewLanguageSet"));
return;
}
await ReplyAsync(
$"That doesn't seem to be a supported language\r\nSupported Languages are {string.Join(", ", _translation.SupportedLanguages)}");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("wiki", RunMode = RunMode.Async)]
[Summary("Change the wikipedia instance (use lang code in xx.wikipedia.org)")]
public async Task SetWikiLanguage([Summary("language")] string languageRaw)
{
try
{
var language = languageRaw.ToLower();
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
guild.WikiLang = language;
await _guildSettingsManager.UpdateSettings(guild);
await ReplyAsync($"Now using the {language} wikipedia");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("ping", RunMode = RunMode.Async)]
[Summary("Enable the ping reply.")]
public async Task TogglePing()
{
try
{
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
guild.Ping = !guild.Ping;
await _guildSettingsManager.UpdateSettings(guild);
await ReplyAsync(guild.Ping ? "i will reply to ping now" : "No more pongs...");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("hui", RunMode = RunMode.Async)]
[Summary("Enable the ping reply.")]
public async Task ToggleHui()
{
try
{
var guild = _guildSettingsManager.GetSettings(Context.Guild.Id);
guild.Hui = !guild.Hui;
await _guildSettingsManager.UpdateSettings(guild);
await ReplyAsync(guild.Hui ? "i will reply to hui now" : "No more hui's...");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task<ISocketMessageChannel> GetModChannel(ulong channelId)
{
try
{
if (channelId == ulong.MinValue) throw new Exception();
var modChannel = (ISocketMessageChannel) _client.GetChannel(channelId);
if (modChannel == null) throw new Exception();
return modChannel;
}
catch
{
await ReplyAsync("Modchannel doesn't seem to exist, please set one with `!admin modchannel [channelId]`");
return null;
}
}
}
}

View file

@ -0,0 +1,38 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Admin
{
[Group("mod")]
[RequireUserPermission(GuildPermission.KickMembers)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireUserPermission(GuildPermission.ManageRoles)]
[DisableInDirectMessage]
public class Mod : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Mod(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("namehistory", RunMode = RunMode.Async)]
[Summary("See past usernames of an user")]
public async Task UsernameHistory([Summary("@someone")] IUser user)
{
try
{
await Context.Channel.SendMessageAsync("This command has been removed due to low usage and excessively high database usage");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,126 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GlobalSettings;
using Geekbot.Core.Logger;
using Geekbot.Core.UserRepository;
namespace Geekbot.Bot.Commands.Admin.Owner
{
[Group("owner")]
[RequireOwner]
public class Owner : ModuleBase
{
private readonly DiscordSocketClient _client;
private readonly IErrorHandler _errorHandler;
private readonly IGlobalSettings _globalSettings;
private readonly IGeekbotLogger _logger;
private readonly IUserRepository _userRepository;
public Owner(DiscordSocketClient client, IGeekbotLogger logger, IUserRepository userRepositry, IErrorHandler errorHandler, IGlobalSettings globalSettings)
{
_client = client;
_logger = logger;
_userRepository = userRepositry;
_errorHandler = errorHandler;
_globalSettings = globalSettings;
}
[Command("youtubekey", RunMode = RunMode.Async)]
[Summary("Set the youtube api key")]
public async Task SetYoutubeKey([Summary("API-Key")] string key)
{
await _globalSettings.SetKey("YoutubeKey", key);
await ReplyAsync("Apikey has been set");
}
[Command("game", RunMode = RunMode.Async)]
[Summary("Set the game that the bot is playing")]
public async Task SetGame([Remainder] [Summary("Game")] string key)
{
await _globalSettings.SetKey("Game", key);
await _client.SetGameAsync(key);
_logger.Information(LogSource.Geekbot, $"Changed game to {key}");
await ReplyAsync($"Now Playing {key}");
}
[Command("popuserrepo", RunMode = RunMode.Async)]
[Summary("Populate user cache")]
public async Task PopUserRepoCommand()
{
var success = 0;
var failed = 0;
try
{
_logger.Warning(LogSource.UserRepository, "Populating User Repositry");
await ReplyAsync("Starting Population of User Repository");
foreach (var guild in _client.Guilds)
{
_logger.Information(LogSource.UserRepository, $"Populating users from {guild.Name}");
foreach (var user in guild.Users)
{
var succeded = await _userRepository.Update(user);
var inc = succeded ? success++ : failed++;
}
}
_logger.Warning(LogSource.UserRepository, "Finished Updating User Repositry");
await ReplyAsync(
$"Successfully Populated User Repository with {success} Users in {_client.Guilds.Count} Guilds (Failed: {failed})");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"Couldn't complete User Repository, see console for more info");
}
}
[Command("refreshuser", RunMode = RunMode.Async)]
[Summary("Refresh a user in the user cache")]
public async Task PopUserRepoCommand([Summary("@someone")] IUser user)
{
try
{
await _userRepository.Update(user as SocketUser);
await ReplyAsync($"Refreshed: {user.Username}#{user.Discriminator}");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("refreshuser", RunMode = RunMode.Async)]
[Summary("Refresh a user in the user cache")]
public async Task PopUserRepoCommand([Summary("user-id")] ulong userId)
{
try
{
var user = _client.GetUser(userId);
await _userRepository.Update(user);
await ReplyAsync($"Refreshed: {user.Username}#{user.Discriminator}");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("error", RunMode = RunMode.Async)]
[Summary("Throw an error un purpose")]
public async Task PurposefulError()
{
try
{
throw new Exception("Error Generated by !owner error");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,196 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.Net;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Localization;
using Geekbot.Core.ReactionListener;
namespace Geekbot.Bot.Commands.Admin
{
[Group("role")]
[DisableInDirectMessage]
public class Role : ModuleBase
{
private readonly DatabaseContext _database;
private readonly IErrorHandler _errorHandler;
private readonly IReactionListener _reactionListener;
private readonly ITranslationHandler _translationHandler;
public Role(DatabaseContext database, IErrorHandler errorHandler, IReactionListener reactionListener, ITranslationHandler translationHandler)
{
_database = database;
_errorHandler = errorHandler;
_reactionListener = reactionListener;
_translationHandler = translationHandler;
}
[Command(RunMode = RunMode.Async)]
[Summary("Get a list of all available roles.")]
public async Task GetAllRoles()
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
var roles = _database.RoleSelfService.Where(g => g.GuildId.Equals(Context.Guild.Id.AsLong())).ToList();
if (roles.Count == 0)
{
await ReplyAsync(transContext.GetString("NoRolesConfigured"));
return;
}
var sb = new StringBuilder();
sb.AppendLine(transContext.GetString("ListHeader", Context.Guild.Name));
sb.AppendLine(transContext.GetString("ListInstruction"));
foreach (var role in roles) sb.AppendLine($"- {role.WhiteListName}");
await ReplyAsync(sb.ToString());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command(RunMode = RunMode.Async)]
[Summary("Get a role by mentioning it.")]
public async Task GiveRole([Summary("role-nickname")] string roleNameRaw)
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
var roleName = roleNameRaw.ToLower();
var roleFromDb = _database.RoleSelfService.FirstOrDefault(e =>
e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.WhiteListName.Equals(roleName));
if (roleFromDb != null)
{
var guildUser = (IGuildUser) Context.User;
var role = Context.Guild.Roles.First(r => r.Id == roleFromDb.RoleId.AsUlong());
if (role == null)
{
await ReplyAsync(transContext.GetString("RoleNotFound"));
return;
}
if (guildUser.RoleIds.Contains(role.Id))
{
await guildUser.RemoveRoleAsync(role);
await ReplyAsync(transContext.GetString("RemovedUserFromRole", role.Name));
return;
}
await guildUser.AddRoleAsync(role);
await ReplyAsync(transContext.GetString("AddedUserFromRole", role.Name));
return;
}
await ReplyAsync(transContext.GetString("RoleNotFound"));
}
catch (HttpException e)
{
await _errorHandler.HandleHttpException(e, Context);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[RequireUserPermission(GuildPermission.ManageRoles)]
[Command("add", RunMode = RunMode.Async)]
[Summary("Add a role to the whitelist.")]
public async Task AddRole([Summary("@role")] IRole role, [Summary("alias")] string roleName)
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
if (role.IsManaged)
{
await ReplyAsync(transContext.GetString("CannotAddManagedRole"));
return;
}
if (role.Permissions.ManageRoles
|| role.Permissions.Administrator
|| role.Permissions.ManageGuild
|| role.Permissions.BanMembers
|| role.Permissions.KickMembers)
{
await ReplyAsync(transContext.GetString("CannotAddDangerousRole"));
return;
}
_database.RoleSelfService.Add(new RoleSelfServiceModel
{
GuildId = Context.Guild.Id.AsLong(),
RoleId = role.Id.AsLong(),
WhiteListName = roleName
});
await _database.SaveChangesAsync();
await ReplyAsync(transContext.GetString("AddedRoleToWhitelist", role.Name));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[RequireUserPermission(GuildPermission.ManageRoles)]
[Command("remove", RunMode = RunMode.Async)]
[Summary("Remove a role from the whitelist.")]
public async Task RemoveRole([Summary("role-nickname")] string roleName)
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
var roleFromDb = _database.RoleSelfService.FirstOrDefault(e =>
e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.WhiteListName.Equals(roleName));
if (roleFromDb != null)
{
_database.RoleSelfService.Remove(roleFromDb);
await _database.SaveChangesAsync();
await ReplyAsync(transContext.GetString("RemovedRoleFromWhitelist", roleName));
return;
}
await ReplyAsync(transContext.GetString("RoleNotFound"));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[RequireUserPermission(GuildPermission.ManageRoles)]
[Summary("Give a role by clicking on an emoji")]
[Command("listen", RunMode = RunMode.Async)]
public async Task AddListener([Summary("message-ID")] string messageIdStr, [Summary("Emoji")] string emoji, [Summary("@role")] IRole role)
{
try
{
var messageId = ulong.Parse(messageIdStr);
var message = (IUserMessage) await Context.Channel.GetMessageAsync(messageId);
var emote = _reactionListener.ConvertStringToEmote(emoji);
await message.AddReactionAsync(emote);
await _reactionListener.AddRoleToListener(messageId, Context.Guild.Id, emoji, role);
await Context.Message.DeleteAsync();
}
catch (HttpException e)
{
await Context.Channel.SendMessageAsync("Custom emojis from other servers are not supported");
Console.WriteLine(e);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,76 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using PokeAPI;
namespace Geekbot.Bot.Commands.Games
{
public class Pokedex : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Pokedex(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("pokedex", RunMode = RunMode.Async)]
[Summary("A Pokedex Tool")]
public async Task GetPokemon([Summary("pokemon-name")] string pokemonName)
{
try
{
DataFetcher.ShouldCacheData = true;
Pokemon pokemon;
try
{
pokemon = await DataFetcher.GetNamedApiObject<Pokemon>(pokemonName.ToLower());
}
catch
{
await ReplyAsync("I couldn't find that pokemon :confused:");
return;
}
var embed = await PokemonEmbedBuilder(pokemon);
await ReplyAsync("", false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task<EmbedBuilder> PokemonEmbedBuilder(Pokemon pokemon)
{
var eb = new EmbedBuilder();
var species = await DataFetcher.GetApiObject<PokemonSpecies>(pokemon.ID);
eb.Title = $"#{pokemon.ID} {ToUpper(pokemon.Name)}";
eb.Description = species.FlavorTexts[1].FlavorText;
eb.ThumbnailUrl = pokemon.Sprites.FrontMale ?? pokemon.Sprites.FrontFemale;
eb.AddInlineField(GetSingularOrPlural(pokemon.Types.Length, "Type"),
string.Join(", ", pokemon.Types.Select(t => ToUpper(t.Type.Name))));
eb.AddInlineField(GetSingularOrPlural(pokemon.Abilities.Length, "Ability"),
string.Join(", ", pokemon.Abilities.Select(t => ToUpper(t.Ability.Name))));
eb.AddInlineField("Height", pokemon.Height);
eb.AddInlineField("Weight", pokemon.Mass);
return eb;
}
private string GetSingularOrPlural(int lenght, string word)
{
if (lenght == 1) return word;
return word.EndsWith("y") ? $"{word.Remove(word.Length - 1)}ies" : $"{word}s";
}
private string ToUpper(string s)
{
if (string.IsNullOrEmpty(s)) return string.Empty;
return char.ToUpper(s[0]) + s.Substring(1);
}
}
}

View file

@ -0,0 +1,98 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.KvInMemoryStore;
using Geekbot.Core.Localization;
using Geekbot.Core.RandomNumberGenerator;
namespace Geekbot.Bot.Commands.Games.Roll
{
public class Roll : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IKvInMemoryStore _kvInMemoryStore;
private readonly ITranslationHandler _translation;
private readonly DatabaseContext _database;
private readonly IRandomNumberGenerator _randomNumberGenerator;
public Roll(IKvInMemoryStore kvInMemoryStore,IErrorHandler errorHandler, ITranslationHandler translation, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator)
{
_kvInMemoryStore = kvInMemoryStore;
_translation = translation;
_database = database;
_randomNumberGenerator = randomNumberGenerator;
_errorHandler = errorHandler;
}
[Command("roll", RunMode = RunMode.Async)]
[Summary("Guess which number the bot will roll (1-100")]
public async Task RollCommand([Remainder] [Summary("guess")] string stuff = null)
{
try
{
var number = _randomNumberGenerator.Next(1, 100);
int.TryParse(stuff, out var guess);
var transContext = await _translation.GetGuildContext(Context);
if (guess <= 100 && guess > 0)
{
var kvKey = $"{Context.Guild.Id}:{Context.User.Id}:RollsPrevious";
var prevRoll = _kvInMemoryStore.Get<RollTimeout>(kvKey);
if (prevRoll?.LastGuess == guess && prevRoll?.GuessedOn.AddDays(1) > DateTime.Now)
{
await ReplyAsync(transContext.GetString(
"NoPrevGuess",
Context.Message.Author.Mention,
transContext.FormatDateTimeAsRemaining(prevRoll.GuessedOn.AddDays(1))));
return;
}
_kvInMemoryStore.Set(kvKey, new RollTimeout { LastGuess = guess, GuessedOn = DateTime.Now });
await ReplyAsync(transContext.GetString("Rolled", Context.Message.Author.Mention, number, guess));
if (guess == number)
{
await ReplyAsync(transContext.GetString("Gratz", Context.Message.Author));
var user = await GetUser(Context.User.Id);
user.Rolls += 1;
_database.Rolls.Update(user);
await _database.SaveChangesAsync();
}
}
else
{
await ReplyAsync(transContext.GetString("RolledNoGuess", Context.Message.Author.Mention, number));
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task<RollsModel> GetUser(ulong userId)
{
var user = _database.Rolls.FirstOrDefault(u =>u.GuildId.Equals(Context.Guild.Id.AsLong()) && u.UserId.Equals(userId.AsLong())) ?? await CreateNewRow(userId);
return user;
}
private async Task<RollsModel> CreateNewRow(ulong userId)
{
var user = new RollsModel()
{
GuildId = Context.Guild.Id.AsLong(),
UserId = userId.AsLong(),
Rolls = 0
};
var newUser = _database.Rolls.Add(user).Entity;
await _database.SaveChangesAsync();
return newUser;
}
}
}

View file

@ -0,0 +1,10 @@
using System;
namespace Geekbot.Bot.Commands.Games.Roll
{
public class RollTimeout
{
public int LastGuess { get; set; }
public DateTime GuessedOn { get; set; }
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Integrations.LolMmr
{
public class LolMmr : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public LolMmr(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("mmr", RunMode = RunMode.Async)]
[Summary("Get the League of Legends MMR for a specified summoner")]
public async Task GetMmr([Remainder] [Summary("summoner")] string summonerName)
{
try
{
LolMmrDto data;
try
{
var name = HttpUtility.UrlEncode(summonerName.ToLower());
var httpClient = HttpAbstractions.CreateDefaultClient();
// setting the user agent in accordance with the whatismymmr.com api rules
httpClient.DefaultRequestHeaders.Remove("User-Agent");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Linux:rocks.pizzaandcoffee.geekbot:v0.0.0");
data = await HttpAbstractions.Get<LolMmrDto>(new Uri($"https://euw.whatismymmr.com/api/v1/summoner?name={name}"), httpClient);
}
catch (HttpRequestException e)
{
if (e.StatusCode != HttpStatusCode.NotFound) throw e;
await Context.Channel.SendMessageAsync("Player not found");
return;
}
var sb = new StringBuilder();
sb.AppendLine($"**MMR for {summonerName}**");
sb.AppendLine($"Normal: {data.Normal.Avg}");
sb.AppendLine($"Ranked: {data.Ranked.Avg}");
sb.AppendLine($"ARAM: {data.ARAM.Avg}");
await Context.Channel.SendMessageAsync(sb.ToString());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Geekbot.Bot.Commands.Integrations.LolMmr
{
public class LolMmrDto
{
public LolMrrInfoDto Ranked { get; set; }
public LolMrrInfoDto Normal { get; set; }
public LolMrrInfoDto ARAM { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace Geekbot.Bot.Commands.Integrations.LolMmr
{
public class LolMrrInfoDto
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public decimal Avg { get; set; } = 0;
}
}

View file

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.Converters;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using MtgApiManager.Lib.Service;
namespace Geekbot.Bot.Commands.Integrations
{
public class MagicTheGathering : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IMtgManaConverter _manaConverter;
public MagicTheGathering(IErrorHandler errorHandler, IMtgManaConverter manaConverter)
{
_errorHandler = errorHandler;
_manaConverter = manaConverter;
}
[Command("mtg", RunMode = RunMode.Async)]
[Summary("Find a Magic The Gathering Card.")]
public async Task GetCard([Remainder] [Summary("card-name")] string cardName)
{
try
{
var message = await Context.Channel.SendMessageAsync($":mag: Looking up\"{cardName}\", please wait...");
var service = new CardService();
var result = service
.Where(x => x.Name, cardName)
// fewer cards less risk of deserialization problems, don't need more than one anyways...
.Where(x => x.PageSize, 1);
var card = result.All().Value.FirstOrDefault();
if (card == null)
{
await message.ModifyAsync(properties => properties.Content = ":red_circle: I couldn't find a card with that name...");
return;
}
var eb = new EmbedBuilder
{
Title = card.Name,
Description = card.Type
};
if (card.Colors != null) eb.WithColor(GetColor(card.Colors));
if (card.ImageUrl != null) eb.ImageUrl = card.ImageUrl.ToString();
if (!string.IsNullOrEmpty(card.Text)) eb.AddField("Text", _manaConverter.ConvertMana(card.Text));
if (!string.IsNullOrEmpty(card.Flavor)) eb.AddField("Flavor", card.Flavor);
if (!string.IsNullOrEmpty(card.SetName)) eb.AddInlineField("Set", card.SetName);
if (!string.IsNullOrEmpty(card.Power)) eb.AddInlineField("Power", card.Power);
if (!string.IsNullOrEmpty(card.Loyalty)) eb.AddInlineField("Loyality", card.Loyalty);
if (!string.IsNullOrEmpty(card.Toughness)) eb.AddInlineField("Thoughness", card.Toughness);
if (!string.IsNullOrEmpty(card.ManaCost)) eb.AddInlineField("Cost", _manaConverter.ConvertMana(card.ManaCost));
if (!string.IsNullOrEmpty(card.Rarity)) eb.AddInlineField("Rarity", card.Rarity);
if (card.Legalities != null && card.Legalities.Count > 0)
eb.AddField("Legality", string.Join(", ", card.Legalities.Select(e => e.Format)));
await message.ModifyAsync(properties =>
{
properties.Content = string.Empty;
properties.Embed = eb.Build();
});
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private Color GetColor(IEnumerable<string> colors)
{
var color = colors.FirstOrDefault();
return color switch
{
"Black" => new Color(203, 194, 191),
"White" => new Color(255, 251, 213),
"Blue" => new Color(170, 224, 250),
"Red" => new Color(250, 170, 143),
"Green" => new Color(155, 211, 174),
_ => new Color(204, 194, 212)
};
}
}
}

View file

@ -0,0 +1,128 @@
using System;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using Discord;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.MalClient;
namespace Geekbot.Bot.Commands.Integrations
{
public class Mal : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IMalClient _malClient;
public Mal(IMalClient malClient, IErrorHandler errorHandler)
{
_malClient = malClient;
_errorHandler = errorHandler;
}
[Command("anime", RunMode = RunMode.Async)]
[Summary("Show Info about an Anime.")]
public async Task SearchAnime([Remainder] [Summary("anime-name")] string animeName)
{
try
{
if (_malClient.IsLoggedIn())
{
var anime = await _malClient.GetAnime(animeName);
if (anime != null)
{
var eb = new EmbedBuilder();
var description = HttpUtility.HtmlDecode(anime.Synopsis)
.Replace("<br />", "")
.Replace("[i]", "*")
.Replace("[/i]", "*");
eb.Title = anime.Title;
eb.Description = description;
eb.ImageUrl = anime.Image;
eb.AddInlineField("Premiered", $"{anime.StartDate}");
eb.AddInlineField("Ended", anime.EndDate == "0000-00-00" ? "???" : anime.EndDate);
eb.AddInlineField("Status", anime.Status);
eb.AddInlineField("Episodes", anime.Episodes);
eb.AddInlineField("MAL Score", anime.Score);
eb.AddInlineField("Type", anime.Type);
eb.AddField("MAL Link", $"https://myanimelist.net/anime/{anime.Id}");
await ReplyAsync("", false, eb.Build());
}
else
{
await ReplyAsync("No anime found with that name...");
}
}
else
{
await ReplyAsync(
"Unfortunally i'm not connected to MyAnimeList.net, please tell my senpai to connect me");
}
}
catch (XmlException e)
{
await _errorHandler.HandleCommandException(e, Context, "The MyAnimeList.net API refused to answer");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("manga", RunMode = RunMode.Async)]
[Summary("Show Info about a Manga.")]
public async Task SearchManga([Remainder] [Summary("manga-name")] string mangaName)
{
try
{
if (_malClient.IsLoggedIn())
{
var manga = await _malClient.GetManga(mangaName);
if (manga != null)
{
var eb = new EmbedBuilder();
var description = HttpUtility.HtmlDecode(manga.Synopsis)
.Replace("<br />", "")
.Replace("[i]", "*")
.Replace("[/i]", "*");
eb.Title = manga.Title;
eb.Description = description;
eb.ImageUrl = manga.Image;
eb.AddInlineField("Premiered", $"{manga.StartDate}");
eb.AddInlineField("Ended", manga.EndDate == "0000-00-00" ? "???" : manga.EndDate);
eb.AddInlineField("Status", manga.Status);
eb.AddInlineField("Volumes", manga.Volumes);
eb.AddInlineField("Chapters", manga.Chapters);
eb.AddInlineField("MAL Score", manga.Score);
eb.AddField("MAL Link", $"https://myanimelist.net/manga/{manga.Id}");
await ReplyAsync("", false, eb.Build());
}
else
{
await ReplyAsync("No manga found with that name...");
}
}
else
{
await ReplyAsync(
"Unfortunally i'm not connected to MyAnimeList.net, please tell my senpai to connect me");
}
}
catch (XmlException e)
{
await _errorHandler.HandleCommandException(e, Context, "The MyAnimeList.net API refused to answer");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Geekbot.Bot.Commands.Integrations.UbranDictionary
{
internal class UrbanListItemDto
{
public string Definition { get; set; }
public string Permalink { get; set; }
public string ThumbsUp { get; set; }
public string Word { get; set; }
public string Example { get; set; }
public string ThumbsDown { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Geekbot.Bot.Commands.Integrations.UbranDictionary
{
internal class UrbanResponseDto
{
public string[] Tags { get; set; }
public List<UrbanListItemDto> List { get; set; }
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Integrations.UbranDictionary
{
public class UrbanDictionary : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public UrbanDictionary(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("urban", RunMode = RunMode.Async)]
[Summary("Lookup something on urban dictionary")]
public async Task UrbanDefine([Remainder] [Summary("word")] string word)
{
try
{
var definitions = await HttpAbstractions.Get<UrbanResponseDto>(new Uri($"https://api.urbandictionary.com/v0/define?term={word}"));
if (definitions.List.Count == 0)
{
await ReplyAsync("That word hasn't been defined...");
return;
}
var definition = definitions.List.First(e => !string.IsNullOrWhiteSpace(e.Example));
var description = definition.Definition;
if (description.Length > 1801)
{
description = description.Substring(0, 1800) + " [...]";
}
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder
{
Name = definition.Word,
Url = definition.Permalink
});
eb.WithColor(new Color(239, 255, 0));
if (!string.IsNullOrEmpty(definition.Definition)) eb.Description = description;
if (!string.IsNullOrEmpty(definition.Example)) eb.AddField("Example", definition.Example ?? "(no example given...)");
if (!string.IsNullOrEmpty(definition.ThumbsUp)) eb.AddInlineField("Upvotes", definition.ThumbsUp);
if (!string.IsNullOrEmpty(definition.ThumbsDown)) eb.AddInlineField("Downvotes", definition.ThumbsDown);
if (definitions.Tags?.Length > 0) eb.AddField("Tags", string.Join(", ", definitions.Tags));
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,112 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.WikipediaClient;
using Geekbot.Core.WikipediaClient.Page;
using HtmlAgilityPack;
namespace Geekbot.Bot.Commands.Integrations
{
public class Wikipedia : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IWikipediaClient _wikipediaClient;
private readonly DatabaseContext _database;
public Wikipedia(IErrorHandler errorHandler, IWikipediaClient wikipediaClient, DatabaseContext database)
{
_errorHandler = errorHandler;
_wikipediaClient = wikipediaClient;
_database = database;
}
[Command("wiki", RunMode = RunMode.Async)]
[Summary("Get an article from wikipedia.")]
public async Task GetPreview([Remainder] [Summary("article")] string articleName)
{
try
{
var wikiLang = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(Context.Guild.Id.AsLong()))?.WikiLang;
if (string.IsNullOrEmpty(wikiLang))
{
wikiLang = "en";
}
var article = await _wikipediaClient.GetPreview(articleName.Replace(" ", "_"), wikiLang);
if (article.Type != PageTypes.Standard)
{
switch (article.Type)
{
case PageTypes.Disambiguation:
await ReplyAsync($"**__Disambiguation__**\r\n{DisambiguationExtractor(article.ExtractHtml)}");
break;
case PageTypes.MainPage:
await ReplyAsync("The main page is not supported");
break;
case PageTypes.NoExtract:
await ReplyAsync($"This page has no summary, here is the link: {article.ContentUrls.Desktop.Page}");
break;
case PageTypes.Standard:
break;
default:
await ReplyAsync($"This page type is currently not supported, here is the link: {article.ContentUrls.Desktop.Page}");
break;
}
return;
}
var eb = new EmbedBuilder
{
Title = article.Title,
Description = article.Description,
ImageUrl = article.Thumbnail?.Source.ToString(),
Url = article.ContentUrls.Desktop.Page.ToString(),
Color = new Color(246,246,246),
Timestamp = article.Timestamp,
Footer = new EmbedFooterBuilder
{
Text = "Last Edit",
IconUrl = "http://icons.iconarchive.com/icons/sykonist/popular-sites/256/Wikipedia-icon.png"
}
};
eb.AddField("Description", article.Extract);
if (article.Coordinates != null) eb.AddField("Coordinates", $"{article.Coordinates.Lat} Lat {article.Coordinates.Lon} Lon");
await ReplyAsync("", false, eb.Build());
}
catch (HttpRequestException)
{
await ReplyAsync("I couldn't find that article");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private string DisambiguationExtractor(string extractHtml)
{
var doc = new HtmlDocument();
doc.LoadHtml(extractHtml);
var nodes = doc.DocumentNode.SelectNodes("//li");
if (nodes == null) return "(List is to long to show)";
var sb = new StringBuilder();
foreach (var node in nodes)
{
var split = node.InnerText.Split(',');
var title = split.First();
var desc = string.Join(",", split.Skip(1));
sb.AppendLine($"• **{title}** -{desc}");
}
return sb.ToString();
}
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GlobalSettings;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
namespace Geekbot.Bot.Commands.Integrations
{
public class Youtube : ModuleBase
{
private readonly IGlobalSettings _globalSettings;
private readonly IErrorHandler _errorHandler;
public Youtube(IGlobalSettings globalSettings, IErrorHandler errorHandler)
{
_globalSettings = globalSettings;
_errorHandler = errorHandler;
}
[Command("yt", RunMode = RunMode.Async)]
[Summary("Search for something on youtube.")]
public async Task Yt([Remainder] [Summary("title")] string searchQuery)
{
var key = _globalSettings.GetKey("YoutubeKey");
if (string.IsNullOrEmpty(key))
{
await ReplyAsync("No youtube key set, please tell my senpai to set one");
return;
}
try
{
var youtubeService = new YouTubeService(new BaseClientService.Initializer
{
ApiKey = key,
ApplicationName = GetType().ToString()
});
var searchListRequest = youtubeService.Search.List("snippet");
searchListRequest.Q = searchQuery;
searchListRequest.MaxResults = 2;
var searchListResponse = await searchListRequest.ExecuteAsync();
var result = searchListResponse.Items[0];
await ReplyAsync(
$"\"{result.Snippet.Title}\" from \"{result.Snippet.ChannelTitle}\" https://youtu.be/{result.Id.VideoId}");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.RandomNumberGenerator;
namespace Geekbot.Bot.Commands.Randomness
{
public class BenedictCumberbatchNameGenerator : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IRandomNumberGenerator _randomNumberGenerator;
public BenedictCumberbatchNameGenerator(IErrorHandler errorHandler, IRandomNumberGenerator randomNumberGenerator)
{
_errorHandler = errorHandler;
_randomNumberGenerator = randomNumberGenerator;
}
[Command("bdcb", RunMode = RunMode.Async)]
[Summary("Benedict Cumberbatch Name Generator")]
public async Task GetQuote()
{
try
{
var firstnameList = new List<string>
{
"Bumblebee", "Bandersnatch", "Broccoli", "Rinkydink", "Bombadil", "Boilerdang", "Bandicoot", "Fragglerock", "Muffintop", "Congleton", "Blubberdick", "Buffalo", "Benadryl",
"Butterfree", "Burberry", "Whippersnatch", "Buttermilk", "Beezlebub", "Budapest", "Boilerdang", "Blubberwhale", "Bumberstump", "Bulbasaur", "Cogglesnatch", "Liverswort",
"Bodybuild", "Johnnycash", "Bendydick", "Burgerking", "Bonaparte", "Bunsenburner", "Billiardball", "Bukkake", "Baseballmitt", "Blubberbutt", "Baseballbat", "Rumblesack",
"Barister", "Danglerack", "Rinkydink", "Bombadil", "Honkytonk", "Billyray", "Bumbleshack", "Snorkeldink", "Beetlejuice", "Bedlington", "Bandicoot", "Boobytrap", "Blenderdick",
"Bentobox", "Pallettown", "Wimbledon", "Buttercup", "Blasphemy", "Syphilis", "Snorkeldink", "Brandenburg", "Barbituate", "Snozzlebert", "Tiddleywomp", "Bouillabaisse",
"Wellington", "Benetton", "Bendandsnap", "Timothy", "Brewery", "Bentobox", "Brandybuck", "Benjamin", "Buckminster", "Bourgeoisie", "Bakery", "Oscarbait", "Buckyball",
"Bourgeoisie", "Burlington", "Buckingham", "Barnoldswick", "Bumblesniff", "Butercup", "Bubblebath", "Fiddlestick", "Bulbasaur", "Bumblebee", "Bettyboop", "Botany", "Cadbury",
"Brendadirk", "Buckingham", "Barnabus", "Barnacle", "Billybong", "Botany", "Benddadick", "Benderchick"
};
var lastnameList = new List<string>
{
"Coddleswort", "Crumplesack", "Curdlesnoot", "Calldispatch", "Humperdinck", "Rivendell", "Cuttlefish", "Lingerie", "Vegemite", "Ampersand", "Cumberbund", "Candycrush",
"Clombyclomp", "Cragglethatch", "Nottinghill", "Cabbagepatch", "Camouflage", "Creamsicle", "Curdlemilk", "Upperclass", "Frumblesnatch", "Crumplehorn", "Talisman", "Candlestick",
"Chesterfield", "Bumbersplat", "Scratchnsniff", "Snugglesnatch", "Charizard", "Carrotstick", "Cumbercooch", "Crackerjack", "Crucifix", "Cuckatoo", "Cockletit", "Collywog",
"Capncrunch", "Covergirl", "Cumbersnatch", "Countryside", "Coggleswort", "Splishnsplash", "Copperwire", "Animorph", "Curdledmilk", "Cheddarcheese", "Cottagecheese", "Crumplehorn",
"Snickersbar", "Banglesnatch", "Stinkyrash", "Cameltoe", "Chickenbroth", "Concubine", "Candygram", "Moldyspore", "Chuckecheese", "Cankersore", "Crimpysnitch", "Wafflesmack",
"Chowderpants", "Toodlesnoot", "Clavichord", "Cuckooclock", "Oxfordshire", "Cumbersome", "Chickenstrips", "Battleship", "Commonwealth", "Cunningsnatch", "Custardbath",
"Kryptonite", "Curdlesnoot", "Cummerbund", "Coochyrash", "Crackerdong", "Crackerdong", "Curdledong", "Crackersprout", "Crumplebutt", "Colonist", "Coochierash", "Anglerfish",
"Cumbersniff", "Charmander", "Scratch-n-sniff", "Cumberbitch", "Pumpkinpatch", "Cramplesnutch", "Lumberjack", "Bonaparte", "Cul-de-sac", "Cankersore", "Cucumbercatch", "Contradict"
};
var lastname = lastnameList[_randomNumberGenerator.Next(0, lastnameList.Count - 1)];
var firstname = firstnameList[_randomNumberGenerator.Next(0, firstnameList.Count - 1)];
await ReplyAsync($"{firstname} {lastname}");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,38 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Cat
{
public class Cat : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Cat(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("cat", RunMode = RunMode.Async)]
[Summary("Return a random image of a cat.")]
public async Task Say()
{
try
{
var response = await HttpAbstractions.Get<CatResponseDto>(new Uri("https://aws.random.cat/meow"));
var eb = new EmbedBuilder
{
ImageUrl = response.File
};
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.Bot.Commands.Randomness.Cat
{
internal class CatResponseDto
{
public string File { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.Bot.Commands.Randomness.Chuck
{
internal class ChuckNorrisJokeResponseDto
{
public string Value { get; set; }
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Chuck
{
public class ChuckNorrisJokes : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public ChuckNorrisJokes(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("chuck", RunMode = RunMode.Async)]
[Summary("A random chuck norris joke")]
public async Task Say()
{
try
{
try
{
var response = await HttpAbstractions.Get<ChuckNorrisJokeResponseDto>(new Uri("https://api.chucknorris.io/jokes/random"));
await ReplyAsync(response.Value);
}
catch (HttpRequestException)
{
await ReplyAsync("Api down...");
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.Bot.Commands.Randomness.Dad
{
internal class DadJokeResponseDto
{
public string Joke { get; set; }
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Dad
{
public class DadJokes : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public DadJokes(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("dad", RunMode = RunMode.Async)]
[Summary("A random dad joke")]
public async Task Say()
{
try
{
var response = await HttpAbstractions.Get<DadJokeResponseDto>(new Uri("https://icanhazdadjoke.com/"));
await ReplyAsync(response.Joke);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,38 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Dog
{
public class Dog : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Dog(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("dog", RunMode = RunMode.Async)]
[Summary("Return a random image of a dog.")]
public async Task Say()
{
try
{
var response = await HttpAbstractions.Get<DogResponseDto>(new Uri("http://random.dog/woof.json"));
var eb = new EmbedBuilder
{
ImageUrl = response.Url
};
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.Bot.Commands.Randomness.Dog
{
internal class DogResponseDto
{
public string Url { get; set; }
}
}

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness
{
public class EightBall : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public EightBall(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("8ball", RunMode = RunMode.Async)]
[Summary("Ask 8Ball a Question.")]
public async Task Ball([Remainder] [Summary("question")] string echo)
{
try
{
var replies = new List<string>
{
"It is certain",
"It is decidedly so",
"Without a doubt",
"Yes, definitely",
"You may rely on it",
"As I see it, yes",
"Most likely",
"Outlook good",
"Yes",
"Signs point to yes",
"Reply hazy try again",
"Ask again later",
"Better not tell you now",
"Cannot predict now",
"Concentrate and ask again",
"Don't count on it",
"My reply is no",
"My sources say no",
"Outlook not so good",
"Very doubtful"
};
var answer = new Random().Next(replies.Count);
await ReplyAsync(replies[answer]);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,23 @@
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.Media;
namespace Geekbot.Bot.Commands.Randomness
{
public class Fortune : ModuleBase
{
private readonly IFortunesProvider _fortunes;
public Fortune(IFortunesProvider fortunes)
{
_fortunes = fortunes;
}
[Command("fortune", RunMode = RunMode.Async)]
[Summary("Get a random fortune")]
public async Task GetAFortune()
{
await ReplyAsync(_fortunes.GetRandomFortune());
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness
{
public class Gdq : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Gdq(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("gdq", RunMode = RunMode.Async)]
[Summary("Get a quote from the GDQ donation generator.")]
public async Task GetQuote()
{
try
{
using var client = new WebClient();
var url = new Uri("http://taskinoz.com/gdq/api/");
var response = client.DownloadString(url);
await ReplyAsync(response);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,11 @@
namespace Geekbot.Bot.Commands.Randomness.Greetings
{
public class GreetingBaseDto
{
public string Language { get; set; }
public string LanguageNative { get; set; }
public string LanguageCode { get; set; }
public string Script { get; set; }
public GreetingDto Primary { get; set; }
}
}

View file

@ -0,0 +1,10 @@
namespace Geekbot.Bot.Commands.Randomness.Greetings
{
public class GreetingDto
{
public string Text { get; set; }
public string Dialect { get; set; }
public string Romanization { get; set; }
public string[] Use { get; set; }
}
}

View file

@ -0,0 +1,51 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Randomness.Greetings
{
public class Greetings : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Greetings(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("hello", RunMode = RunMode.Async)]
[Alias("greeting", "hi", "hallo")]
[Summary("Say hello to the bot and get a reply in a random language")]
public async Task GetGreeting()
{
try
{
var greeting = await HttpAbstractions.Get<GreetingBaseDto>(new Uri("https://api.greetings.dev/v1/greeting"));
var eb = new EmbedBuilder();
eb.Title = greeting.Primary.Text;
eb.AddInlineField("Language", greeting.Language);
if (greeting.Primary.Dialect != null)
{
eb.AddInlineField("Dialect", greeting.Primary.Dialect);
}
if (greeting.Primary.Romanization != null)
{
eb.AddInlineField("Roman", greeting.Primary.Romanization);
}
await ReplyAsync(string.Empty, false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Randomness.Kanye
{
public class Kanye : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Kanye(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("kanye", RunMode = RunMode.Async)]
[Summary("A random kayne west quote")]
public async Task Say()
{
try
{
var response = await HttpAbstractions.Get<KanyeResponseDto>(new Uri("https://api.kanye.rest/"));
await ReplyAsync(response.Quote);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,8 @@
namespace Geekbot.Bot.Commands.Randomness.Kanye
{
public class KanyeResponseDto
{
public string Id { get; set; }
public string Quote { get; set; }
}
}

View file

@ -0,0 +1,80 @@
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.Media;
namespace Geekbot.Bot.Commands.Randomness
{
public class RandomAnimals : ModuleBase
{
private readonly IMediaProvider _mediaProvider;
public RandomAnimals(IMediaProvider mediaProvider)
{
_mediaProvider = mediaProvider;
}
[Command("panda", RunMode = RunMode.Async)]
[Summary("Get a random panda image")]
public async Task Panda()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Panda)));
}
[Command("croissant", RunMode = RunMode.Async)]
[Alias("gipfeli")]
[Summary("Get a random croissant image")]
public async Task Croissant()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Croissant)));
}
[Command("pumpkin", RunMode = RunMode.Async)]
[Summary("Get a random pumpkin image")]
public async Task Pumpkin()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Pumpkin)));
}
[Command("squirrel", RunMode = RunMode.Async)]
[Summary("Get a random squirrel image")]
public async Task Squirrel()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Squirrel)));
}
[Command("turtle", RunMode = RunMode.Async)]
[Summary("Get a random turtle image")]
public async Task Turtle()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Turtle)));
}
[Command("penguin", RunMode = RunMode.Async)]
[Alias("pengu")]
[Summary("Get a random penguin image")]
public async Task Penguin()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Penguin)));
}
[Command("fox", RunMode = RunMode.Async)]
[Summary("Get a random fox image")]
public async Task Fox()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Fox)));
}
[Command("dab", RunMode = RunMode.Async)]
[Summary("Get a random dab image")]
public async Task Dab()
{
await ReplyAsync("", false, Eb(_mediaProvider.GetMedia(MediaType.Dab)));
}
private static Embed Eb(string image)
{
return new EmbedBuilder {ImageUrl = image}.Build();
}
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Localization;
using Geekbot.Core.RandomNumberGenerator;
namespace Geekbot.Bot.Commands.Randomness
{
public class Ship : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IRandomNumberGenerator _randomNumberGenerator;
private readonly ITranslationHandler _translation;
private readonly DatabaseContext _database;
public Ship(DatabaseContext database, IErrorHandler errorHandler, IRandomNumberGenerator randomNumberGenerator, ITranslationHandler translation)
{
_database = database;
_errorHandler = errorHandler;
_randomNumberGenerator = randomNumberGenerator;
_translation = translation;
}
[Command("Ship", RunMode = RunMode.Async)]
[Summary("Ask the Shipping meter")]
public async Task Command([Summary("@user1")] IUser user1, [Summary("@user2")] IUser user2)
{
try
{
var userKeys = user1.Id < user2.Id
? new Tuple<long, long>(user1.Id.AsLong(), user2.Id.AsLong())
: new Tuple<long, long>(user2.Id.AsLong(), user1.Id.AsLong());
var dbval = _database.Ships.FirstOrDefault(s =>
s.FirstUserId.Equals(userKeys.Item1) &&
s.SecondUserId.Equals(userKeys.Item2));
var shippingRate = 0;
if (dbval == null)
{
shippingRate = _randomNumberGenerator.Next(1, 100);
_database.Ships.Add(new ShipsModel()
{
FirstUserId = userKeys.Item1,
SecondUserId = userKeys.Item2,
Strength = shippingRate
});
await _database.SaveChangesAsync();
}
else
{
shippingRate = dbval.Strength;
}
var transContext = await _translation.GetGuildContext(Context);
var reply = $":heartpulse: **{transContext.GetString("Matchmaking")}** :heartpulse:\r\n";
reply += $":two_hearts: {user1.Mention} :heart: {user2.Mention} :two_hearts:\r\n";
reply += $"0% [{BlockCounter(shippingRate)}] 100% - {DeterminateSuccess(shippingRate, transContext)}";
await ReplyAsync(reply);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private string DeterminateSuccess(int rate, TranslationGuildContext transContext)
{
return (rate / 20) switch
{
0 => transContext.GetString("NotGonnaToHappen"),
1 => transContext.GetString("NotSuchAGoodIdea"),
2 => transContext.GetString("ThereMightBeAChance"),
3 => transContext.GetString("CouldWork"),
4 => transContext.GetString("ItsAMatch"),
_ => "nope"
};
}
private string BlockCounter(int rate)
{
var amount = rate / 10;
Console.WriteLine(amount);
var blocks = "";
for (var i = 1; i <= 10; i++)
if (i <= amount)
{
blocks += ":white_medium_small_square:";
if (i == amount)
blocks += $" {rate}% ";
}
else
{
blocks += ":black_medium_small_square:";
}
return blocks;
}
}
}

View file

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Randomness
{
public class Slap : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
public Slap(IErrorHandler errorHandler, DatabaseContext database)
{
_errorHandler = errorHandler;
_database = database;
}
[Command("slap", RunMode = RunMode.Async)]
[Summary("slap someone")]
public async Task Slapper([Summary("@someone")] IUser user)
{
try
{
if (user.Id == Context.User.Id)
{
await ReplyAsync("Why would you slap yourself?");
return;
}
var things = new List<string>
{
"thing",
"rubber chicken",
"leek stick",
"large trout",
"flat hand",
"strip of bacon",
"feather",
"piece of pizza",
"moldy banana",
"sharp retort",
"printed version of wikipedia",
"panda paw",
"spiked sledgehammer",
"monstertruck",
"dirty toilet brush",
"sleeping seagull",
"sunflower",
"mousepad",
"lolipop",
"bottle of rum",
"cheese slice",
"critical 1",
"natural 20",
"mjölnir (aka mewmew)",
"kamehameha",
"copy of Twilight",
"med pack (get ready for the end boss)",
"derp",
"condom (used)",
"gremlin fed after midnight",
"wet baguette",
"exploding kitten",
"shiny piece of shit",
"mismatched pair of socks",
"horcrux",
"tuna",
"suggestion",
"teapot",
"candle",
"dictionary",
"powerless banhammer"
};
await ReplyAsync($"{Context.User.Username} slapped {user.Username} with a {things[new Random().Next(things.Count - 1)]}");
await UpdateRecieved(user.Id);
await UpdateGiven(Context.User.Id);
await _database.SaveChangesAsync();
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task UpdateGiven(ulong userId)
{
var user = await GetUser(userId);
user.Given++;
_database.Slaps.Update(user);
}
private async Task UpdateRecieved(ulong userId)
{
var user = await GetUser(userId);
user.Recieved++;
_database.Slaps.Update(user);
}
private async Task<SlapsModel> GetUser(ulong userId)
{
var user = _database.Slaps.FirstOrDefault(e =>
e.GuildId.Equals(Context.Guild.Id.AsLong()) &&
e.UserId.Equals(userId.AsLong())
);
if (user != null) return user;
_database.Slaps.Add(new SlapsModel
{
GuildId = Context.Guild.Id.AsLong(),
UserId = userId.AsLong(),
Recieved = 0,
Given = 0
});
await _database.SaveChangesAsync();
return _database.Slaps.FirstOrDefault(e =>
e.GuildId.Equals(Context.Guild.Id.AsLong()) &&
e.UserId.Equals(userId.AsLong()));
}
}
}

View file

@ -0,0 +1,161 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Localization;
using Geekbot.Core.RandomNumberGenerator;
namespace Geekbot.Bot.Commands.Rpg
{
[DisableInDirectMessage]
[Group("cookies")]
[Alias("cookie")]
public class Cookies : ModuleBase
{
private readonly DatabaseContext _database;
private readonly IErrorHandler _errorHandler;
private readonly ITranslationHandler _translation;
private readonly IRandomNumberGenerator _randomNumberGenerator;
public Cookies(DatabaseContext database, IErrorHandler errorHandler, ITranslationHandler translation , IRandomNumberGenerator randomNumberGenerator)
{
_database = database;
_errorHandler = errorHandler;
_translation = translation;
_randomNumberGenerator = randomNumberGenerator;
}
[Command("get", RunMode = RunMode.Async)]
[Summary("Get a cookie every 24 hours")]
public async Task GetCookies()
{
try
{
var transContext = await _translation.GetGuildContext(Context);
var actor = await GetUser(Context.User.Id);
if (actor.LastPayout.Value.AddDays(1).Date > DateTime.Now.Date)
{
var formatedWaitTime = transContext.FormatDateTimeAsRemaining(DateTimeOffset.Now.AddDays(1).Date);
await ReplyAsync(transContext.GetString("WaitForMoreCookies", formatedWaitTime));
return;
}
actor.Cookies += 10;
actor.LastPayout = DateTimeOffset.Now;
await SetUser(actor);
await ReplyAsync(transContext.GetString("GetCookies", 10, actor.Cookies));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("jar", RunMode = RunMode.Async)]
[Summary("Look at your cookie jar")]
public async Task PeekIntoCookieJar()
{
try
{
var transContext = await _translation.GetGuildContext(Context);
var actor = await GetUser(Context.User.Id);
await ReplyAsync(transContext.GetString("InYourJar", actor.Cookies));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("give", RunMode = RunMode.Async)]
[Summary("Give cookies to someone")]
public async Task GiveACookie([Summary("@someone")] IUser user, [Summary("amount")] int amount = 1)
{
try
{
var transContext = await _translation.GetGuildContext(Context);
var giver = await GetUser(Context.User.Id);
if (giver.Cookies < amount)
{
await ReplyAsync(transContext.GetString("NotEnoughToGive"));
return;
}
var taker = await GetUser(user.Id);
giver.Cookies -= amount;
taker.Cookies += amount;
await SetUser(giver);
await SetUser(taker);
await ReplyAsync(transContext.GetString("Given", amount, user.Username));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("eat", RunMode = RunMode.Async)]
[Summary("Eat a cookie")]
public async Task EatACookie()
{
try
{
var transContext = await _translation.GetGuildContext(Context);
var actor = await GetUser(Context.User.Id);
if (actor.Cookies < 5)
{
await ReplyAsync(transContext.GetString("NotEnoughCookiesToEat"));
return;
}
var amount = _randomNumberGenerator.Next(1, 5);
actor.Cookies -= amount;
await SetUser(actor);
await ReplyAsync(transContext.GetString("AteCookies", amount, actor.Cookies));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task<CookiesModel> GetUser(ulong userId)
{
var user = _database.Cookies.FirstOrDefault(u =>u.GuildId.Equals(Context.Guild.Id.AsLong()) && u.UserId.Equals(userId.AsLong())) ?? await CreateNewRow(userId);
return user;
}
private async Task SetUser(CookiesModel user)
{
_database.Cookies.Update(user);
await _database.SaveChangesAsync();
}
private async Task<CookiesModel> CreateNewRow(ulong userId)
{
var user = new CookiesModel()
{
GuildId = Context.Guild.Id.AsLong(),
UserId = userId.AsLong(),
Cookies = 0,
LastPayout = DateTimeOffset.MinValue
};
var newUser = _database.Cookies.Add(user).Entity;
await _database.SaveChangesAsync();
return newUser;
}
}
}

View file

@ -0,0 +1,60 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Levels;
namespace Geekbot.Bot.Commands.User
{
public class GuildInfo : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
private readonly ILevelCalc _levelCalc;
public GuildInfo(DatabaseContext database, ILevelCalc levelCalc, IErrorHandler errorHandler)
{
_database = database;
_levelCalc = levelCalc;
_errorHandler = errorHandler;
}
[Command("serverstats", RunMode = RunMode.Async)]
[Summary("Show some info about the bot.")]
[DisableInDirectMessage]
public async Task GetInfo()
{
try
{
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(Context.Guild.IconUrl)
.WithName(Context.Guild.Name));
eb.WithColor(new Color(110, 204, 147));
var created = Context.Guild.CreatedAt;
var age = Math.Floor((DateTime.Now - created).TotalDays);
var messages = _database.Messages
.Where(e => e.GuildId == Context.Guild.Id.AsLong())
.Sum(e => e.MessageCount);
var level = _levelCalc.GetLevel(messages);
eb.AddField("Server Age", $"{created.Day}/{created.Month}/{created.Year} ({age} days)");
eb.AddInlineField("Level", level)
.AddInlineField("Messages", messages);
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,153 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.User
{
[DisableInDirectMessage]
public class Karma : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
private readonly ITranslationHandler _translation;
public Karma(DatabaseContext database, IErrorHandler errorHandler, ITranslationHandler translation)
{
_database = database;
_errorHandler = errorHandler;
_translation = translation;
}
[Command("good", RunMode = RunMode.Async)]
[Summary("Increase Someones Karma")]
public async Task Good([Summary("@someone")] IUser user)
{
try
{
var transContext = await _translation.GetGuildContext(Context);
var actor = await GetUser(Context.User.Id);
if (user.Id == Context.User.Id)
{
await ReplyAsync(transContext.GetString("CannotChangeOwn", Context.User.Username));
}
else if (TimeoutFinished(actor.TimeOut))
{
var formatedWaitTime = transContext.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3));
await ReplyAsync(transContext.GetString("WaitUntill", Context.User.Username, formatedWaitTime));
}
else
{
var target = await GetUser(user.Id);
target.Karma += 1;
SetUser(target);
actor.TimeOut = DateTimeOffset.Now;
SetUser(actor);
await _database.SaveChangesAsync();
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(user.GetAvatarUrl())
.WithName(user.Username));
eb.WithColor(new Color(138, 219, 146));
eb.Title = transContext.GetString("Increased");
eb.AddInlineField(transContext.GetString("By"), Context.User.Username);
eb.AddInlineField(transContext.GetString("Amount"), "+1");
eb.AddInlineField(transContext.GetString("Current"), target.Karma);
await ReplyAsync("", false, eb.Build());
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("bad", RunMode = RunMode.Async)]
[Summary("Decrease Someones Karma")]
public async Task Bad([Summary("@someone")] IUser user)
{
try
{
var transContext = await _translation.GetGuildContext(Context);
var actor = await GetUser(Context.User.Id);
if (user.Id == Context.User.Id)
{
await ReplyAsync(transContext.GetString("CannotChangeOwn", Context.User.Username));
}
else if (TimeoutFinished(actor.TimeOut))
{
var formatedWaitTime = transContext.FormatDateTimeAsRemaining(actor.TimeOut.AddMinutes(3));
await ReplyAsync(transContext.GetString("WaitUntill", Context.User.Username, formatedWaitTime));
}
else
{
var target = await GetUser(user.Id);
target.Karma -= 1;
SetUser(target);
actor.TimeOut = DateTimeOffset.Now;
SetUser(actor);
await _database.SaveChangesAsync();
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(user.GetAvatarUrl())
.WithName(user.Username));
eb.WithColor(new Color(138, 219, 146));
eb.Title = transContext.GetString("Decreased");
eb.AddInlineField(transContext.GetString("By"), Context.User.Username);
eb.AddInlineField(transContext.GetString("Amount"), "-1");
eb.AddInlineField(transContext.GetString("Current"), target.Karma);
await ReplyAsync("", false, eb.Build());
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private bool TimeoutFinished(DateTimeOffset lastKarma)
{
return lastKarma.AddMinutes(3) > DateTimeOffset.Now;
}
private async Task<KarmaModel> GetUser(ulong userId)
{
var user = _database.Karma.FirstOrDefault(u =>u.GuildId.Equals(Context.Guild.Id.AsLong()) && u.UserId.Equals(userId.AsLong())) ?? await CreateNewRow(userId);
return user;
}
private void SetUser(KarmaModel user)
{
_database.Karma.Update(user);
}
private async Task<KarmaModel> CreateNewRow(ulong userId)
{
var user = new KarmaModel()
{
GuildId = Context.Guild.Id.AsLong(),
UserId = userId.AsLong(),
Karma = 0,
TimeOut = DateTimeOffset.MinValue
};
var newUser = _database.Karma.Add(user).Entity;
await _database.SaveChangesAsync();
return newUser;
}
}
}

View file

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Converters;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Highscores;
using Geekbot.Core.Localization;
using Geekbot.Core.UserRepository;
namespace Geekbot.Bot.Commands.User.Ranking
{
public class Rank : ModuleBase
{
private readonly IEmojiConverter _emojiConverter;
private readonly IHighscoreManager _highscoreManager;
private readonly ITranslationHandler _translationHandler;
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
private readonly IUserRepository _userRepository;
public Rank(DatabaseContext database, IErrorHandler errorHandler, IUserRepository userRepository,
IEmojiConverter emojiConverter, IHighscoreManager highscoreManager, ITranslationHandler translationHandler)
{
_database = database;
_errorHandler = errorHandler;
_userRepository = userRepository;
_emojiConverter = emojiConverter;
_highscoreManager = highscoreManager;
_translationHandler = translationHandler;
}
[Command("rank", RunMode = RunMode.Async)]
[Summary("get user top 10 in messages or karma")]
[DisableInDirectMessage]
public async Task RankCmd([Summary("type")] string typeUnformated = "messages", [Summary("amount")] int amount = 10)
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
HighscoreTypes type;
try
{
type = Enum.Parse<HighscoreTypes>(typeUnformated, true);
if (!Enum.IsDefined(typeof(HighscoreTypes), type)) throw new Exception();
}
catch
{
await ReplyAsync(transContext.GetString("InvalidType"));
return;
}
var replyBuilder = new StringBuilder();
if (amount > 20)
{
await ReplyAsync(transContext.GetString("LimitingTo20Warning"));
amount = 20;
}
var guildId = Context.Guild.Id;
Dictionary<HighscoreUserDto, int> highscoreUsers;
try
{
highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount);
}
catch (HighscoreListEmptyException)
{
await ReplyAsync(transContext.GetString("NoTypeFoundForServer", type));
return;
}
var guildMessages = 0;
if (type == HighscoreTypes.messages)
{
guildMessages = _database.Messages
.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong()))
.Select(e => e.MessageCount)
.Sum();
}
var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username));
if (failedToRetrieveUser) replyBuilder.AppendLine(transContext.GetString("FailedToResolveAllUsernames"));
replyBuilder.AppendLine(transContext.GetString("HighscoresFor", type.ToString().CapitalizeFirst(), Context.Guild.Name));
var highscorePlace = 1;
foreach (var user in highscoreUsers)
{
replyBuilder.Append(highscorePlace < 11
? $"{_emojiConverter.NumberToEmoji(highscorePlace)} "
: $"`{highscorePlace}.` ");
replyBuilder.Append(user.Key.Username != null
? $"**{user.Key.Username}#{user.Key.Discriminator}**"
: $"**{user.Key.Id}**");
replyBuilder.Append(type == HighscoreTypes.messages
? $" - {user.Value} {type} - {Math.Round((double) (100 * user.Value) / guildMessages, 2)}%\n"
: $" - {user.Value} {type}\n");
highscorePlace++;
}
await ReplyAsync(replyBuilder.ToString());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,90 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Levels;
namespace Geekbot.Bot.Commands.User
{
public class Stats : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly ILevelCalc _levelCalc;
private readonly DatabaseContext _database;
public Stats(DatabaseContext database, IErrorHandler errorHandler, ILevelCalc levelCalc)
{
_database = database;
_errorHandler = errorHandler;
_levelCalc = levelCalc;
}
[Command("stats", RunMode = RunMode.Async)]
[Summary("Get information about this user")]
[DisableInDirectMessage]
public async Task User([Summary("@someone")] IUser user = null)
{
try
{
var userInfo = user ?? Context.Message.Author;
var userGuildInfo = (IGuildUser) userInfo;
var createdAt = userInfo.CreatedAt;
var joinedAt = userGuildInfo.JoinedAt.Value;
var age = Math.Floor((DateTime.Now - createdAt).TotalDays);
var joinedDayAgo = Math.Floor((DateTime.Now - joinedAt).TotalDays);
var messages = _database.Messages
?.FirstOrDefault(e => e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.UserId.Equals(userInfo.Id.AsLong()))
?.MessageCount ?? 0;
var guildMessages = _database.Messages
?.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong()))
.Select(e => e.MessageCount)
.Sum() ?? 0;
var level = _levelCalc.GetLevel(messages);
var percent = Math.Round((double) (100 * messages) / guildMessages, 2);
var cookies = _database.Cookies
?.FirstOrDefault(e => e.GuildId.Equals(Context.Guild.Id.AsLong()) && e.UserId.Equals(userInfo.Id.AsLong()))
?.Cookies ?? 0;
var eb = new EmbedBuilder();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(userInfo.GetAvatarUrl())
.WithName(userInfo.Username));
eb.WithColor(new Color(221, 255, 119));
var karma = _database.Karma.FirstOrDefault(e =>
e.GuildId.Equals(Context.Guild.Id.AsLong()) &&
e.UserId.Equals(userInfo.Id.AsLong()));
var correctRolls = _database.Rolls.FirstOrDefault(e =>
e.GuildId.Equals(Context.Guild.Id.AsLong()) &&
e.UserId.Equals(userInfo.Id.AsLong()));
eb.AddInlineField("Discordian Since",
$"{createdAt.Day}.{createdAt.Month}.{createdAt.Year} ({age} days)")
.AddInlineField("Joined Server",
$"{joinedAt.Day}.{joinedAt.Month}.{joinedAt.Year} ({joinedDayAgo} days)")
.AddInlineField("Karma", karma?.Karma ?? 0)
.AddInlineField("Level", level)
.AddInlineField("Messages Sent", messages)
.AddInlineField("Server Total", $"{percent}%");
if (correctRolls != null) eb.AddInlineField("Guessed Rolls", correctRolls.Rolls);
if (cookies > 0) eb.AddInlineField("Cookies", cookies);
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class AvatarGetter : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public AvatarGetter(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("avatar", RunMode = RunMode.Async)]
[Summary("Get someones avatar")]
public async Task GetAvatar([Remainder, Summary("@someone")] IUser user = null)
{
try
{
if (user == null) user = Context.User;
var url = user.GetAvatarUrl().Replace("128", "1024");
await ReplyAsync(url);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class Changelog : ModuleBase
{
private readonly DiscordSocketClient _client;
private readonly IErrorHandler _errorHandler;
public Changelog(IErrorHandler errorHandler, DiscordSocketClient client)
{
_errorHandler = errorHandler;
_client = client;
}
[Command("changelog", RunMode = RunMode.Async)]
[Summary("Show the latest 10 updates")]
public async Task GetChangelog()
{
try
{
var commits = await HttpAbstractions.Get<List<CommitDto>>(new Uri("https://api.github.com/repos/pizzaandcoffee/geekbot.net/commits"));
var eb = new EmbedBuilder();
eb.WithColor(new Color(143, 165, 102));
eb.WithAuthor(new EmbedAuthorBuilder
{
IconUrl = _client.CurrentUser.GetAvatarUrl(),
Name = "Latest Updates",
Url = "https://geekbot.pizzaandcoffee.rocks/updates"
});
var sb = new StringBuilder();
foreach (var commit in commits.Take(10))
sb.AppendLine($"- {commit.Commit.Message} ({commit.Commit.Author.Date:yyyy-MM-dd})");
eb.Description = sb.ToString();
eb.WithFooter(new EmbedFooterBuilder
{
Text = $"List generated from github commits on {DateTime.Now:yyyy-MM-dd}"
});
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class CommitAuthorDto
{
public string Name { get; set; }
public string Email { get; set; }
public DateTimeOffset Date { get; set; }
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class CommitDto
{
public CommitInfoDto Commit { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Geekbot.Bot.Commands.Utils.Changelog
{
public class CommitInfoDto
{
public CommitAuthorDto Author { get; set; }
public string Message { get; set; }
}
}

View file

@ -0,0 +1,38 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Localization;
namespace Geekbot.Bot.Commands.Utils
{
public class Choose : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly ITranslationHandler _translation;
public Choose(IErrorHandler errorHandler, ITranslationHandler translation)
{
_errorHandler = errorHandler;
_translation = translation;
}
[Command("choose", RunMode = RunMode.Async)]
[Summary("Let the bot choose for you, seperate options with a semicolon.")]
public async Task Command([Remainder] [Summary("option1;option2")]
string choices)
{
try
{
var transContext = await _translation.GetGuildContext(Context);
var choicesArray = choices.Split(';');
var choice = new Random().Next(choicesArray.Length);
await ReplyAsync(transContext.GetString("Choice", choicesArray[choice].Trim()));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,66 @@
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Utils.Corona
{
public class CoronaStats : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public CoronaStats(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("corona", RunMode = RunMode.Async)]
[Summary("Get the latest worldwide corona statistics")]
public async Task Summary()
{
try
{
var summary = await HttpAbstractions.Get<CoronaSummaryDto>(new Uri("https://api.covid19api.com/world/total"));
var activeCases = summary.TotalConfirmed - (summary.TotalRecovered + summary.TotalDeaths);
string CalculatePercentage(decimal i) => (i / summary.TotalConfirmed).ToString("#0.##%");
var activePercent = CalculatePercentage(activeCases);
var recoveredPercentage = CalculatePercentage(summary.TotalRecovered);
var deathsPercentage = CalculatePercentage(summary.TotalDeaths);
var numberFormat = "#,#";
var totalFormatted = summary.TotalConfirmed.ToString(numberFormat);
var activeFormatted = activeCases.ToString(numberFormat);
var recoveredFormatted = summary.TotalRecovered.ToString(numberFormat);
var deathsFormatted = summary.TotalDeaths.ToString(numberFormat);
var eb = new EmbedBuilder
{
Author = new EmbedAuthorBuilder
{
Name = "Confirmed Corona Cases",
IconUrl = "https://www.redcross.org/content/dam/icons/disasters/virus/Virus-1000x1000-R-Pl.png"
},
Footer = new EmbedFooterBuilder
{
Text = "Source: covid19api.com",
},
Color = Color.Red
};
eb.AddField("Total", totalFormatted);
eb.AddInlineField("Active", $"{activeFormatted} ({activePercent})");
eb.AddInlineField("Recovered", $"{recoveredFormatted} ({recoveredPercentage})");
eb.AddInlineField("Deaths", $"{deathsFormatted} ({deathsPercentage})");
await Context.Channel.SendMessageAsync(String.Empty, false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Geekbot.Bot.Commands.Utils.Corona
{
public class CoronaSummaryDto
{
public decimal TotalConfirmed { get; set; }
public decimal TotalDeaths { get; set; }
public decimal TotalRecovered { get; set; }
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.DiceParser;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Dice : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly IDiceParser _diceParser;
public Dice(IErrorHandler errorHandler, IDiceParser diceParser)
{
_errorHandler = errorHandler;
_diceParser = diceParser;
}
// ToDo: Separate representation and logic
// ToDo: Translate
[Command("dice", RunMode = RunMode.Async)]
[Summary("Roll a dice. (use '!dice help' for a manual)")]
public async Task RollCommand([Remainder] [Summary("input")] string diceInput = "1d20")
{
try
{
if (diceInput == "help")
{
await ShowDiceHelp();
return;
}
var parsed = _diceParser.Parse(diceInput);
var sb = new StringBuilder();
sb.AppendLine($"{Context.User.Mention} :game_die:");
foreach (var die in parsed.Dice)
{
sb.AppendLine($"**{die.DiceName}**");
var diceResultList = new List<string>();
var total = 0;
foreach (var roll in die.Roll())
{
diceResultList.Add(roll.ToString());
total += roll.Result;
}
sb.AppendLine(string.Join(" | ", diceResultList));
if (parsed.SkillModifier != 0)
{
sb.AppendLine($"Skill: {parsed.SkillModifier}");
}
if (parsed.Options.ShowTotal)
{
var totalLine = $"Total: {total}";
if (parsed.SkillModifier > 0)
{
totalLine += ($" (+{parsed.SkillModifier} = {total + parsed.SkillModifier})");
}
if (parsed.SkillModifier < 0)
{
totalLine += ($" ({parsed.SkillModifier} = {total - parsed.SkillModifier})");
}
sb.AppendLine(totalLine);
}
}
await Context.Channel.SendMessageAsync(sb.ToString());
}
catch (DiceException e)
{
await Context.Channel.SendMessageAsync($"**:warning: {e.DiceName} is invalid:** {e.Message}");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task ShowDiceHelp()
{
var sb = new StringBuilder();
sb.AppendLine("**__Examples__**");
sb.AppendLine("```");
sb.AppendLine("!dice - throw a 1d20");
sb.AppendLine("!dice 1d12 - throw a 1d12");
sb.AppendLine("!dice +1d20 - throw with advantage");
sb.AppendLine("!dice -1d20 - throw with disadvantage");
sb.AppendLine("!dice 1d20 +2 - throw with a +2 skill bonus");
sb.AppendLine("!dice 1d20 -2 - throw with a -2 skill bonus");
sb.AppendLine("!dice 8d6 - throw a fireball 🔥");
sb.AppendLine("!dice 8d6 total - calculate the total");
sb.AppendLine("!dice 2d20 6d6 2d12 - drop your dice pouch");
sb.AppendLine("```");
await Context.Channel.SendMessageAsync(sb.ToString());
}
}
}

View file

@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.Core.Converters;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Emojify : ModuleBase
{
private readonly IEmojiConverter _emojiConverter;
private readonly IErrorHandler _errorHandler;
public Emojify(IErrorHandler errorHandler, IEmojiConverter emojiConverter)
{
_errorHandler = errorHandler;
_emojiConverter = emojiConverter;
}
[Command("emojify", RunMode = RunMode.Async)]
[Summary("Emojify text")]
public async Task Dflt([Remainder] [Summary("text")] string text)
{
try
{
var emojis = _emojiConverter.TextToEmoji(text);
if (emojis.Length > 1999)
{
await ReplyAsync("I can't take that much at once!");
return;
}
await ReplyAsync($"{Context.User.Username}#{Context.User.Discriminator} said:");
await ReplyAsync(emojis);
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Help : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Help(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("help", RunMode = RunMode.Async)]
[Summary("List all Commands")]
public async Task GetHelp()
{
try
{
var sb = new StringBuilder();
sb.AppendLine("For a list of all commands, please visit the following page");
sb.AppendLine("https://geekbot.pizzaandcoffee.rocks/commands");
var dm = await Context.User.GetOrCreateDMChannelAsync();
await dm.SendMessageAsync(sb.ToString());
await Context.Message.AddReactionAsync(new Emoji("✅"));
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Core;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
namespace Geekbot.Bot.Commands.Utils
{
public class Info : ModuleBase
{
private readonly DiscordSocketClient _client;
private readonly CommandService _commands;
private readonly IErrorHandler _errorHandler;
public Info(IErrorHandler errorHandler, DiscordSocketClient client, CommandService commands)
{
_errorHandler = errorHandler;
_client = client;
_commands = commands;
}
[Command("info", RunMode = RunMode.Async)]
[Summary("Get Information about the bot")]
public async Task BotInfo()
{
try
{
var eb = new EmbedBuilder();
var appInfo = await _client.GetApplicationInfoAsync();
eb.WithAuthor(new EmbedAuthorBuilder()
.WithIconUrl(appInfo.IconUrl)
.WithName($"{Constants.Name} V{Constants.BotVersion()}"));
var uptime = DateTime.Now.Subtract(Process.GetCurrentProcess().StartTime);
eb.AddInlineField("Bot Name", _client.CurrentUser.Username);
eb.AddInlineField("Bot Owner", $"{appInfo.Owner.Username}#{appInfo.Owner.Discriminator}");
eb.AddInlineField("Library", $"Discord.NET {Constants.LibraryVersion()}");
eb.AddInlineField("Uptime", $"{uptime.Days}D {uptime.Hours}H {uptime.Minutes}M {uptime.Seconds}S");
eb.AddInlineField("Servers", Context.Client.GetGuildsAsync().Result.Count);
eb.AddInlineField("Total Commands", _commands.Commands.Count());
eb.AddField("Website", "https://geekbot.pizzaandcoffee.rocks/");
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
[Command("uptime", RunMode = RunMode.Async)]
[Summary("Get the Bot Uptime")]
public async Task BotUptime()
{
try
{
var uptime = DateTime.Now.Subtract(Process.GetCurrentProcess().StartTime);
await ReplyAsync($"{uptime.Days}D {uptime.Hours}H {uptime.Minutes}M {uptime.Seconds}S");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Threading.Tasks;
using System.Web;
using Discord.Commands;
using Geekbot.Core.ErrorHandling;
namespace Geekbot.Bot.Commands.Utils
{
public class Lmgtfy : ModuleBase
{
private readonly IErrorHandler _errorHandler;
public Lmgtfy(IErrorHandler errorHandler)
{
_errorHandler = errorHandler;
}
[Command("lmgtfy", RunMode = RunMode.Async)]
[Summary("Get a 'Let me google that for you' link")]
public async Task GetUrl([Remainder] [Summary("question")] string question)
{
try
{
var encoded = HttpUtility.UrlEncode(question).Replace("%20", "+");
await Context.Channel.SendMessageAsync($"<https://lmgtfy.com/?q={encoded}>");
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
}
}

View file

@ -0,0 +1,15 @@
using System.Threading.Tasks;
using Discord.Commands;
namespace Geekbot.Bot.Commands.Utils
{
public class Ping : ModuleBase
{
[Command("👀", RunMode = RunMode.Async)]
[Summary("Look at the bot.")]
public async Task Eyes()
{
await ReplyAsync("S... Stop looking at me... baka!");
}
}
}

View file

@ -0,0 +1,313 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Geekbot.Core.CommandPreconditions;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.Extensions;
using Geekbot.Core.Localization;
using Geekbot.Core.Polyfills;
using Geekbot.Core.RandomNumberGenerator;
namespace Geekbot.Bot.Commands.Utils.Quote
{
[Group("quote")]
[DisableInDirectMessage]
public class Quote : ModuleBase
{
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
private readonly IRandomNumberGenerator _randomNumberGenerator;
private readonly ITranslationHandler _translationHandler;
public Quote(IErrorHandler errorHandler, DatabaseContext database, IRandomNumberGenerator randomNumberGenerator, ITranslationHandler translationHandler)
{
_errorHandler = errorHandler;
_database = database;
_randomNumberGenerator = randomNumberGenerator;
_translationHandler = translationHandler;
}
[Command]
[Summary("Return a random quoute from the database")]
public async Task GetRandomQuote()
{
try
{
var s = _database.Quotes.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())).ToList();
if (!s.Any())
{
var transContext = await _translationHandler.GetGuildContext(Context);
await ReplyAsync(transContext.GetString("NoQuotesFound"));
return;
}
var random = _randomNumberGenerator.Next(0, s.Count);
var quote = s[random];
var embed = QuoteBuilder(quote);
await ReplyAsync("", false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context, "Whoops, seems like the quote was to edgy to return");
}
}
[Command("save")]
[Summary("Save a quote from the last sent message by @user")]
public async Task SaveQuote([Summary("@someone")] IUser user)
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
if (user.Id == Context.Message.Author.Id)
{
await ReplyAsync(transContext.GetString("CannotSaveOwnQuotes"));
return;
}
if (user.IsBot)
{
await ReplyAsync(transContext.GetString("CannotQuoteBots"));
return;
}
var lastMessage = await GetLastMessageByUser(user);
if (lastMessage == null) return;
var quote = CreateQuoteObject(lastMessage);
_database.Quotes.Add(quote);
await _database.SaveChangesAsync();
var embed = QuoteBuilder(quote);
await ReplyAsync(transContext.GetString("QuoteAdded"), false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"I counldn't find a quote from that user :disappointed:");
}
}
[Command("save")]
[Summary("Save a quote from a message id")]
public async Task SaveQuote([Summary("message-ID")] ulong messageId)
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
var message = await Context.Channel.GetMessageAsync(messageId);
if (message.Author.Id == Context.Message.Author.Id)
{
await ReplyAsync(transContext.GetString("CannotSaveOwnQuotes"));
return;
}
if (message.Author.IsBot)
{
await ReplyAsync(transContext.GetString("CannotQuoteBots"));
return;
}
var quote = CreateQuoteObject(message);
_database.Quotes.Add(quote);
await _database.SaveChangesAsync();
var embed = QuoteBuilder(quote);
await ReplyAsync(transContext.GetString("QuoteAdded"), false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"I couldn't find a message with that id :disappointed:");
}
}
[Command("make")]
[Summary("Create a quote from the last sent message by @user")]
public async Task ReturnSpecifiedQuote([Summary("@someone")] IUser user)
{
try
{
var lastMessage = await GetLastMessageByUser(user);
if (lastMessage == null) return;
var quote = CreateQuoteObject(lastMessage);
var embed = QuoteBuilder(quote);
await ReplyAsync("", false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"I counldn't find a quote from that user :disappointed:");
}
}
[Command("make")]
[Summary("Create a quote from a message id")]
public async Task ReturnSpecifiedQuote([Summary("message-ID")] ulong messageId)
{
try
{
var message = await Context.Channel.GetMessageAsync(messageId);
var quote = CreateQuoteObject(message);
var embed = QuoteBuilder(quote);
await ReplyAsync("", false, embed.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context,
"I couldn't find a message with that id :disappointed:");
}
}
[Command("remove")]
[RequireUserPermission(GuildPermission.ManageMessages)]
[Summary("Remove a quote (user needs the 'ManageMessages' permission)")]
public async Task RemoveQuote([Summary("quote-ID")] int id)
{
try
{
var transContext = await _translationHandler.GetGuildContext(Context);
var quote = _database.Quotes.Where(e => e.GuildId == Context.Guild.Id.AsLong() && e.InternalId == id)?.FirstOrDefault();
if (quote != null)
{
_database.Quotes.Remove(quote);
await _database.SaveChangesAsync();
var embed = QuoteBuilder(quote);
await ReplyAsync(transContext.GetString("Removed", id), false, embed.Build());
}
else
{
await ReplyAsync(transContext.GetString("NotFoundWithId"));
}
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context, "I couldn't find a quote with that id :disappointed:");
}
}
[Command("stats")]
[Summary("Show quote stats for this server")]
public async Task GetQuoteStatsForServer()
{
try
{
// setup
var transContext = await _translationHandler.GetGuildContext(Context);
var eb = new EmbedBuilder();
eb.Author = new EmbedAuthorBuilder()
{
IconUrl = Context.Guild.IconUrl,
Name = $"{Context.Guild.Name} - {transContext.GetString("QuoteStats")}"
};
// gather data
var totalQuotes = _database.Quotes.Count(row => row.GuildId == Context.Guild.Id.AsLong());
if (totalQuotes == 0)
{
// no quotes, no stats, end of the road
await ReplyAsync(transContext.GetString("NoQuotesFound"));
return;
}
var mostQuotedPerson = _database.Quotes
.Where(row => row.GuildId == Context.Guild.Id.AsLong())
.GroupBy(row => row.UserId)
.Select(row => new { userId = row.Key, amount = row.Count()})
.OrderBy(row => row.amount)
.Last();
var mostQuotedPersonUser = Context.Client.GetUserAsync(mostQuotedPerson.userId.AsUlong()).Result ?? new UserPolyfillDto {Username = "Unknown User"};
var quotesByYear = _database.Quotes
.Where(row => row.GuildId == Context.Guild.Id.AsLong())
.GroupBy(row => row.Time.Year)
.Select(row => new { year = row.Key, amount = row.Count()})
.OrderBy(row => row.year);
// add data to the embed
eb.AddField(transContext.GetString("MostQuotesPerson"), $"{mostQuotedPersonUser.Username} ({mostQuotedPerson.amount})");
eb.AddInlineField(transContext.GetString("TotalQuotes"), totalQuotes);
foreach (var year in quotesByYear)
{
eb.AddInlineField(year.year.ToString(), year.amount);
}
await ReplyAsync("", false, eb.Build());
}
catch (Exception e)
{
await _errorHandler.HandleCommandException(e, Context);
}
}
private async Task<IMessage> GetLastMessageByUser(IUser user)
{
try
{
var list = Context.Channel.GetMessagesAsync().Flatten();
return await list.FirstOrDefaultAsync(msg =>
msg.Author.Id == user.Id &&
msg.Embeds.Count == 0 &&
msg.Id != Context.Message.Id &&
!msg.Content.ToLower().StartsWith("!"));
}
catch
{
await ReplyAsync($"No quoteable message have been sent by {user.Username} in this channel");
return null;
}
}
private EmbedBuilder QuoteBuilder(QuoteModel quote)
{
var user = Context.Client.GetUserAsync(quote.UserId.AsUlong()).Result ?? new UserPolyfillDto { Username = "Unknown User" };
var eb = new EmbedBuilder();
eb.WithColor(new Color(143, 167, 232));
if (quote.InternalId == 0)
{
eb.Title = $"{user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}";
}
else
{
eb.Title = $"#{quote.InternalId} | {user.Username} @ {quote.Time.Day}.{quote.Time.Month}.{quote.Time.Year}";
}
eb.Description = quote.Quote;
eb.ThumbnailUrl = user.GetAvatarUrl();
if (quote.Image != null) eb.ImageUrl = quote.Image;
return eb;
}
private QuoteModel CreateQuoteObject(IMessage message)
{
string image;
try
{
image = message.Attachments.First().Url;
}
catch (Exception)
{
image = null;
}
var last = _database.Quotes.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong())).OrderByDescending(e => e.InternalId).FirstOrDefault();
var internalId = 0;
if (last != null) internalId = last.InternalId + 1;
return new QuoteModel()
{
InternalId = internalId,
GuildId = Context.Guild.Id.AsLong(),
UserId = message.Author.Id.AsLong(),
Time = message.Timestamp.DateTime,
Quote = message.Content,
Image = image
};
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Geekbot.Bot.Commands.Utils.Quote
{
internal class QuoteObjectDto
{
public ulong UserId { get; set; }
public string Quote { get; set; }
public DateTime Time { get; set; }
public string Image { get; set; }
}
}

View file

@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.Rest;
using Discord.WebSocket;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Logger;
namespace Geekbot.Bot.Handlers
{
public class CommandHandler
{
private readonly IDiscordClient _client;
private readonly IGeekbotLogger _logger;
private readonly IServiceProvider _servicesProvider;
private readonly CommandService _commands;
private readonly RestApplication _applicationInfo;
private readonly IGuildSettingsManager _guildSettingsManager;
private readonly List<ulong> _ignoredServers;
private readonly DatabaseContext _database;
public CommandHandler(DatabaseContext database, IDiscordClient client, IGeekbotLogger logger, IServiceProvider servicesProvider, CommandService commands, RestApplication applicationInfo,
IGuildSettingsManager guildSettingsManager)
{
_database = database;
_client = client;
_logger = logger;
_servicesProvider = servicesProvider;
_commands = commands;
_applicationInfo = applicationInfo;
_guildSettingsManager = guildSettingsManager;
// Some guilds only want very specific functionally without any of the commands, a quick hack that solves that "short term"
// ToDo: create a clean solution for this...
_ignoredServers = new List<ulong>
{
228623803201224704, // SwitzerLAN
169844523181015040, // EEvent
248531441548263425, // MYI
110373943822540800 // Discord Bots
};
}
public Task RunCommand(SocketMessage messageParam)
{
try
{
if (!(messageParam is SocketUserMessage message)) return Task.CompletedTask;
if (message.Author.IsBot) return Task.CompletedTask;
ulong guildId = message.Author switch
{
SocketGuildUser user => user.Guild.Id,
_ => 0 // DM Channel
};
if (IsIgnoredGuild(guildId, message.Author.Id)) return Task.CompletedTask;
var lowCaseMsg = message.ToString().ToLower();
if (ShouldHui(lowCaseMsg, guildId))
{
message.Channel.SendMessageAsync("hui!!!");
return Task.CompletedTask;
}
if (ShouldPong(lowCaseMsg, guildId))
{
message.Channel.SendMessageAsync("pong");
return Task.CompletedTask;
}
var argPos = 0;
if (!IsCommand(message, ref argPos)) return Task.CompletedTask;
ExecuteCommand(message, argPos);
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to Process Message", e);
}
return Task.CompletedTask;
}
private void ExecuteCommand(IUserMessage message, int argPos)
{
var context = new CommandContext(_client, message);
_commands.ExecuteAsync(context, argPos, _servicesProvider);
_logger.Information(LogSource.Command, context.Message.Content.Split(" ")[0].Replace("!", ""), SimpleConextConverter.ConvertContext(context));
}
private bool IsIgnoredGuild(ulong guildId, ulong authorId)
{
if (!_ignoredServers.Contains(guildId)) return false;
return authorId == _applicationInfo.Owner.Id;
}
private bool IsCommand(IUserMessage message, ref int argPos)
{
return message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos);
}
private bool ShouldPong(string lowerCaseMessage, ulong guildId)
{
if (!lowerCaseMessage.StartsWith("ping ") && !lowerCaseMessage.Equals("ping")) return false;
if (guildId == 0) return true;
return GetGuildSettings(guildId)?.Ping ?? false;
}
private bool ShouldHui(string lowerCaseMessage, ulong guildId)
{
if (!lowerCaseMessage.StartsWith("hui")) return false;
if (guildId == 0) return true;
return GetGuildSettings(guildId)?.Hui ?? false;
}
private GuildSettingsModel GetGuildSettings(ulong guildId)
{
return _guildSettingsManager.GetSettings(guildId, false);
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Geekbot.Core.Database;
using Geekbot.Core.Extensions;
using Geekbot.Core.Logger;
namespace Geekbot.Bot.Handlers
{
public class MessageDeletedHandler
{
private readonly DatabaseContext _database;
private readonly IGeekbotLogger _logger;
private readonly IDiscordClient _client;
public MessageDeletedHandler(DatabaseContext database, IGeekbotLogger logger, IDiscordClient client)
{
_database = database;
_logger = logger;
_client = client;
}
public async Task HandleMessageDeleted(Cacheable<IMessage, ulong> message, ISocketMessageChannel channel)
{
try
{
var guildSocketData = ((IGuildChannel) channel).Guild;
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildSocketData.Id.AsLong()));
if ((guild?.ShowDelete ?? false) && guild?.ModChannel != 0)
{
var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(guild.ModChannel.AsUlong());
var sb = new StringBuilder();
if (message.Value != null)
{
sb.AppendLine($"The following message from {message.Value.Author.Username}#{message.Value.Author.Discriminator} was deleted in <#{channel.Id}>");
sb.AppendLine(message.Value.Content);
}
else
{
sb.AppendLine("Someone deleted a message, the message was not cached...");
}
await modChannelSocket.SendMessageAsync(sb.ToString());
}
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send delete message...", e);
}
}
}
}

View file

@ -0,0 +1,33 @@
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Geekbot.Core.ReactionListener;
namespace Geekbot.Bot.Handlers
{
public class ReactionHandler
{
private readonly IReactionListener _reactionListener;
public ReactionHandler(IReactionListener reactionListener)
{
_reactionListener = reactionListener;
}
public Task Added(Cacheable<IUserMessage, ulong> cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
{
if (reaction.User.Value.IsBot) return Task.CompletedTask;
if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
_reactionListener.GiveRole(socketMessageChannel, reaction);
return Task.CompletedTask;
}
public Task Removed(Cacheable<IUserMessage, ulong> cacheable, ISocketMessageChannel socketMessageChannel, SocketReaction reaction)
{
if (reaction.User.Value.IsBot) return Task.CompletedTask;
if (!_reactionListener.IsListener(reaction.MessageId)) return Task.CompletedTask;
_reactionListener.RemoveRole(socketMessageChannel, reaction);
return Task.CompletedTask;
}
}
}

View file

@ -0,0 +1,62 @@
using System;
using System.Threading.Tasks;
using Discord.WebSocket;
using Geekbot.Core.Database;
using Geekbot.Core.Database.Models;
using Geekbot.Core.Extensions;
using Geekbot.Core.Logger;
using Microsoft.EntityFrameworkCore;
namespace Geekbot.Bot.Handlers
{
public class StatsHandler
{
private readonly IGeekbotLogger _logger;
private readonly DatabaseContext _database;
public StatsHandler(IGeekbotLogger logger, DatabaseContext database)
{
_logger = logger;
_database = database;
}
public async Task UpdateStats(SocketMessage message)
{
try
{
if (message == null) return;
if (message.Channel.Name.StartsWith('@'))
{
_logger.Information(LogSource.Message, $"[DM-Channel] {message.Content}", SimpleConextConverter.ConvertSocketMessage(message, true));
return;
}
var channel = (SocketGuildChannel) message.Channel;
var rowId = await _database.Database.ExecuteSqlRawAsync(
"UPDATE \"Messages\" SET \"MessageCount\" = \"MessageCount\" + 1 WHERE \"GuildId\" = {0} AND \"UserId\" = {1}",
channel.Guild.Id.AsLong(),
message.Author.Id.AsLong()
);
if (rowId == 0)
{
await _database.Messages.AddAsync(new MessagesModel
{
UserId = message.Author.Id.AsLong(),
GuildId = channel.Guild.Id.AsLong(),
MessageCount = 1
});
await _database.SaveChangesAsync();
}
if (message.Author.IsBot) return;
_logger.Information(LogSource.Message, message.Content, SimpleConextConverter.ConvertSocketMessage(message));
}
catch (Exception e)
{
_logger.Error(LogSource.Message, "Could not process message stats", e);
}
}
}
}

View file

@ -0,0 +1,96 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Rest;
using Discord.WebSocket;
using Geekbot.Core.Database;
using Geekbot.Core.Extensions;
using Geekbot.Core.Logger;
using Geekbot.Core.UserRepository;
namespace Geekbot.Bot.Handlers
{
public class UserHandler
{
private readonly IUserRepository _userRepository;
private readonly IGeekbotLogger _logger;
private readonly DatabaseContext _database;
private readonly IDiscordClient _client;
public UserHandler(IUserRepository userRepository, IGeekbotLogger logger, DatabaseContext database, IDiscordClient client)
{
_userRepository = userRepository;
_logger = logger;
_database = database;
_client = client;
}
public async Task Joined(SocketGuildUser user)
{
try
{
var userRepoUpdate = _userRepository.Update(user);
_logger.Information(LogSource.Geekbot, $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
if (!user.IsBot)
{
var guildSettings = _database.GuildSettings.FirstOrDefault(guild => guild.GuildId == user.Guild.Id.AsLong());
var message = guildSettings?.WelcomeMessage;
if (string.IsNullOrEmpty(message)) return;
message = message.Replace("$user", user.Mention);
var fallbackSender = new Func<Task<RestUserMessage>>(() => user.Guild.DefaultChannel.SendMessageAsync(message));
if (guildSettings.WelcomeChannel != 0)
{
try
{
var target = await _client.GetChannelAsync(guildSettings.WelcomeChannel.AsUlong());
var channel = target as ISocketMessageChannel;
await channel.SendMessageAsync(message);
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send welcome message to user defined welcome channel", e);
await fallbackSender();
}
}
else
{
await fallbackSender();
}
}
await userRepoUpdate;
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send welcome message", e);
}
}
public async Task Updated(SocketUser oldUser, SocketUser newUser)
{
await _userRepository.Update(newUser);
}
public async Task Left(SocketGuildUser user)
{
try
{
var guild = _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(user.Guild.Id.AsLong()));
if (guild?.ShowLeave ?? false)
{
var modChannelSocket = (ISocketMessageChannel) await _client.GetChannelAsync(guild.ModChannel.AsUlong());
await modChannelSocket.SendMessageAsync($"{user.Username}#{user.Discriminator} left the server");
}
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Failed to send leave message", e);
}
_logger.Information(LogSource.Geekbot, $"{user.Username} ({user.Id}) joined {user.Guild.Name} ({user.Guild.Id})");
}
}
}

0
src/Bot/Logs/.keep Normal file
View file

234
src/Bot/Program.cs Normal file
View file

@ -0,0 +1,234 @@
using System;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using CommandLine;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Bot.Handlers;
using Geekbot.Core;
using Geekbot.Core.Converters;
using Geekbot.Core.Database;
using Geekbot.Core.DiceParser;
using Geekbot.Core.ErrorHandling;
using Geekbot.Core.GlobalSettings;
using Geekbot.Core.GuildSettingsManager;
using Geekbot.Core.Highscores;
using Geekbot.Core.KvInMemoryStore;
using Geekbot.Core.Levels;
using Geekbot.Core.Localization;
using Geekbot.Core.Logger;
using Geekbot.Core.MalClient;
using Geekbot.Core.Media;
using Geekbot.Core.RandomNumberGenerator;
using Geekbot.Core.ReactionListener;
using Geekbot.Core.UserRepository;
using Geekbot.Core.WikipediaClient;
using Geekbot.Web;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Geekbot.Bot
{
internal class Program
{
private DiscordSocketClient _client;
private CommandService _commands;
private DatabaseInitializer _databaseInitializer;
private IGlobalSettings _globalSettings;
private IServiceProvider _servicesProvider;
private GeekbotLogger _logger;
private IUserRepository _userRepository;
private RunParameters _runParameters;
private IReactionListener _reactionListener;
private IGuildSettingsManager _guildSettingsManager;
private static async Task Main(string[] args)
{
RunParameters runParameters = null;
Parser.Default.ParseArguments<RunParameters>(args)
.WithParsed(e => runParameters = e)
.WithNotParsed(_ => Environment.Exit(GeekbotExitCode.InvalidArguments.GetHashCode()));
var logo = new StringBuilder();
logo.AppendLine(@" ____ _____ _____ _ ______ ___ _____");
logo.AppendLine(@" / ___| ____| ____| |/ / __ ) / _ \\_ _|");
logo.AppendLine(@"| | _| _| | _| | ' /| _ \| | | || |");
logo.AppendLine(@"| |_| | |___| |___| . \| |_) | |_| || |");
logo.AppendLine(@" \____|_____|_____|_|\_\____/ \___/ |_|");
logo.AppendLine($"Version {Constants.BotVersion()} ".PadRight(41, '='));
Console.WriteLine(logo.ToString());
var logger = new GeekbotLogger(runParameters);
logger.Information(LogSource.Geekbot, "Starting...");
try
{
await new Program().Start(runParameters, logger);
}
catch (Exception e)
{
logger.Error(LogSource.Geekbot, "RIP", e);
}
}
private async Task Start(RunParameters runParameters, GeekbotLogger logger)
{
_logger = logger;
_runParameters = runParameters;
logger.Information(LogSource.Geekbot, "Connecting to Database");
var database = ConnectToDatabase();
_globalSettings = new GlobalSettings(database);
logger.Information(LogSource.Geekbot, "Connecting to Discord");
SetupDiscordClient();
await Login();
_logger.Information(LogSource.Geekbot, $"Now Connected as {_client.CurrentUser.Username} to {_client.Guilds.Count} Servers");
await _client.SetGameAsync(_globalSettings.GetKey("Game"));
_logger.Information(LogSource.Geekbot, "Loading Dependencies and Handlers");
RegisterDependencies();
await RegisterHandlers();
_logger.Information(LogSource.Api, "Starting Web API");
StartWebApi();
_logger.Information(LogSource.Geekbot, "Done and ready for use");
await Task.Delay(-1);
}
private async Task Login()
{
try
{
var token = await GetToken();
await _client.LoginAsync(TokenType.Bot, token);
await _client.StartAsync();
while (!_client.ConnectionState.Equals(ConnectionState.Connected)) await Task.Delay(25);
}
catch (Exception e)
{
_logger.Error(LogSource.Geekbot, "Could not connect to Discord", e);
Environment.Exit(GeekbotExitCode.CouldNotLogin.GetHashCode());
}
}
private DatabaseContext ConnectToDatabase()
{
_databaseInitializer = new DatabaseInitializer(_runParameters, _logger);
var database = _databaseInitializer.Initialize();
database.Database.EnsureCreated();
if(!_runParameters.InMemory) database.Database.Migrate();
return database;
}
private async Task<string> GetToken()
{
var token = _runParameters.Token ?? _globalSettings.GetKey("DiscordToken");
if (string.IsNullOrEmpty(token))
{
Console.Write("Your bot Token: ");
var newToken = Console.ReadLine();
await _globalSettings.SetKey("DiscordToken", newToken);
await _globalSettings.SetKey("Game", "Ping Pong");
token = newToken;
}
return token;
}
private void SetupDiscordClient()
{
_client = new DiscordSocketClient(new DiscordSocketConfig
{
LogLevel = LogSeverity.Verbose,
MessageCacheSize = 1000,
ExclusiveBulkDelete = true
});
var discordLogger = new DiscordLogger(_logger);
_client.Log += discordLogger.Log;
}
private void RegisterDependencies()
{
var services = new ServiceCollection();
_userRepository = new UserRepository(_databaseInitializer.Initialize(), _logger);
_reactionListener = new ReactionListener(_databaseInitializer.Initialize());
_guildSettingsManager = new GuildSettingsManager(_databaseInitializer.Initialize());
var fortunes = new FortunesProvider(_logger);
var malClient = new MalClient(_globalSettings, _logger);
var levelCalc = new LevelCalc();
var emojiConverter = new EmojiConverter();
var mtgManaConverter = new MtgManaConverter();
var wikipediaClient = new WikipediaClient();
var randomNumberGenerator = new RandomNumberGenerator();
var mediaProvider = new MediaProvider(_logger, randomNumberGenerator);
var kvMemoryStore = new KvInInMemoryStore();
var translationHandler = new TranslationHandler(_logger, _guildSettingsManager);
var errorHandler = new ErrorHandler(_logger, translationHandler, _runParameters);
var diceParser = new DiceParser(randomNumberGenerator);
services.AddSingleton(_userRepository);
services.AddSingleton<IGeekbotLogger>(_logger);
services.AddSingleton<ILevelCalc>(levelCalc);
services.AddSingleton<IEmojiConverter>(emojiConverter);
services.AddSingleton<IFortunesProvider>(fortunes);
services.AddSingleton<IMediaProvider>(mediaProvider);
services.AddSingleton<IMalClient>(malClient);
services.AddSingleton<IMtgManaConverter>(mtgManaConverter);
services.AddSingleton<IWikipediaClient>(wikipediaClient);
services.AddSingleton<IRandomNumberGenerator>(randomNumberGenerator);
services.AddSingleton<IKvInMemoryStore>(kvMemoryStore);
services.AddSingleton<IGlobalSettings>(_globalSettings);
services.AddSingleton<IErrorHandler>(errorHandler);
services.AddSingleton<IDiceParser>(diceParser);
services.AddSingleton<ITranslationHandler>(translationHandler);
services.AddSingleton<IReactionListener>(_reactionListener);
services.AddSingleton<IGuildSettingsManager>(_guildSettingsManager);
services.AddSingleton(_client);
services.AddTransient<IHighscoreManager>(e => new HighscoreManager(_databaseInitializer.Initialize(), _userRepository));
services.AddTransient(e => _databaseInitializer.Initialize());
_servicesProvider = services.BuildServiceProvider();
}
private async Task RegisterHandlers()
{
var applicationInfo = await _client.GetApplicationInfoAsync();
_commands = new CommandService();
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _servicesProvider);
var commandHandler = new CommandHandler(_databaseInitializer.Initialize(), _client, _logger, _servicesProvider, _commands, applicationInfo, _guildSettingsManager);
var userHandler = new UserHandler(_userRepository, _logger, _databaseInitializer.Initialize(), _client);
var reactionHandler = new ReactionHandler(_reactionListener);
var statsHandler = new StatsHandler(_logger, _databaseInitializer.Initialize());
var messageDeletedHandler = new MessageDeletedHandler(_databaseInitializer.Initialize(), _logger, _client);
_client.MessageReceived += commandHandler.RunCommand;
_client.MessageDeleted += messageDeletedHandler.HandleMessageDeleted;
_client.UserJoined += userHandler.Joined;
_client.UserUpdated += userHandler.Updated;
_client.UserLeft += userHandler.Left;
_client.ReactionAdded += reactionHandler.Added;
_client.ReactionRemoved += reactionHandler.Removed;
if (!_runParameters.InMemory) _client.MessageReceived += statsHandler.UpdateStats;
}
private void StartWebApi()
{
if (_runParameters.DisableApi)
{
_logger.Warning(LogSource.Api, "Web API is disabled");
return;
}
var highscoreManager = new HighscoreManager(_databaseInitializer.Initialize(), _userRepository);
WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings, highscoreManager);
}
}
}

17
src/Bot/Storage/croissant Normal file
View file

@ -0,0 +1,17 @@
https://i2.wp.com/epicureandculture.com/wp-content/uploads/2014/12/shutterstock_172040546.jpg
http://www.bakespace.com/images/large/5d79070cf21b2f33c3a1dd4336cb27d2.jpeg
http://food.fnr.sndimg.com/content/dam/images/food/fullset/2015/5/7/1/SD1B43_croissants-recipe_s4x3.jpg.rend.hgtvcom.616.462.suffix/1431052139248.jpeg
http://img.taste.com.au/u-Bwjfm_/taste/2016/11/mini-croissants-with-3-fillings-14692-1.jpeg
https://media.newyorker.com/photos/590974702179605b11ad8096/16:9/w_1200,h_630,c_limit/Gopnik-TheMurkyMeaningsofStraightenedOutCroissants.jpg
http://bt.static-redmouse.ch/sites/bielertagblatt.ch/files/styles/bt_article_showroom_landscape/hash/84/c9/84c9aed08415265911ec05c46d25d3ef.jpg?itok=hP5PnHaT
https://www.dermann.at/wp-content/uploads/Schokocroissant_HPBild_1400x900px.jpeg
https://www.bettybossi.ch/static/rezepte/x/bb_bkxx060101_0360a_x.jpg
http://www.engel-beck.ch/uploads/pics/tete-de-moine-gipfel-.jpg
https://storage.cpstatic.ch/storage/og_image/laugengipfel--425319.jpg
https://www.backhaus-kutzer.de/fileadmin/templates/Resources/Public/img/produkte/suesses-gebaeck/Milchhoernchen.png
https://www.kuechengoetter.de/uploads/media/1000x524/00/36390-vanillekipferl-0.jpg?v=1-0
https://c1.staticflickr.com/3/2835/10874180753_2b2916e3ce_b.jpg
http://www.mistercool.ch/wp-content/uploads/2017/02/Gipfel-mit-Cerealien-7168.png
https://scontent-sea1-1.cdninstagram.com/t51.2885-15/s480x480/e35/c40.0.999.999/15099604_105396696611384_2866237281000226816_n.jpg?ig_cache_key=MTM4MzQxOTU1MDc5NjUxNzcwMA%3D%3D.2.c
http://www.lecrobag.de/wp-content/uploads/2014/03/Wurst_2014_l.jpg
https://www.thecookierookie.com/wp-content/uploads/2017/02/sheet-pan-chocolate-croissants-collage1.jpeg

8
src/Bot/Storage/dab Normal file
View file

@ -0,0 +1,8 @@
https://pre00.deviantart.net/dcde/th/pre/i/2018/247/8/1/dabbing_pug_cute_dab_dance_by_manekibb-dcm2lvd.png
https://banner2.kisspng.com/20180625/xfv/kisspng-squidward-tentacles-dab-desktop-wallpaper-dab-emoji-5b31a97a839bf2.6353972915299813065391.jpg
https://djbooth.net/.image/t_share/MTUzNDg2MDIzOTU4NzM0NzA1/life-death-of-dab-dancejpg.jpg
https://res.cloudinary.com/teepublic/image/private/s--gfsWHvaH--/t_Preview/b_rgb:262c3a,c_limit,f_jpg,h_630,q_90,w_630/v1493209189/production/designs/1524888_1.jpg
https://i1.wp.com/fortniteskins.net/wp-content/uploads/2018/04/dab-skin-1.png?quality=90&strip=all&ssl=1
https://i.pinimg.com/originals/12/d4/0a/12d40a8fc66b7a7ea8b9044ee0303974.png
https://images.bewakoof.com/t540/dab-penguin-boyfriend-t-shirt-women-s-printed-boyfriend-t-shirts-196918-1538034349.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSKH1FLh5L3pOR2tamx-5OBqm_W2FFl8F7gteTAs2vMowwJ1Y32

9760
src/Bot/Storage/fortunes Normal file

File diff suppressed because it is too large Load diff

29
src/Bot/Storage/foxes Normal file
View file

@ -0,0 +1,29 @@
https://i.ytimg.com/vi/qF6OOGuT_hI/maxresdefault.jpg
https://www.hd-wallpapersdownload.com/script/bulk-upload/desktop-funny-fox-wallpaper.jpg
http://moziru.com/images/drawn-fox-funny-18.jpg
https://static.tumblr.com/bb34d8f163098ad1daafcffbdbb03975/rk23uap/Nwwp0rmi2/tumblr_static_tumblr_static__640.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQoHUFOnZ3wJ2kT1skNdztFXXSvpU8bEoGS1alNZiuyLXvGJhcY
http://childrenstorytales.com/wp-content/uploads/2011/03/how-to-draw-a-red-fox-in-the-snow.jpg
https://www.popsci.com/sites/popsci.com/files/styles/1000_1x_/public/import/2013/images/2013/09/redfoxyawn.jpg?itok=yRkSVe8T
https://hdqwalls.com/wallpapers/wild-fox-art.jpg
https://ae01.alicdn.com/kf/HTB1Q9dpLpXXXXbhXpXXq6xXFXXXl/new-cute-fox-toy-lifelike-soft-long-yellow-fox-doll-gift-about-73cm.jpg_640x640.jpg
https://i.imgur.com/ktK9yXX.jpg
https://res.cloudinary.com/teepublic/image/private/s--yTx2ncFA--/t_Preview/b_rgb:c8e0ec,c_limit,f_auto,h_313,q_90,w_313/v1506478249/production/designs/1932607_0
http://4.bp.blogspot.com/-Hz-o_KYj3Xk/Vlm2mwbztjI/AAAAAAAA8Ss/jbH5ovjmC9A/s1600/ScreenShot5502.jpg
https://i.pinimg.com/originals/1e/d5/2f/1ed52f70873a95ac02fa074e48edfb71.jpg
https://i.imgur.com/2vCrtap.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSfukWGu_IBaDeJOMBqOhVAwsDfqEPw0BFpCn5_-Iyr_xjd7zi9
https://cdn.pixabay.com/photo/2017/01/31/18/36/animal-2026297_960_720.png
https://i.pinimg.com/originals/e2/63/67/e26367a0844633b2a697b0a9d69e8cc9.jpg
https://i.ebayimg.com/images/g/BvkAAOSwqxdTqrip/s-l300.jpg
https://res.cloudinary.com/teepublic/image/private/s--1R53bger--/t_Preview/b_rgb:eae0c7,c_limit,f_jpg,h_630,q_90,w_630/v1481013120/production/designs/914528_1.jpg
https://i.pinimg.com/originals/97/fe/69/97fe698462afde7b4209ccefeecbce71.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT6G0ch6g-wG1TuDJ6BbkOFelMNnkgFXC6CxOw7qSNjoFkx-BCe
https://wallpaperscraft.com/image/fox_forest_grass_117190_540x960.jpg
https://image.freepik.com/free-vector/cartoon-flat-illustration-funny-cute-fox_6317-1174.jpg
https://orig00.deviantart.net/2feb/f/2013/137/a/f/fox_and_curious_squirrel_by_tamarar-d65ju8d.jpg
https://res.cloudinary.com/teepublic/image/private/s--dICeNmBx--/t_Preview/b_rgb:6e2229,c_limit,f_jpg,h_630,q_90,w_630/v1505243196/production/designs/1890493_1.jpg
https://vignette.wikia.nocookie.net/puppyinmypocketfanon/images/4/49/L-Baby-Fox.jpg/revision/latest?cb=20130421001806
http://7-themes.com/data_images/out/69/7009194-fox-puppy.jpg
http://www.tehcute.com/pics/201401/little-fox-big.jpg
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR6QXB1APLdUsyzO39kPvhnC9cOvcwzEtsxown9QjWilWppia2mwg

11
src/Bot/Storage/pandas Normal file
View file

@ -0,0 +1,11 @@
https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Grosser_Panda.JPG/1200px-Grosser_Panda.JPG
https://c402277.ssl.cf1.rackcdn.com/photos/13100/images/featured_story/BIC_128.png?1485963152
https://nationalzoo.si.edu/sites/default/files/styles/slide_1400x700/public/support/adopt/giantpanda-03.jpg?itok=3EdEO0Vi
https://media4.s-nbcnews.com/j/newscms/2016_36/1685951/ss-160826-twip-05_8cf6d4cb83758449fd400c7c3d71aa1f.nbcnews-ux-2880-1000.jpg
https://ichef-1.bbci.co.uk/news/660/cpsprodpb/169F6/production/_91026629_gettyimages-519508400.jpg
https://cdn.history.com/sites/2/2017/03/GettyImages-157278376.jpg
https://www.pandasinternational.org/wptemp/wp-content/uploads/2012/10/slider1.jpg
https://tctechcrunch2011.files.wordpress.com/2015/11/panda.jpg
http://www.nationalgeographic.com/content/dam/magazine/rights-exempt/2016/08/departments/panda-mania-12.jpg
http://animals.sandiegozoo.org/sites/default/files/2016-09/panda1_10.jpg
http://kids.nationalgeographic.com/content/dam/kids/photos/animals/Mammals/A-G/giant-panda-eating.adapt.945.1.jpg

13
src/Bot/Storage/penguins Normal file
View file

@ -0,0 +1,13 @@
https://i.ytimg.com/vi/Qr6sULJnu2o/maxresdefault.jpg
https://www.apex-expeditions.com/wp-content/uploads/2015/08/newzealandSlider_Macquarie_ElephantSealKingPenguins_GRiehle_1366x601.jpg
https://www.birdlife.org/sites/default/files/styles/1600/public/slide.jpg?itok=HRhQfA1S
http://experimentexchange.com/wp-content/uploads/2016/07/penguins-fact.jpg
http://images.mentalfloss.com/sites/default/files/styles/mf_image_16x9/public/istock-511366776.jpg?itok=cWhdWNZ8&resize=1100x619
https://www.thevaporplace.ch/media/catalog/product/cache/1/thumbnail/800x800/9df78eab33525d08d6e5fb8d27136e95/a/t/atopack_penguin-15.jpg
https://www.superfastbusiness.com/wp-content/uploads/2015/10/real-time-penguin-algorithm-featured.jpg
http://www.antarctica.gov.au/__data/assets/image/0011/147737/varieties/antarctic.jpg
https://vignette.wikia.nocookie.net/robloxcreepypasta/images/1/11/AAEAAQAAAAAAAAdkAAAAJDc3YzkyYjJhLTYyZjctNDY2Mi04M2VjLTg4NjY4ZjgwYzRmNg.png/revision/latest?cb=20180207200526
https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR3xV0lhpZuhT8Nmm6LaITsppZ7VfWcWXuyu2cPHrlv_dt_M92K5g
http://goboiano.com/wp-content/uploads/2017/04/Penguin-Kemeno-Friends-Waifu.jpg
https://cdn.yoast.com/app/uploads/2015/10/Penguins_1200x628.png
https://images.justwatch.com/backdrop/8611153/s1440/pingu

23
src/Bot/Storage/pumpkin Normal file
View file

@ -0,0 +1,23 @@
https://i.pinimg.com/736x/0a/a7/8a/0aa78af25e114836e1a42585fb7b09ed--funny-pumpkins-pumkin-carving.jpg
http://wdy.h-cdn.co/assets/16/31/980x1470/gallery-1470321728-shot-two-021.jpg
https://i.pinimg.com/736x/6c/62/bf/6c62bfa73a19ffd9fc6f2d720d5e9764--cool-pumpkin-carving-carving-pumpkins.jpg
http://images6.fanpop.com/image/photos/38900000/Jack-o-Lantern-halloween-38991566-500-415.jpg
http://ghk.h-cdn.co/assets/15/37/1441834730-pumpkin-carve-2.jpg
http://diy.sndimg.com/content/dam/images/diy/fullset/2011/7/26/1/iStock-10761186_halloween-pumpkin-in-garden_s4x3.jpg.rend.hgtvcom.966.725.suffix/1420851319631.jpeg
http://ghk.h-cdn.co/assets/cm/15/11/54ffe537af882-snail-pumpkin-de.jpg
https://www.digsdigs.com/photos/2009/10/100-halloween-pumpkin-carving-ideas-12.jpg
http://diy.sndimg.com/content/dam/images/diy/fullset/2010/6/4/0/CI-Kyle-Nishioka_big-teeth-Jack-O-Lantern_s4x3.jpg.rend.hgtvcom.966.725.suffix/1420699522718.jpeg
https://twistedsifter.files.wordpress.com/2011/10/most-amazing-pumpkin-carving-ray-villafane-10.jpg?w=521&h=739
https://i.pinimg.com/736x/09/c4/b1/09c4b187b266c1f65332294f66009944--funny-pumpkins-halloween-pumpkins.jpg
http://www.evilmilk.com/pictures/The_Pumpkin_Man.jpg
http://cache.lovethispic.com/uploaded_images/blogs/13-Funny-Pumpkin-Carvings-5773-9.JPG
http://ihappyhalloweenpictures.com/wp-content/uploads/2016/10/funny-halloween-pumpkin.jpg
http://www.smallhomelove.com/wp-content/uploads/2012/08/leg-eating-pumpkin.jpg
https://cdn.shopify.com/s/files/1/0773/6789/articles/Halloween_Feature_8ff7a7c4-2cb3-4584-a85f-5d4d1e6ca26e.jpg?v=1476211360
http://4vector.com/i/free-vector-pumpkin-boy-color-version-clip-art_107714_Pumpkin_Boy_Color_Version_clip_art_hight.png
https://i.pinimg.com/736x/59/8a/0f/598a0fbf789631b76c1ffd4443194d8e--halloween-pumpkins-fall-halloween.jpg
https://i.pinimg.com/originals/8f/86/f9/8f86f95457467872b371ba697d341961.jpg
http://nerdist.com/wp-content/uploads/2015/08/taleshalloween1.jpg
http://www.designbolts.com/wp-content/uploads/2014/09/Scary-Pumpkin_Grin_stencil-Ideas.jpg
http://vignette2.wikia.nocookie.net/scoobydoo/images/7/75/Pumpkin_monsters_%28Witch%27s_Ghost%29.png/revision/latest?cb=20140520070213
https://taholtorf.files.wordpress.com/2013/10/36307-1920x1280.jpg

45
src/Bot/Storage/squirrel Normal file
View file

@ -0,0 +1,45 @@
http://orig14.deviantart.net/6016/f/2010/035/c/b/first_squirrel_assassin_by_shotokanteddy.jpg
https://thumbs-prod.si-cdn.com/eoEYA_2Hau4795uKoecUZZgz-3w=/800x600/filters:no_upscale()/https://public-media.smithsonianmag.com/filer/52/f9/52f93262-c29b-4a4f-b031-0c7ad145ed5f/42-33051942.jpg
http://images5.fanpop.com/image/photos/30700000/Squirrel-squirrels-30710732-400-300.jpg
https://www.lovethegarden.com/sites/default/files/files/Red%20%26%20Grey%20Squirrel%20picture%20side%20by%20side-LR.jpg
http://i.dailymail.co.uk/i/pix/2016/02/24/16/158F7E7C000005DC-3462228-image-a-65_1456331226865.jpg
http://2.bp.blogspot.com/-egfnMhUb8tg/T_dAIu1m6cI/AAAAAAAAPPU/v4x9q4WqWl8/s640/cute-squirrel-hey-watcha-thinkin-about.jpg
https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Squirrel_posing.jpg/287px-Squirrel_posing.jpg
https://i.pinimg.com/736x/51/db/9b/51db9bad4a87d445d321923c7d56b501--red-squirrel-animal-kingdom.jpg
https://metrouk2.files.wordpress.com/2016/10/ad_223291521.jpg?w=620&h=949&crop=1
http://www.redsquirrelsunited.org.uk/wp-content/uploads/2016/07/layer-slider.jpg
http://images.mentalfloss.com/sites/default/files/squirrel-hero.jpg?resize=1100x740
https://i.pinimg.com/736x/ce/9c/59/ce9c5990b193046400d98724595cdaf3--red-squirrel-chipmunks.jpg
https://www.brooklynpaper.com/assets/photos/40/30/dtg-squirrel-attacks-prospect-park-patrons-2017-07-28-bk01_z.jpg
http://www.freakingnews.com/pictures/16000/Squirrel-Shark-16467.jpg
http://img09.deviantart.net/5c1c/i/2013/138/0/6/barbarian_squirel_by_coucoucmoa-d64r9m4.jpg
https://i.pinimg.com/736x/b4/5c/0d/b45c0d00b1a57e9f84f27f13cb019001--baby-squirrel-red-squirrel.jpg
https://i.pinimg.com/736x/0f/75/87/0f7587bb613ab524763afe8c9a532e5c--cute-squirrel-squirrels.jpg
http://cdn.images.express.co.uk/img/dynamic/128/590x/Grey-squirrel-828838.jpg
http://www.lovethispic.com/uploaded_images/79964-Squirrel-Smelling-A-Flower.jpg
https://i.pinimg.com/736x/23/d5/f9/23d5f9868f7d76c79c49bef53ae08f7f--squirrel-funny-red-squirrel.jpg
http://stories.barkpost.com/wp-content/uploads/2016/01/squirrel-3-copy.jpg
https://i.ytimg.com/vi/pzUs0DdzK3Y/hqdefault.jpg
https://www.askideas.com/media/41/I-Swear-It-Wasnt-Me-Funny-Squirrel-Meme-Picture-For-Facebook.jpg
https://i.pinimg.com/736x/2d/54/d8/2d54d8d2a9b3ab9d3e78544b75afd88e--funny-animal-pictures-humorous-pictures.jpg
http://www.funny-animalpictures.com/media/content/items/images/funnysquirrels0012_O.jpg
http://funny-pics.co/wp-content/uploads/funny-squirrel-and-coffee-picture.jpg
https://pbs.twimg.com/media/Bi4Ij6CIgAAgEdZ.jpg
http://www.funnyjunksite.com/pictures/wp-content/uploads/2015/06/Funny-Superman-Squirrels.jpg
https://i.pinimg.com/736x/bf/35/00/bf3500104f8394909d116259d1f0575e--funny-squirrel-squirrel-girl.jpg
http://quotespill.com/wp-content/uploads/2017/07/Squirrel-Meme-Draw-me-like-one-of-your-french-squirrrels-min.jpg
https://i.pinimg.com/736x/e2/16/bb/e216bba53f80fc8e0111d371e9850159--funny-squirrels-cute-squirrel.jpg
https://i.pinimg.com/736x/52/43/c9/5243c93377245be1f686218c266d775c--funny-squirrel-baby-squirrel.jpg
https://i.pinimg.com/736x/0c/be/1d/0cbe1da8ad2c0cf3882a806b6fd88965--cute-pictures-funny-animal-pictures.jpg
https://i.pinimg.com/736x/e5/08/67/e508670aa00ca3c896eccb81c4f6e2a8--funny-squirrel-baby-squirrel.jpg
https://i.pinimg.com/736x/1c/7d/4f/1c7d4f067a10066aad802ce5ac468d71--group-boards-a-squirrel.jpg
http://funny-pics.co/wp-content/uploads/funny-squirrel-on-a-branch.jpg
http://loldamn.com/wp-content/uploads/2016/06/funny-squirrel-playing-water-bending.jpg
https://cdn.trendhunterstatic.com/thumbs/squirrel-photography.jpeg
https://i.pinimg.com/736x/d6/42/12/d64212cc6221916db4173962bf6c131a--cute-squirrel-baby-squirrel.jpg
https://i.pinimg.com/236x/10/13/58/101358f2afc2c7d6b6a668046e7b8382--funny-animal-pictures-funny-animals.jpg
https://i.pinimg.com/736x/da/0d/fe/da0dfe93bb26887795f906e8fa97d68e--secret-squirrel-cute-squirrel.jpg
http://2.bp.blogspot.com/-HLieBqEuQoM/UDkRmeyzB5I/AAAAAAAABHs/RtsEynn5t6Y/s1600/hd-squirrel-wallpaper-with-a-brown-squirrel-eating-watermelon-wallpapers-backgrounds-pictures-photos.jpg
http://www.city-data.com/forum/members/brenda-starz-328928-albums-brenda-s-funny-squirrel-comment-pic-s-pic5075-punk-squirrels.jpg
http://img15.deviantart.net/9c50/i/2011/213/c/9/just_taking_it_easy_by_lou_in_canada-d42do3d.jpg
http://3.bp.blogspot.com/-AwsSk76R2Is/USQa3-dszKI/AAAAAAAABUQ/KF_F8HbtP1U/w1200-h630-p-k-no-nu/crazySquirrel.jpg

21
src/Bot/Storage/turtles Normal file
View file

@ -0,0 +1,21 @@
https://i.guim.co.uk/img/media/6b9be13031738e642f93f9271f3592044726a9b1/0_0_2863_1610/2863.jpg?w=640&h=360&q=55&auto=format&usm=12&fit=max&s=85f3b33cc158b5aa120c143dae1916ed
http://cf.ltkcdn.net/small-pets/images/std/212089-676x450-Turtle-feeding-on-leaf.jpg
https://static1.squarespace.com/static/5369465be4b0507a1fd05af0/53767a6be4b0ad0822345e52/57e40ba4893fc031e05a018f/1498243318058/solvin.jpg?format=1500w
https://c402277.ssl.cf1.rackcdn.com/photos/419/images/story_full_width/HI_287338Hero.jpg?1433950119
https://www.cdc.gov/salmonella/agbeni-08-17/images/turtle.jpg
https://cdn.arstechnica.net/wp-content/uploads/2017/08/GettyImages-524757168.jpg
http://pmdvod.nationalgeographic.com/NG_Video/595/319/4504517_098_05_TOS_thumbnail_640x360_636296259676.jpg
http://cdn1.arkive.org/media/7D/7D46329A-6ED2-4F08-909E-7B596417994A/Presentation.Large/Big-headed-turtle-close-up.jpg
http://s7d2.scene7.com/is/image/PetSmart/ARTHMB-CleaningYourTortoiseOrTurtlesHabitat-20160818?$AR1104$
https://fthmb.tqn.com/9VGWzK_GWlvrjxtdFPX6EJxOq24=/960x0/filters:no_upscale()/133605352-56a2bce53df78cf7727960db.jpg
https://i.imgur.com/46QmzgF.jpg
https://www.wildgratitude.com/wp-content/uploads/2015/07/turtle-spirit-animal1.jpg
http://www.backwaterreptiles.com/images/turtles/red-eared-slider-turtle-for-sale.jpg
https://i.pinimg.com/736x/f1/f4/13/f1f413d6d07912be6080c08b186630ac--happy-turtle-funny-stuff.jpg
http://www.dupageforest.org/uploadedImages/Content/District_News/Nature_Stories/2016/Snapping%20Turtle%20Scott%20Plantier%20STP4793.jpg
http://turtlebackzoo.com/wp-content/uploads/2016/07/exhibit-headers_0008_SOUTH-AMERICA-600x400.jpg
https://i.ytimg.com/vi/_YfYHFM3Das/maxresdefault.jpg
https://i.pinimg.com/736x/dd/4e/7f/dd4e7f2f921ac28b1d5a59174d477131--cute-baby-sea-turtles-adorable-turtles.jpg
http://kids.nationalgeographic.com/content/dam/kids/photos/animals/Reptiles/A-G/green-sea-turtle-closeup-underwater.adapt.945.1.jpg
https://i.ytimg.com/vi/p4Jj9QZFJvw/hqdefault.jpg
https://fthmb.tqn.com/nirxHkH3jBAe74ife6fJJu6k6q8=/2121x1414/filters:fill(auto,1)/Red-eared-sliders-GettyImages-617946009-58fae8835f9b581d59a5bab6.jpg

BIN
src/Bot/derp.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB