Split Geekbot.net into src/Bot, src/Core, and src/Web
This commit is contained in:
parent
7b6dd2d2f9
commit
fc0af492ad
197 changed files with 542 additions and 498 deletions
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
|
||||
namespace Geekbot.Core.CommandPreconditions
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class DisableInDirectMessageAttribute : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
var result = context.Guild.Id != 0 ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Command unavailable in Direct Messaging");
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
21
src/Core/Constants.cs
Normal file
21
src/Core/Constants.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Geekbot.Core
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string Name = "Geekbot";
|
||||
|
||||
public static string BotVersion()
|
||||
{
|
||||
return typeof(Constants).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
|
||||
public static string LibraryVersion()
|
||||
{
|
||||
return typeof(Discord.WebSocket.DiscordSocketClient).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
|
||||
public const double ApiVersion = 1;
|
||||
}
|
||||
}
|
93
src/Core/Converters/EmojiConverter.cs
Normal file
93
src/Core/Converters/EmojiConverter.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace Geekbot.Core.Converters
|
||||
{
|
||||
public class EmojiConverter : IEmojiConverter
|
||||
{
|
||||
public string NumberToEmoji(int number)
|
||||
{
|
||||
if (number == 10)
|
||||
{
|
||||
return "🔟";
|
||||
}
|
||||
var emojiMap = new[]
|
||||
{
|
||||
":zero:",
|
||||
":one:",
|
||||
":two:",
|
||||
":three:",
|
||||
":four:",
|
||||
":five:",
|
||||
":six:",
|
||||
":seven:",
|
||||
":eight:",
|
||||
":nine:"
|
||||
};
|
||||
var numbers = number.ToString().ToCharArray();
|
||||
var returnString = new StringBuilder();
|
||||
foreach (var n in numbers)
|
||||
{
|
||||
returnString.Append(emojiMap[int.Parse(n.ToString())]);
|
||||
}
|
||||
return returnString.ToString();
|
||||
}
|
||||
|
||||
public string TextToEmoji(string text)
|
||||
{
|
||||
var emojiMap = new Hashtable
|
||||
{
|
||||
['A'] = ":regional_indicator_a: ",
|
||||
['B'] = ":b: ",
|
||||
['C'] = ":regional_indicator_c: ",
|
||||
['D'] = ":regional_indicator_d: ",
|
||||
['E'] = ":regional_indicator_e: ",
|
||||
['F'] = ":regional_indicator_f: ",
|
||||
['G'] = ":regional_indicator_g: ",
|
||||
['H'] = ":regional_indicator_h: ",
|
||||
['I'] = ":regional_indicator_i: ",
|
||||
['J'] = ":regional_indicator_j: ",
|
||||
['K'] = ":regional_indicator_k: ",
|
||||
['L'] = ":regional_indicator_l: ",
|
||||
['M'] = ":regional_indicator_m: ",
|
||||
['N'] = ":regional_indicator_n: ",
|
||||
['O'] = ":regional_indicator_o: ",
|
||||
['P'] = ":regional_indicator_p: ",
|
||||
['Q'] = ":regional_indicator_q: ",
|
||||
['R'] = ":regional_indicator_r: ",
|
||||
['S'] = ":regional_indicator_s: ",
|
||||
['T'] = ":regional_indicator_t: ",
|
||||
['U'] = ":regional_indicator_u: ",
|
||||
['V'] = ":regional_indicator_v: ",
|
||||
['W'] = ":regional_indicator_w: ",
|
||||
['X'] = ":regional_indicator_x: ",
|
||||
['Y'] = ":regional_indicator_y: ",
|
||||
['Z'] = ":regional_indicator_z: ",
|
||||
['!'] = ":exclamation: ",
|
||||
['?'] = ":question: ",
|
||||
['#'] = ":hash: ",
|
||||
['*'] = ":star2: ",
|
||||
['+'] = ":heavy_plus_sign: ",
|
||||
['0'] = ":zero: ",
|
||||
['1'] = ":one: ",
|
||||
['2'] = ":two: ",
|
||||
['3'] = ":three: ",
|
||||
['4'] = ":four: ",
|
||||
['5'] = ":five: ",
|
||||
['6'] = ":six: ",
|
||||
['7'] = ":seven: ",
|
||||
['8'] = ":eight: ",
|
||||
['9'] = ":nine: ",
|
||||
[' '] = " "
|
||||
};
|
||||
var letters = text.ToUpper().ToCharArray();
|
||||
var returnString = new StringBuilder();
|
||||
foreach (var n in letters)
|
||||
{
|
||||
var emoji = emojiMap[n] ?? n;
|
||||
returnString.Append(emoji);
|
||||
}
|
||||
return returnString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
8
src/Core/Converters/IEmojiConverter.cs
Normal file
8
src/Core/Converters/IEmojiConverter.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Geekbot.Core.Converters
|
||||
{
|
||||
public interface IEmojiConverter
|
||||
{
|
||||
string NumberToEmoji(int number);
|
||||
string TextToEmoji(string text);
|
||||
}
|
||||
}
|
7
src/Core/Converters/IMtgManaConverter.cs
Normal file
7
src/Core/Converters/IMtgManaConverter.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Core.Converters
|
||||
{
|
||||
public interface IMtgManaConverter
|
||||
{
|
||||
string ConvertMana(string mana);
|
||||
}
|
||||
}
|
81
src/Core/Converters/MtgManaConverter.cs
Normal file
81
src/Core/Converters/MtgManaConverter.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Geekbot.Core.Converters
|
||||
{
|
||||
public class MtgManaConverter : IMtgManaConverter
|
||||
{
|
||||
private readonly Dictionary<string, string> _manaDict;
|
||||
|
||||
public MtgManaConverter()
|
||||
{
|
||||
// these emotes can be found at https://discord.gg/bz8HyA7
|
||||
_manaDict = new Dictionary<string, string>
|
||||
{
|
||||
{"{0}", "<:mtg_0:415216130043412482>"},
|
||||
{"{1}", "<:mtg_1:415216130253389835>"},
|
||||
{"{2}", "<:mtg_2:415216130031091713>"},
|
||||
{"{3}", "<:mtg_3:415216130467037194>"},
|
||||
{"{4}", "<:mtg_4:415216130026635295>"},
|
||||
{"{5}", "<:mtg_5:415216130492203008>"},
|
||||
{"{6}", "<:mtg_6:415216130458779658>"},
|
||||
{"{7}", "<:mtg_7:415216130190475265>"},
|
||||
{"{8}", "<:mtg_8:415216130517630986>"},
|
||||
{"{9}", "<:mtg_9:415216130500722689>"},
|
||||
{"{10", "<:mtg_10:415216130450391051>"},
|
||||
{"{11}", "<:mtg_11:415216130811101185>"},
|
||||
{"{12}", "<:mtg_12:415216130525888532>"},
|
||||
{"{13}", "<:mtg_13:415216130517631000>"},
|
||||
{"{14}", "<:mtg_14:415216130165178370>"},
|
||||
{"{15}", "<:mtg_15:415216130576089108>"},
|
||||
{"{16}", "<:mtg_16:415216130358247425>"},
|
||||
{"{17}", "<:mtg_17:415216130601517056>"},
|
||||
{"{18}", "<:mtg_18:415216130462842891>"},
|
||||
{"{19}", "<:mtg_19:415216130614099988>"},
|
||||
{"{20}", "<:mtg_20:415216130656043038>"},
|
||||
{"{W}", "<:mtg_white:415216131515744256>"},
|
||||
{"{U}", "<:mtg_blue:415216130521694209>"},
|
||||
{"{B}", "<:mtg_black:415216130873884683>"},
|
||||
{"{R}", "<:mtg_red:415216131322806272>"},
|
||||
{"{G}", "<:mtg_green:415216131180331009>"},
|
||||
{"{S}", "<:mtg_s:415216131293446144>"},
|
||||
{"{T}", "<:mtg_tap:415258392727257088>"},
|
||||
{"{C}", "<:mtg_colorless:415216130706374666>"},
|
||||
{"{2/W}", "<:mtg_2w:415216130446065664>"},
|
||||
{"{2/U}", "<:mtg_2u:415216130429550592>"},
|
||||
{"{2/B}", "<:mtg_2b:415216130160984065>"},
|
||||
{"{2/R}", "<:mtg_2r:415216130454716436>"},
|
||||
{"{2/G}", "<:mtg_2g:415216130420899840>"},
|
||||
{"{W/U}", "<:mtg_wu:415216130970484736>"},
|
||||
{"{W/B}", "<:mtg_wb:415216131222011914>"},
|
||||
{"{U/R}", "<:mtg_ur:415216130962096128>"},
|
||||
{"{U/B}", "<:mtg_ub:415216130865758218>"},
|
||||
{"{R/W}", "<:mtg_rw:415216130878210057>"},
|
||||
{"{G/W}", "<:mtg_gw:415216130567962646>"},
|
||||
{"{G/U}", "<:mtg_gu:415216130739666945>"},
|
||||
{"{B/R}", "<:mtg_br:415216130580283394>"},
|
||||
{"{B/G}", "<:mtg_bg:415216130781609994>"},
|
||||
{"{U/P}", "<:mtg_up:415216130861432842>"},
|
||||
{"{R/P}", "<:mtg_rp:415216130597322783>"},
|
||||
{"{G/P}", "<:mtg_gp:415216130760769546>"},
|
||||
{"{W/P}", "<:mtg_wp:415216131541041172>"},
|
||||
{"{B/P}", "<:mtg_bp:415216130664169482>"}
|
||||
};
|
||||
}
|
||||
|
||||
public string ConvertMana(string mana)
|
||||
{
|
||||
var rgx = Regex.Matches(mana, @"(\{(.*?)\})");
|
||||
foreach (Match manaTypes in rgx)
|
||||
{
|
||||
var m = _manaDict.GetValueOrDefault(manaTypes.Value);
|
||||
if (!string.IsNullOrEmpty(m))
|
||||
{
|
||||
mana = mana.Replace(manaTypes.Value, m);
|
||||
}
|
||||
}
|
||||
|
||||
return mana;
|
||||
}
|
||||
}
|
||||
}
|
40
src/Core/Core.csproj
Normal file
40
src/Core/Core.csproj
Normal file
|
@ -0,0 +1,40 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
|
||||
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
|
||||
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.0-DEV</Version>
|
||||
<RootNamespace>Geekbot.Core</RootNamespace>
|
||||
<AssemblyName>Geekbot.Core</AssemblyName>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Discord.Net" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0-preview.7.*" />
|
||||
<PackageReference Include="MyAnimeListSharp" Version="1.3.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NLog" Version="4.7.2" />
|
||||
<PackageReference Include="NLog.Config" Version="4.7.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.0-preview6" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
<PackageReference Include="SumoLogic.Logging.NLog" Version="1.0.1.3" />
|
||||
<PackageReference Include="YamlDotNet" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Localization\Translations.yml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
21
src/Core/Database/DatabaseContext.cs
Normal file
21
src/Core/Database/DatabaseContext.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using Geekbot.Core.Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Geekbot.Core.Database
|
||||
{
|
||||
public class DatabaseContext : DbContext
|
||||
{
|
||||
public DbSet<QuoteModel> Quotes { get; set; }
|
||||
public DbSet<UserModel> Users { get; set; }
|
||||
public DbSet<GuildSettingsModel> GuildSettings { get; set; }
|
||||
public DbSet<KarmaModel> Karma { get; set; }
|
||||
public DbSet<ShipsModel> Ships { get; set; }
|
||||
public DbSet<RollsModel> Rolls { get; set; }
|
||||
public DbSet<MessagesModel> Messages { get; set; }
|
||||
public DbSet<SlapsModel> Slaps { get; set; }
|
||||
public DbSet<GlobalsModel> Globals { get; set; }
|
||||
public DbSet<RoleSelfServiceModel> RoleSelfService { get; set; }
|
||||
public DbSet<CookiesModel> Cookies { get; set; }
|
||||
public DbSet<ReactionListenerModel> ReactionListeners { get; set; }
|
||||
}
|
||||
}
|
58
src/Core/Database/DatabaseInitializer.cs
Normal file
58
src/Core/Database/DatabaseInitializer.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using Geekbot.Core.Database.LoggingAdapter;
|
||||
using Geekbot.Core.Logger;
|
||||
using Npgsql.Logging;
|
||||
|
||||
namespace Geekbot.Core.Database
|
||||
{
|
||||
public class DatabaseInitializer
|
||||
{
|
||||
private readonly RunParameters _runParameters;
|
||||
private readonly GeekbotLogger _logger;
|
||||
|
||||
public DatabaseInitializer(RunParameters runParameters, GeekbotLogger logger)
|
||||
{
|
||||
_runParameters = runParameters;
|
||||
_logger = logger;
|
||||
NpgsqlLogManager.Provider = new NpgsqlLoggingProviderAdapter(logger, runParameters);
|
||||
}
|
||||
|
||||
public DatabaseContext Initialize()
|
||||
{
|
||||
DatabaseContext database = null;
|
||||
try
|
||||
{
|
||||
if (_runParameters.InMemory)
|
||||
{
|
||||
database = new InMemoryDatabase("geekbot");
|
||||
}
|
||||
else
|
||||
{
|
||||
database = new SqlDatabase(new SqlConnectionString
|
||||
{
|
||||
Host = _runParameters.DbHost,
|
||||
Port = _runParameters.DbPort,
|
||||
Database = _runParameters.DbDatabase,
|
||||
Username = _runParameters.DbUser,
|
||||
Password = _runParameters.DbPassword,
|
||||
RequireSsl = _runParameters.DbSsl,
|
||||
TrustServerCertificate = _runParameters.DbTrustCert,
|
||||
RedshiftCompatibility = _runParameters.DbRedshiftCompatibility
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(LogSource.Geekbot, "Could not Connect to datbase", e);
|
||||
Environment.Exit(GeekbotExitCode.DatabaseConnectionFailed.GetHashCode());
|
||||
}
|
||||
|
||||
if (_runParameters.DbLogging)
|
||||
{
|
||||
_logger.Information(LogSource.Database, $"Connected with {database.Database.ProviderName}");
|
||||
}
|
||||
|
||||
return database;
|
||||
}
|
||||
}
|
||||
}
|
17
src/Core/Database/InMemoryDatabase.cs
Normal file
17
src/Core/Database/InMemoryDatabase.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Geekbot.Core.Database
|
||||
{
|
||||
public class InMemoryDatabase : DatabaseContext
|
||||
{
|
||||
private readonly string _name;
|
||||
|
||||
public InMemoryDatabase(string name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
=> optionsBuilder.UseInMemoryDatabase(_name);
|
||||
}
|
||||
}
|
68
src/Core/Database/LoggingAdapter/NpgsqlLoggingAdapter.cs
Normal file
68
src/Core/Database/LoggingAdapter/NpgsqlLoggingAdapter.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using Geekbot.Core.Logger;
|
||||
using Npgsql.Logging;
|
||||
using LogLevel = NLog.LogLevel;
|
||||
|
||||
namespace Geekbot.Core.Database.LoggingAdapter
|
||||
{
|
||||
public class NpgsqlLoggingAdapter : NpgsqlLogger
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly IGeekbotLogger _geekbotLogger;
|
||||
private readonly RunParameters _runParameters;
|
||||
|
||||
public NpgsqlLoggingAdapter(string name, IGeekbotLogger geekbotLogger, RunParameters runParameters)
|
||||
{
|
||||
_name = name.Substring(7);
|
||||
_geekbotLogger = geekbotLogger;
|
||||
_runParameters = runParameters;
|
||||
geekbotLogger.Trace(LogSource.Database, $"Loaded Npgsql logging adapter: {name}");
|
||||
}
|
||||
|
||||
public override bool IsEnabled(NpgsqlLogLevel level)
|
||||
{
|
||||
return (_runParameters.DbLogging && _geekbotLogger.GetNLogger().IsEnabled(ToGeekbotLogLevel(level)));
|
||||
}
|
||||
|
||||
public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception exception = null)
|
||||
{
|
||||
var nameAndMessage = $"{_name}: {msg}";
|
||||
switch (level)
|
||||
{
|
||||
case NpgsqlLogLevel.Trace:
|
||||
_geekbotLogger.Trace(LogSource.Database, nameAndMessage);
|
||||
break;
|
||||
case NpgsqlLogLevel.Debug:
|
||||
_geekbotLogger.Debug(LogSource.Database, nameAndMessage);
|
||||
break;
|
||||
case NpgsqlLogLevel.Info:
|
||||
_geekbotLogger.Information(LogSource.Database, nameAndMessage);
|
||||
break;
|
||||
case NpgsqlLogLevel.Warn:
|
||||
_geekbotLogger.Warning(LogSource.Database, nameAndMessage, exception);
|
||||
break;
|
||||
case NpgsqlLogLevel.Error:
|
||||
case NpgsqlLogLevel.Fatal:
|
||||
_geekbotLogger.Error(LogSource.Database, nameAndMessage, exception);
|
||||
break;
|
||||
default:
|
||||
_geekbotLogger.Information(LogSource.Database, nameAndMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static LogLevel ToGeekbotLogLevel(NpgsqlLogLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
NpgsqlLogLevel.Trace => LogLevel.Trace,
|
||||
NpgsqlLogLevel.Debug => LogLevel.Debug,
|
||||
NpgsqlLogLevel.Info => LogLevel.Info,
|
||||
NpgsqlLogLevel.Warn => LogLevel.Warn,
|
||||
NpgsqlLogLevel.Error => LogLevel.Error,
|
||||
NpgsqlLogLevel.Fatal => LogLevel.Fatal,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(level))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Geekbot.Core.Logger;
|
||||
using Npgsql.Logging;
|
||||
|
||||
namespace Geekbot.Core.Database.LoggingAdapter
|
||||
{
|
||||
public class NpgsqlLoggingProviderAdapter : INpgsqlLoggingProvider
|
||||
{
|
||||
private readonly GeekbotLogger _geekbotLogger;
|
||||
private readonly RunParameters _runParameters;
|
||||
|
||||
public NpgsqlLoggingProviderAdapter(GeekbotLogger geekbotLogger, RunParameters runParameters)
|
||||
{
|
||||
_geekbotLogger = geekbotLogger;
|
||||
_runParameters = runParameters;
|
||||
}
|
||||
|
||||
public NpgsqlLogger CreateLogger(string name)
|
||||
{
|
||||
return new NpgsqlLoggingAdapter(name, _geekbotLogger, _runParameters);
|
||||
}
|
||||
}
|
||||
}
|
21
src/Core/Database/Models/CookiesModel.cs
Normal file
21
src/Core/Database/Models/CookiesModel.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class CookiesModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long UserId { get; set; }
|
||||
|
||||
public int Cookies { get; set; } = 0;
|
||||
|
||||
public DateTimeOffset? LastPayout { get; set; }
|
||||
}
|
||||
}
|
18
src/Core/Database/Models/GlobalsModel.cs
Normal file
18
src/Core/Database/Models/GlobalsModel.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class GlobalsModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Value { get; set; }
|
||||
|
||||
public string Meta { get; set; }
|
||||
}
|
||||
}
|
31
src/Core/Database/Models/GuildSettingsModel.cs
Normal file
31
src/Core/Database/Models/GuildSettingsModel.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class GuildSettingsModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
public bool Ping { get; set; } = false;
|
||||
|
||||
public bool Hui { get; set; } = false;
|
||||
|
||||
public long ModChannel { get; set; } = 0;
|
||||
|
||||
public string WelcomeMessage { get; set; }
|
||||
|
||||
public long WelcomeChannel { get; set; }
|
||||
|
||||
public bool ShowDelete { get; set; } = false;
|
||||
|
||||
public bool ShowLeave { get; set; } = false;
|
||||
|
||||
public string WikiLang { get; set; } = "en";
|
||||
|
||||
public string Language { get; set; } = "EN";
|
||||
}
|
||||
}
|
21
src/Core/Database/Models/KarmaModel.cs
Normal file
21
src/Core/Database/Models/KarmaModel.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class KarmaModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long UserId { get; set; }
|
||||
|
||||
public int Karma { get; set; }
|
||||
|
||||
public DateTimeOffset TimeOut { get; set; }
|
||||
}
|
||||
}
|
18
src/Core/Database/Models/MessagesModel.cs
Normal file
18
src/Core/Database/Models/MessagesModel.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class MessagesModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long UserId { get; set; }
|
||||
|
||||
public int MessageCount { get; set; }
|
||||
}
|
||||
}
|
28
src/Core/Database/Models/QuoteModel.cs
Normal file
28
src/Core/Database/Models/QuoteModel.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class QuoteModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public int InternalId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.DateTime)]
|
||||
public DateTime Time { get; set; }
|
||||
|
||||
public string Quote { get; set; }
|
||||
|
||||
public string Image { get; set; }
|
||||
}
|
||||
}
|
22
src/Core/Database/Models/ReactionListenerModel.cs
Normal file
22
src/Core/Database/Models/ReactionListenerModel.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class ReactionListenerModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long MessageId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long RoleId { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Reaction { get; set; }
|
||||
}
|
||||
}
|
17
src/Core/Database/Models/RoleSelfServiceModel.cs
Normal file
17
src/Core/Database/Models/RoleSelfServiceModel.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class RoleSelfServiceModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
public long RoleId { get; set; }
|
||||
|
||||
public string WhiteListName { get; set; }
|
||||
}
|
||||
}
|
18
src/Core/Database/Models/RollsModel.cs
Normal file
18
src/Core/Database/Models/RollsModel.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class RollsModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long UserId { get; set; }
|
||||
|
||||
public int Rolls { get; set; }
|
||||
}
|
||||
}
|
16
src/Core/Database/Models/ShipsModel.cs
Normal file
16
src/Core/Database/Models/ShipsModel.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class ShipsModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public long FirstUserId { get; set; }
|
||||
|
||||
public long SecondUserId { get; set; }
|
||||
|
||||
public int Strength { get; set; }
|
||||
}
|
||||
}
|
20
src/Core/Database/Models/SlapsModel.cs
Normal file
20
src/Core/Database/Models/SlapsModel.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class SlapsModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long GuildId { get; set; }
|
||||
|
||||
[Required]
|
||||
public long UserId { get; set; }
|
||||
|
||||
public int Given { get; set; }
|
||||
|
||||
public int Recieved { get; set; }
|
||||
}
|
||||
}
|
27
src/Core/Database/Models/UserModel.cs
Normal file
27
src/Core/Database/Models/UserModel.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Geekbot.Core.Database.Models
|
||||
{
|
||||
public class UserModel
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public long UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Discriminator { get; set; }
|
||||
|
||||
public string AvatarUrl { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool IsBot { get; set; }
|
||||
|
||||
public DateTimeOffset Joined { get; set; }
|
||||
}
|
||||
}
|
39
src/Core/Database/SqlConnectionString.cs
Normal file
39
src/Core/Database/SqlConnectionString.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Text;
|
||||
|
||||
namespace Geekbot.Core.Database
|
||||
{
|
||||
public class SqlConnectionString
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public string Port { get; set; }
|
||||
public string Database { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool RequireSsl { get; set; }
|
||||
public bool TrustServerCertificate { get; set; }
|
||||
public bool RedshiftCompatibility { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("Application Name=Geekbot;");
|
||||
|
||||
sb.Append($"Host={Host};");
|
||||
sb.Append($"Port={Port};");
|
||||
sb.Append($"Database={Database};");
|
||||
sb.Append($"Username={Username};");
|
||||
sb.Append($"Password={Password};");
|
||||
|
||||
var sslMode = RequireSsl ? "Require" : "Prefer";
|
||||
sb.Append($"SSL Mode={sslMode};");
|
||||
sb.Append($"Trust Server Certificate={TrustServerCertificate.ToString()};");
|
||||
|
||||
if (RedshiftCompatibility)
|
||||
{
|
||||
sb.Append("Server Compatibility Mode=Redshift");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
17
src/Core/Database/SqlDatabase.cs
Normal file
17
src/Core/Database/SqlDatabase.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Geekbot.Core.Database
|
||||
{
|
||||
public class SqlDatabase : DatabaseContext
|
||||
{
|
||||
private readonly SqlConnectionString _connectionString;
|
||||
|
||||
public SqlDatabase(SqlConnectionString connectionString)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
=> optionsBuilder.UseNpgsql(_connectionString.ToString());
|
||||
}
|
||||
}
|
13
src/Core/DiceParser/DiceException.cs
Normal file
13
src/Core/DiceParser/DiceException.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public class DiceException : Exception
|
||||
{
|
||||
public DiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public string DiceName { get; set; }
|
||||
}
|
||||
}
|
11
src/Core/DiceParser/DiceInput.cs
Normal file
11
src/Core/DiceParser/DiceInput.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public class DiceInput
|
||||
{
|
||||
public List<SingleDie> Dice { get; set; } = new List<SingleDie>();
|
||||
public DiceInputOptions Options { get; set; } = new DiceInputOptions();
|
||||
public int SkillModifier { get; set; }
|
||||
}
|
||||
}
|
7
src/Core/DiceParser/DiceInputOptions.cs
Normal file
7
src/Core/DiceParser/DiceInputOptions.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public struct DiceInputOptions
|
||||
{
|
||||
public bool ShowTotal { get; set; }
|
||||
}
|
||||
}
|
102
src/Core/DiceParser/DiceParser.cs
Normal file
102
src/Core/DiceParser/DiceParser.cs
Normal file
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Geekbot.Core.RandomNumberGenerator;
|
||||
|
||||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public class DiceParser : IDiceParser
|
||||
{
|
||||
private readonly IRandomNumberGenerator _randomNumberGenerator;
|
||||
private readonly Regex _inputRegex;
|
||||
private readonly Regex _singleDieRegex;
|
||||
|
||||
public DiceParser(IRandomNumberGenerator randomNumberGenerator)
|
||||
{
|
||||
_randomNumberGenerator = randomNumberGenerator;
|
||||
_inputRegex = new Regex(
|
||||
@"((?<DieAdvantage>\+\d+d\d+)|(?<DieDisadvantage>\-\d+d\d+)|(?<DieNormal>\d+d\d+)|(?<Keywords>(total))|(?<SkillModifer>(\+|\-)\d+))\s",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase,
|
||||
new TimeSpan(0, 0, 2));
|
||||
_singleDieRegex = new Regex(
|
||||
@"\d+d\d+",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase,
|
||||
new TimeSpan(0, 0, 0, 0, 500));
|
||||
}
|
||||
|
||||
public DiceInput Parse(string input)
|
||||
{
|
||||
// adding a whitespace at the end, otherwise the parser might pickup on false items
|
||||
var inputWithExtraWhitespace = $"{input} ";
|
||||
|
||||
var matches = _inputRegex.Matches(inputWithExtraWhitespace);
|
||||
var result = new DiceInput();
|
||||
var resultOptions = new DiceInputOptions();
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
foreach (Group matchGroup in match.Groups)
|
||||
{
|
||||
if (matchGroup.Success)
|
||||
{
|
||||
switch (matchGroup.Name)
|
||||
{
|
||||
case "DieNormal":
|
||||
result.Dice.Add(Die(matchGroup.Value, DieAdvantageType.None));
|
||||
break;
|
||||
case "DieAdvantage":
|
||||
result.Dice.Add(Die(matchGroup.Value, DieAdvantageType.Advantage));
|
||||
break;
|
||||
case "DieDisadvantage":
|
||||
result.Dice.Add(Die(matchGroup.Value, DieAdvantageType.Disadvantage));
|
||||
break;
|
||||
case "Keywords":
|
||||
Keywords(matchGroup.Value, ref resultOptions);
|
||||
break;
|
||||
case "SkillModifer":
|
||||
result.SkillModifier = SkillModifer(matchGroup.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.Dice.Any())
|
||||
{
|
||||
result.Dice.Add(new SingleDie(_randomNumberGenerator));
|
||||
}
|
||||
|
||||
result.Options = resultOptions;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SingleDie Die(string match, DieAdvantageType advantageType)
|
||||
{
|
||||
var x = _singleDieRegex.Match(match).Value.Split('d');
|
||||
var die = new SingleDie(_randomNumberGenerator)
|
||||
{
|
||||
Amount = int.Parse(x[0]),
|
||||
Sides = int.Parse(x[1]),
|
||||
AdvantageType = advantageType
|
||||
};
|
||||
die.ValidateDie();
|
||||
return die;
|
||||
}
|
||||
|
||||
private int SkillModifer(string match)
|
||||
{
|
||||
return int.Parse(match);
|
||||
}
|
||||
|
||||
private void Keywords(string match, ref DiceInputOptions options)
|
||||
{
|
||||
switch (match)
|
||||
{
|
||||
case "total":
|
||||
options.ShowTotal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
src/Core/DiceParser/DieAdvantageType.cs
Normal file
9
src/Core/DiceParser/DieAdvantageType.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public enum DieAdvantageType
|
||||
{
|
||||
Advantage,
|
||||
Disadvantage,
|
||||
None
|
||||
}
|
||||
}
|
30
src/Core/DiceParser/DieResult.cs
Normal file
30
src/Core/DiceParser/DieResult.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public class DieResult
|
||||
{
|
||||
// public int Result { get; set; }
|
||||
public int Roll1 { get; set; }
|
||||
public int Roll2 { get; set; }
|
||||
public DieAdvantageType AdvantageType { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return AdvantageType switch
|
||||
{
|
||||
DieAdvantageType.Advantage => Roll1 > Roll2 ? $"(**{Roll1}**, {Roll2})" : $"({Roll1}, **{Roll2}**)",
|
||||
DieAdvantageType.Disadvantage => Roll1 < Roll2 ? $"(**{Roll1}**, {Roll2})" : $"({Roll1}, **{Roll2}**)",
|
||||
_ => Result.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public int Result => AdvantageType switch
|
||||
{
|
||||
DieAdvantageType.None => Roll1,
|
||||
DieAdvantageType.Advantage => Math.Max(Roll1, Roll2),
|
||||
DieAdvantageType.Disadvantage => Math.Min(Roll1, Roll2),
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
7
src/Core/DiceParser/IDiceParser.cs
Normal file
7
src/Core/DiceParser/IDiceParser.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public interface IDiceParser
|
||||
{
|
||||
DiceInput Parse(string input);
|
||||
}
|
||||
}
|
72
src/Core/DiceParser/SingleDie.cs
Normal file
72
src/Core/DiceParser/SingleDie.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using System.Collections.Generic;
|
||||
using Geekbot.Core.Extensions;
|
||||
using Geekbot.Core.RandomNumberGenerator;
|
||||
|
||||
namespace Geekbot.Core.DiceParser
|
||||
{
|
||||
public class SingleDie
|
||||
{
|
||||
private readonly IRandomNumberGenerator _random;
|
||||
|
||||
public SingleDie(IRandomNumberGenerator random)
|
||||
{
|
||||
_random = random;
|
||||
}
|
||||
|
||||
public int Sides { get; set; } = 20;
|
||||
public int Amount { get; set; } = 1;
|
||||
public DieAdvantageType AdvantageType { get; set; } = DieAdvantageType.None;
|
||||
|
||||
public string DiceName => AdvantageType switch
|
||||
{
|
||||
DieAdvantageType.Advantage => $"{Amount}d{Sides} (with advantage)",
|
||||
DieAdvantageType.Disadvantage => $"{Amount}d{Sides} (with disadvantage)",
|
||||
_ => $"{Amount}d{Sides}"
|
||||
};
|
||||
|
||||
public List<DieResult> Roll()
|
||||
{
|
||||
var results = new List<DieResult>();
|
||||
|
||||
Amount.Times(() =>
|
||||
{
|
||||
var result = new DieResult
|
||||
{
|
||||
Roll1 = _random.Next(1, Sides + 1),
|
||||
AdvantageType = AdvantageType
|
||||
};
|
||||
|
||||
if (AdvantageType == DieAdvantageType.Advantage || AdvantageType == DieAdvantageType.Disadvantage)
|
||||
{
|
||||
result.Roll2 = _random.Next(1, Sides);
|
||||
}
|
||||
|
||||
results.Add(result);
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public void ValidateDie()
|
||||
{
|
||||
if (Amount < 1)
|
||||
{
|
||||
throw new DiceException("To few dice, must be a minimum of 1");
|
||||
}
|
||||
if (Amount > 24)
|
||||
{
|
||||
throw new DiceException("To many dice, maximum allowed is 24") { DiceName = DiceName };
|
||||
}
|
||||
|
||||
if (Sides < 2)
|
||||
{
|
||||
throw new DiceException("Die must have at least 2 sides") { DiceName = DiceName };
|
||||
}
|
||||
|
||||
if (Sides > 144)
|
||||
{
|
||||
throw new DiceException("Die can not have more than 144 sides") { DiceName = DiceName };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
src/Core/ErrorHandling/ErrorHandler.cs
Normal file
106
src/Core/ErrorHandling/ErrorHandler.cs
Normal file
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Discord.Net;
|
||||
using Geekbot.Core.Localization;
|
||||
using Geekbot.Core.Logger;
|
||||
using SharpRaven;
|
||||
using SharpRaven.Data;
|
||||
using Exception = System.Exception;
|
||||
|
||||
namespace Geekbot.Core.ErrorHandling
|
||||
{
|
||||
public class ErrorHandler : IErrorHandler
|
||||
{
|
||||
private readonly IGeekbotLogger _logger;
|
||||
private readonly ITranslationHandler _translation;
|
||||
private readonly IRavenClient _raven;
|
||||
private readonly bool _errorsInChat;
|
||||
|
||||
public ErrorHandler(IGeekbotLogger logger, ITranslationHandler translation, RunParameters runParameters)
|
||||
{
|
||||
_logger = logger;
|
||||
_translation = translation;
|
||||
_errorsInChat = runParameters.ExposeErrors;
|
||||
|
||||
var sentryDsn = runParameters.SentryEndpoint;
|
||||
if (!string.IsNullOrEmpty(sentryDsn))
|
||||
{
|
||||
_raven = new RavenClient(sentryDsn) { Release = Constants.BotVersion(), Environment = "Production" };
|
||||
_logger.Information(LogSource.Geekbot, $"Command Errors will be logged to Sentry: {sentryDsn}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_raven = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleCommandException(Exception e, ICommandContext context, string errorMessage = "def")
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorString = errorMessage == "def" ? await _translation.GetString(context.Guild?.Id ?? 0, "errorHandler", "SomethingWentWrong") : errorMessage;
|
||||
var errorObj = SimpleConextConverter.ConvertContext(context);
|
||||
if (e.Message.Contains("50007")) return;
|
||||
if (e.Message.Contains("50013")) return;
|
||||
_logger.Error(LogSource.Geekbot, "An error ocured", e, errorObj);
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
if (_errorsInChat)
|
||||
{
|
||||
var resStackTrace = string.IsNullOrEmpty(e.InnerException?.ToString()) ? e.StackTrace : e.InnerException?.ToString();
|
||||
if (!string.IsNullOrEmpty(resStackTrace))
|
||||
{
|
||||
var maxLen = Math.Min(resStackTrace.Length, 1850);
|
||||
await context.Channel.SendMessageAsync($"{e.Message}\r\n```\r\n{resStackTrace.Substring(0, maxLen)}\r\n```");
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.Channel.SendMessageAsync(e.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.Channel.SendMessageAsync(errorString);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ReportExternal(e, errorObj);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.Channel.SendMessageAsync("Something went really really wrong here");
|
||||
_logger.Error(LogSource.Geekbot, "Errorception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleHttpException(HttpException e, ICommandContext context)
|
||||
{
|
||||
var errorStrings = await _translation.GetDict(context, "httpErrors");
|
||||
switch(e.HttpCode)
|
||||
{
|
||||
case HttpStatusCode.Forbidden:
|
||||
await context.Channel.SendMessageAsync(errorStrings["403"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReportExternal(Exception e, MessageDto errorObj)
|
||||
{
|
||||
if (_raven == null) return;
|
||||
var sentryEvent = new SentryEvent(e)
|
||||
{
|
||||
Tags =
|
||||
{
|
||||
["discord_server"] = errorObj.Guild.Name,
|
||||
["discord_user"] = errorObj.User.Name
|
||||
},
|
||||
Message = errorObj.Message.Content,
|
||||
Extra = errorObj
|
||||
};
|
||||
_raven.Capture(sentryEvent);
|
||||
}
|
||||
}
|
||||
}
|
13
src/Core/ErrorHandling/IErrorHandler.cs
Normal file
13
src/Core/ErrorHandling/IErrorHandler.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Discord.Net;
|
||||
|
||||
namespace Geekbot.Core.ErrorHandling
|
||||
{
|
||||
public interface IErrorHandler
|
||||
{
|
||||
Task HandleCommandException(Exception e, ICommandContext context, string errorMessage = "def");
|
||||
Task HandleHttpException(HttpException e, ICommandContext context);
|
||||
}
|
||||
}
|
28
src/Core/Extensions/DbSetExtensions.cs
Normal file
28
src/Core/Extensions/DbSetExtensions.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
|
||||
namespace Geekbot.Core.Extensions
|
||||
{
|
||||
public static class DbSetExtensions
|
||||
{
|
||||
public static EntityEntry<T> AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
|
||||
{
|
||||
var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
|
||||
return !exists ? dbSet.Add(entity) : null;
|
||||
}
|
||||
|
||||
// https://github.com/dotnet/efcore/issues/18124
|
||||
public static IAsyncEnumerable<TEntity> AsAsyncEnumerable<TEntity>(this Microsoft.EntityFrameworkCore.DbSet<TEntity> obj) where TEntity : class
|
||||
{
|
||||
return Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsAsyncEnumerable(obj);
|
||||
}
|
||||
public static IQueryable<TEntity> Where<TEntity>(this Microsoft.EntityFrameworkCore.DbSet<TEntity> obj, System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate) where TEntity : class
|
||||
{
|
||||
return System.Linq.Queryable.Where(obj, predicate);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Core/Extensions/EmbedBuilderExtensions.cs
Normal file
12
src/Core/Extensions/EmbedBuilderExtensions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Discord;
|
||||
|
||||
namespace Geekbot.Core.Extensions
|
||||
{
|
||||
public static class EmbedBuilderExtensions
|
||||
{
|
||||
public static EmbedBuilder AddInlineField(this EmbedBuilder builder, string name, object value)
|
||||
{
|
||||
return builder.AddField(new EmbedFieldBuilder().WithIsInline(true).WithName(name).WithValue(value));
|
||||
}
|
||||
}
|
||||
}
|
15
src/Core/Extensions/IntExtensions.cs
Normal file
15
src/Core/Extensions/IntExtensions.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.Extensions
|
||||
{
|
||||
public static class IntExtensions
|
||||
{
|
||||
public static void Times(this int count, Action action)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
src/Core/Extensions/LongExtensions.cs
Normal file
12
src/Core/Extensions/LongExtensions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.Extensions
|
||||
{
|
||||
public static class LongExtensions
|
||||
{
|
||||
public static ulong AsUlong(this long thing)
|
||||
{
|
||||
return Convert.ToUInt64(thing);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Core/Extensions/StringExtensions.cs
Normal file
12
src/Core/Extensions/StringExtensions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Linq;
|
||||
|
||||
namespace Geekbot.Core.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string CapitalizeFirst(this string source)
|
||||
{
|
||||
return source.First().ToString().ToUpper() + source.Substring(1);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Core/Extensions/UlongExtensions.cs
Normal file
12
src/Core/Extensions/UlongExtensions.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.Extensions
|
||||
{
|
||||
public static class UlongExtensions
|
||||
{
|
||||
public static long AsLong(this ulong thing)
|
||||
{
|
||||
return Convert.ToInt64(thing);
|
||||
}
|
||||
}
|
||||
}
|
20
src/Core/GeekbotExitCode.cs
Normal file
20
src/Core/GeekbotExitCode.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
namespace Geekbot.Core
|
||||
{
|
||||
public enum GeekbotExitCode
|
||||
{
|
||||
// General
|
||||
Clean = 0,
|
||||
InvalidArguments = 1,
|
||||
|
||||
// Geekbot Internals
|
||||
TranslationsFailed = 201,
|
||||
|
||||
// Dependent Services
|
||||
/* 301 not in use anymore (redis) */
|
||||
DatabaseConnectionFailed = 302,
|
||||
|
||||
// Discord Related
|
||||
CouldNotLogin = 401
|
||||
|
||||
}
|
||||
}
|
68
src/Core/GlobalSettings/GlobalSettings.cs
Normal file
68
src/Core/GlobalSettings/GlobalSettings.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.Database.Models;
|
||||
|
||||
namespace Geekbot.Core.GlobalSettings
|
||||
{
|
||||
public class GlobalSettings : IGlobalSettings
|
||||
{
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly Dictionary<string, string> _cache;
|
||||
|
||||
public GlobalSettings(DatabaseContext database)
|
||||
{
|
||||
_database = database;
|
||||
_cache = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public async Task<bool> SetKey(string keyName, string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = GetKeyFull(keyName);
|
||||
if (key == null)
|
||||
{
|
||||
_database.Globals.Add(new GlobalsModel()
|
||||
{
|
||||
Name = keyName,
|
||||
Value = value
|
||||
});
|
||||
await _database.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
key.Value = value;
|
||||
_database.Globals.Update(key);
|
||||
_cache[keyName] = value;
|
||||
await _database.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetKey(string keyName)
|
||||
{
|
||||
var keyValue = "";
|
||||
if (string.IsNullOrEmpty(_cache.GetValueOrDefault(keyName)))
|
||||
{
|
||||
keyValue = _database.Globals.FirstOrDefault(k => k.Name.Equals(keyName))?.Value ?? string.Empty;
|
||||
_cache[keyName] = keyValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
keyValue = _cache[keyName];
|
||||
}
|
||||
return keyValue ;
|
||||
}
|
||||
|
||||
public GlobalsModel GetKeyFull(string keyName)
|
||||
{
|
||||
var key = _database.Globals.FirstOrDefault(k => k.Name.Equals(keyName));
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
12
src/Core/GlobalSettings/IGlobalSettings.cs
Normal file
12
src/Core/GlobalSettings/IGlobalSettings.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.Database.Models;
|
||||
|
||||
namespace Geekbot.Core.GlobalSettings
|
||||
{
|
||||
public interface IGlobalSettings
|
||||
{
|
||||
Task<bool> SetKey(string keyName, string value);
|
||||
string GetKey(string keyName);
|
||||
GlobalsModel GetKeyFull(string keyName);
|
||||
}
|
||||
}
|
68
src/Core/GuildSettingsManager/GuildSettingsManager.cs
Normal file
68
src/Core/GuildSettingsManager/GuildSettingsManager.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.Database.Models;
|
||||
using Geekbot.Core.Extensions;
|
||||
|
||||
namespace Geekbot.Core.GuildSettingsManager
|
||||
{
|
||||
public class GuildSettingsManager : IGuildSettingsManager
|
||||
{
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly Dictionary<ulong, GuildSettingsModel> _settings;
|
||||
|
||||
public GuildSettingsManager(DatabaseContext database)
|
||||
{
|
||||
_database = database;
|
||||
_settings = new Dictionary<ulong, GuildSettingsModel>();
|
||||
}
|
||||
|
||||
public GuildSettingsModel GetSettings(ulong guildId, bool createIfNonExist = true)
|
||||
{
|
||||
return _settings.ContainsKey(guildId) ? _settings[guildId] : GetFromDatabase(guildId, createIfNonExist);
|
||||
}
|
||||
|
||||
public async Task UpdateSettings(GuildSettingsModel settings)
|
||||
{
|
||||
_database.GuildSettings.Update(settings);
|
||||
if (_settings.ContainsKey(settings.GuildId.AsUlong()))
|
||||
{
|
||||
_settings[settings.GuildId.AsUlong()] = settings;
|
||||
}
|
||||
else
|
||||
{
|
||||
_settings.Add(settings.GuildId.AsUlong(), settings);
|
||||
}
|
||||
await _database.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private GuildSettingsModel GetFromDatabase(ulong guildId, bool createIfNonExist)
|
||||
{
|
||||
var settings = _database.GuildSettings.FirstOrDefault(guild => guild.GuildId.Equals(guildId.AsLong()));
|
||||
if (createIfNonExist && settings == null)
|
||||
{
|
||||
settings = CreateSettings(guildId);
|
||||
}
|
||||
|
||||
_settings.Add(guildId, settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private GuildSettingsModel CreateSettings(ulong guildId)
|
||||
{
|
||||
_database.GuildSettings.Add(new GuildSettingsModel
|
||||
{
|
||||
GuildId = guildId.AsLong(),
|
||||
Hui = false,
|
||||
Ping = false,
|
||||
Language = "EN",
|
||||
ShowDelete = false,
|
||||
ShowLeave = false,
|
||||
WikiLang = "en"
|
||||
});
|
||||
_database.SaveChanges();
|
||||
return _database.GuildSettings.FirstOrDefault(g => g.GuildId.Equals(guildId.AsLong()));
|
||||
}
|
||||
}
|
||||
}
|
11
src/Core/GuildSettingsManager/IGuildSettingsManager.cs
Normal file
11
src/Core/GuildSettingsManager/IGuildSettingsManager.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.Database.Models;
|
||||
|
||||
namespace Geekbot.Core.GuildSettingsManager
|
||||
{
|
||||
public interface IGuildSettingsManager
|
||||
{
|
||||
GuildSettingsModel GetSettings(ulong guildId, bool createIfNonExist = true);
|
||||
Task UpdateSettings(GuildSettingsModel settings);
|
||||
}
|
||||
}
|
13
src/Core/Highscores/HighscoreListEmptyException.cs
Normal file
13
src/Core/Highscores/HighscoreListEmptyException.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.Highscores
|
||||
{
|
||||
public class HighscoreListEmptyException : Exception
|
||||
{
|
||||
public HighscoreListEmptyException() {}
|
||||
|
||||
public HighscoreListEmptyException(string message) : base(message) {}
|
||||
|
||||
public HighscoreListEmptyException(string message, Exception inner) : base(message, inner) {}
|
||||
}
|
||||
}
|
105
src/Core/Highscores/HighscoreManager.cs
Normal file
105
src/Core/Highscores/HighscoreManager.cs
Normal file
|
@ -0,0 +1,105 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.Extensions;
|
||||
using Geekbot.Core.UserRepository;
|
||||
|
||||
namespace Geekbot.Core.Highscores
|
||||
{
|
||||
public class HighscoreManager : IHighscoreManager
|
||||
{
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public HighscoreManager(DatabaseContext databaseContext, IUserRepository userRepository)
|
||||
{
|
||||
_database = databaseContext;
|
||||
_userRepository = userRepository;
|
||||
|
||||
}
|
||||
|
||||
public Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount)
|
||||
{
|
||||
var list = type switch
|
||||
{
|
||||
HighscoreTypes.messages => GetMessageList(guildId, amount),
|
||||
HighscoreTypes.karma => GetKarmaList(guildId, amount),
|
||||
HighscoreTypes.rolls => GetRollsList(guildId, amount),
|
||||
HighscoreTypes.cookies => GetCookiesList(guildId, amount),
|
||||
_ => new Dictionary<ulong, int>()
|
||||
};
|
||||
|
||||
if (!list.Any())
|
||||
{
|
||||
throw new HighscoreListEmptyException($"No {type} found for guild {guildId}");
|
||||
}
|
||||
|
||||
var highscoreUsers = new Dictionary<HighscoreUserDto, int>();
|
||||
foreach (var user in list)
|
||||
{
|
||||
try
|
||||
{
|
||||
var guildUser = _userRepository.Get(user.Key);
|
||||
if (guildUser?.Username != null)
|
||||
{
|
||||
highscoreUsers.Add(new HighscoreUserDto
|
||||
{
|
||||
Username = guildUser.Username,
|
||||
Discriminator = guildUser.Discriminator,
|
||||
Avatar = guildUser.AvatarUrl
|
||||
}, user.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
highscoreUsers.Add(new HighscoreUserDto
|
||||
{
|
||||
Id = user.Key.ToString()
|
||||
}, user.Value);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return highscoreUsers;
|
||||
}
|
||||
|
||||
public Dictionary<ulong, int> GetMessageList(ulong guildId, int amount)
|
||||
{
|
||||
return _database.Messages
|
||||
.Where(k => k.GuildId.Equals(guildId.AsLong()))
|
||||
.OrderByDescending(o => o.MessageCount)
|
||||
.Take(amount)
|
||||
.ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount);
|
||||
}
|
||||
|
||||
public Dictionary<ulong, int> GetKarmaList(ulong guildId, int amount)
|
||||
{
|
||||
return _database.Karma
|
||||
.Where(k => k.GuildId.Equals(guildId.AsLong()))
|
||||
.OrderByDescending(o => o.Karma)
|
||||
.Take(amount)
|
||||
.ToDictionary(key => key.UserId.AsUlong(), key => key.Karma);
|
||||
}
|
||||
|
||||
public Dictionary<ulong, int> GetRollsList(ulong guildId, int amount)
|
||||
{
|
||||
return _database.Rolls
|
||||
.Where(k => k.GuildId.Equals(guildId.AsLong()))
|
||||
.OrderByDescending(o => o.Rolls)
|
||||
.Take(amount)
|
||||
.ToDictionary(key => key.UserId.AsUlong(), key => key.Rolls);
|
||||
}
|
||||
|
||||
private Dictionary<ulong, int> GetCookiesList(ulong guildId, int amount)
|
||||
{
|
||||
return _database.Cookies
|
||||
.Where(k => k.GuildId.Equals(guildId.AsLong()))
|
||||
.OrderByDescending(o => o.Cookies)
|
||||
.Take(amount)
|
||||
.ToDictionary(key => key.UserId.AsUlong(), key => key.Cookies);
|
||||
}
|
||||
}
|
||||
}
|
10
src/Core/Highscores/HighscoreTypes.cs
Normal file
10
src/Core/Highscores/HighscoreTypes.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Geekbot.Core.Highscores
|
||||
{
|
||||
public enum HighscoreTypes
|
||||
{
|
||||
messages,
|
||||
karma,
|
||||
rolls,
|
||||
cookies
|
||||
}
|
||||
}
|
10
src/Core/Highscores/HighscoreUserDto.cs
Normal file
10
src/Core/Highscores/HighscoreUserDto.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Geekbot.Core.Highscores
|
||||
{
|
||||
public class HighscoreUserDto
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Avatar { get; set; }
|
||||
public string Discriminator { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
12
src/Core/Highscores/IHighscoreManager.cs
Normal file
12
src/Core/Highscores/IHighscoreManager.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Geekbot.Core.Highscores
|
||||
{
|
||||
public interface IHighscoreManager
|
||||
{
|
||||
Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount);
|
||||
Dictionary<ulong, int> GetMessageList(ulong guildId, int amount);
|
||||
Dictionary<ulong, int> GetKarmaList(ulong guildId, int amount);
|
||||
Dictionary<ulong, int> GetRollsList(ulong guildId, int amount);
|
||||
}
|
||||
}
|
42
src/Core/HttpAbstractions.cs
Normal file
42
src/Core/HttpAbstractions.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Geekbot.Core
|
||||
{
|
||||
public static class HttpAbstractions
|
||||
{
|
||||
public static HttpClient CreateDefaultClient()
|
||||
{
|
||||
var client = new HttpClient
|
||||
{
|
||||
DefaultRequestHeaders =
|
||||
{
|
||||
Accept = {MediaTypeWithQualityHeaderValue.Parse("application/json")},
|
||||
}
|
||||
};
|
||||
client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Geekbot/v0.0.0 (+https://geekbot.pizzaandcoffee.rocks/)");
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public static async Task<T> Get<T>(Uri location, HttpClient httpClient = null, bool disposeClient = true)
|
||||
{
|
||||
httpClient ??= CreateDefaultClient();
|
||||
httpClient.BaseAddress = location;
|
||||
|
||||
var response = await httpClient.GetAsync(location.PathAndQuery);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (disposeClient)
|
||||
{
|
||||
httpClient.Dispose();
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(stringResponse);
|
||||
}
|
||||
}
|
||||
}
|
9
src/Core/KvInMemoryStore/IKvInMemoryStore.cs
Normal file
9
src/Core/KvInMemoryStore/IKvInMemoryStore.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Geekbot.Core.KvInMemoryStore
|
||||
{
|
||||
public interface IKvInMemoryStore
|
||||
{
|
||||
public T Get<T>(string key);
|
||||
public void Set<T>(string key, T value);
|
||||
public void Remove(string key);
|
||||
}
|
||||
}
|
32
src/Core/KvInMemoryStore/KvInMemoryStore.cs
Normal file
32
src/Core/KvInMemoryStore/KvInMemoryStore.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Geekbot.Core.KvInMemoryStore
|
||||
{
|
||||
public class KvInInMemoryStore : IKvInMemoryStore
|
||||
{
|
||||
private readonly Dictionary<string, object> _storage = new Dictionary<string, object>();
|
||||
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T) _storage[key];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public void Set<T>(string key, T value)
|
||||
{
|
||||
_storage.Remove(key);
|
||||
_storage.Add(key, value);
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
_storage.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
7
src/Core/Levels/ILevelCalc.cs
Normal file
7
src/Core/Levels/ILevelCalc.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Core.Levels
|
||||
{
|
||||
public interface ILevelCalc
|
||||
{
|
||||
int GetLevel(int? experience);
|
||||
}
|
||||
}
|
28
src/Core/Levels/LevelCalc.cs
Normal file
28
src/Core/Levels/LevelCalc.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Geekbot.Core.Levels
|
||||
{
|
||||
public class LevelCalc : ILevelCalc
|
||||
{
|
||||
private readonly int[] _levels;
|
||||
|
||||
public LevelCalc()
|
||||
{
|
||||
var levels = new List<int>();
|
||||
double total = 0;
|
||||
for (var i = 1; i < 120; i++)
|
||||
{
|
||||
total += Math.Floor(i + 300 * Math.Pow(2, i / 7.0));
|
||||
levels.Add((int) Math.Floor(total / 16));
|
||||
}
|
||||
_levels = levels.ToArray();
|
||||
}
|
||||
|
||||
public int GetLevel(int? messages)
|
||||
{
|
||||
return 1 + _levels.TakeWhile(level => !(level > messages)).Count();
|
||||
}
|
||||
}
|
||||
}
|
16
src/Core/Localization/ITranslationHandler.cs
Normal file
16
src/Core/Localization/ITranslationHandler.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
|
||||
namespace Geekbot.Core.Localization
|
||||
{
|
||||
public interface ITranslationHandler
|
||||
{
|
||||
Task<string> GetString(ulong guildId, string command, string stringName);
|
||||
string GetString(string language, string command, string stringName);
|
||||
Task<Dictionary<string, string>> GetDict(ICommandContext context, string command);
|
||||
Task<TranslationGuildContext> GetGuildContext(ICommandContext context);
|
||||
Task<bool> SetLanguage(ulong guildId, string language);
|
||||
List<string> SupportedLanguages { get; }
|
||||
}
|
||||
}
|
90
src/Core/Localization/TranslationGuildContext.cs
Normal file
90
src/Core/Localization/TranslationGuildContext.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Geekbot.Core.Localization
|
||||
{
|
||||
public class TranslationGuildContext
|
||||
{
|
||||
public ITranslationHandler TranslationHandler { get; }
|
||||
public string Language { get; }
|
||||
public Dictionary<string, string> Dict { get; }
|
||||
|
||||
public TranslationGuildContext(ITranslationHandler translationHandler, string language, Dictionary<string, string> dict)
|
||||
{
|
||||
TranslationHandler = translationHandler;
|
||||
Language = language;
|
||||
Dict = dict;
|
||||
}
|
||||
|
||||
public string GetString(string stringToFormat, params object[] args)
|
||||
{
|
||||
return string.Format(Dict[stringToFormat] ?? "", args);
|
||||
}
|
||||
|
||||
public string FormatDateTimeAsRemaining(DateTimeOffset dateTime)
|
||||
{
|
||||
var remaining = dateTime - DateTimeOffset.Now;
|
||||
const string formattable = "{0} {1}";
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (remaining.Days > 0)
|
||||
{
|
||||
var s = GetTimeString(TimeTypes.Days);
|
||||
sb.AppendFormat(formattable, remaining.Days, GetSingOrPlur(remaining.Days, s));
|
||||
}
|
||||
|
||||
if (remaining.Hours > 0)
|
||||
{
|
||||
if (sb.Length > 0) sb.Append(", ");
|
||||
var s = GetTimeString(TimeTypes.Hours);
|
||||
sb.AppendFormat(formattable, remaining.Hours, GetSingOrPlur(remaining.Hours, s));
|
||||
}
|
||||
|
||||
if (remaining.Minutes > 0)
|
||||
{
|
||||
if (sb.Length > 0) sb.Append(", ");
|
||||
var s = GetTimeString(TimeTypes.Minutes);
|
||||
sb.AppendFormat(formattable, remaining.Minutes, GetSingOrPlur(remaining.Minutes, s));
|
||||
}
|
||||
|
||||
if (remaining.Seconds > 0)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
var and = TranslationHandler.GetString(Language, "dateTime", "And");
|
||||
sb.AppendFormat(" {0} ", and);
|
||||
}
|
||||
var s = GetTimeString(TimeTypes.Seconds);
|
||||
sb.AppendFormat(formattable, remaining.Seconds, GetSingOrPlur(remaining.Seconds, s));
|
||||
}
|
||||
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
|
||||
public Task<bool> SetLanguage(ulong guildId, string language)
|
||||
{
|
||||
return TranslationHandler.SetLanguage(guildId, language);
|
||||
}
|
||||
|
||||
private string GetTimeString(TimeTypes type)
|
||||
{
|
||||
return TranslationHandler.GetString(Language, "dateTime", type.ToString());
|
||||
}
|
||||
|
||||
private string GetSingOrPlur(int number, string rawString)
|
||||
{
|
||||
var versions = rawString.Split('|');
|
||||
return number == 1 ? versions[0] : versions[1];
|
||||
}
|
||||
|
||||
private enum TimeTypes
|
||||
{
|
||||
Days,
|
||||
Hours,
|
||||
Minutes,
|
||||
Seconds
|
||||
}
|
||||
}
|
||||
}
|
194
src/Core/Localization/TranslationHandler.cs
Normal file
194
src/Core/Localization/TranslationHandler.cs
Normal file
|
@ -0,0 +1,194 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using Geekbot.Core.GuildSettingsManager;
|
||||
using Geekbot.Core.Logger;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace Geekbot.Core.Localization
|
||||
{
|
||||
public class TranslationHandler : ITranslationHandler
|
||||
{
|
||||
private readonly IGeekbotLogger _logger;
|
||||
private readonly IGuildSettingsManager _guildSettingsManager;
|
||||
private readonly Dictionary<ulong, string> _serverLanguages;
|
||||
private Dictionary<string, Dictionary<string, Dictionary<string, string>>> _translations;
|
||||
|
||||
public TranslationHandler(IGeekbotLogger logger, IGuildSettingsManager guildSettingsManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_guildSettingsManager = guildSettingsManager;
|
||||
_logger.Information(LogSource.Geekbot, "Loading Translations");
|
||||
LoadTranslations();
|
||||
_serverLanguages = new Dictionary<ulong, string>();
|
||||
}
|
||||
|
||||
private void LoadTranslations()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Read the file
|
||||
var translationFile = File.ReadAllText(Path.GetFullPath("./Lib/Localization/Translations.yml"));
|
||||
|
||||
// Deserialize
|
||||
var input = new StringReader(translationFile);
|
||||
var mergingParser = new MergingParser(new Parser(input));
|
||||
var deserializer = new DeserializerBuilder().Build();
|
||||
var rawTranslations = deserializer.Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(mergingParser);
|
||||
|
||||
// Sort
|
||||
var sortedPerLanguage = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();
|
||||
foreach (var command in rawTranslations)
|
||||
{
|
||||
foreach (var str in command.Value)
|
||||
{
|
||||
foreach (var lang in str.Value)
|
||||
{
|
||||
if (!sortedPerLanguage.ContainsKey(lang.Key))
|
||||
{
|
||||
var commandDict = new Dictionary<string, Dictionary<string, string>>();
|
||||
var strDict = new Dictionary<string, string>
|
||||
{
|
||||
{str.Key, lang.Value}
|
||||
};
|
||||
commandDict.Add(command.Key, strDict);
|
||||
sortedPerLanguage.Add(lang.Key, commandDict);
|
||||
}
|
||||
if (!sortedPerLanguage[lang.Key].ContainsKey(command.Key))
|
||||
{
|
||||
var strDict = new Dictionary<string, string>
|
||||
{
|
||||
{str.Key, lang.Value}
|
||||
};
|
||||
sortedPerLanguage[lang.Key].Add(command.Key, strDict);
|
||||
}
|
||||
if (!sortedPerLanguage[lang.Key][command.Key].ContainsKey(str.Key))
|
||||
{
|
||||
sortedPerLanguage[lang.Key][command.Key].Add(str.Key, lang.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_translations = sortedPerLanguage;
|
||||
|
||||
// Find Languages
|
||||
SupportedLanguages = new List<string>();
|
||||
foreach (var lang in sortedPerLanguage)
|
||||
{
|
||||
SupportedLanguages.Add(lang.Key);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(LogSource.Geekbot, "Failed to load Translations", e);
|
||||
Environment.Exit(GeekbotExitCode.TranslationsFailed.GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
private Task<string> GetServerLanguage(ulong guildId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string lang;
|
||||
try
|
||||
{
|
||||
lang = _serverLanguages[guildId];
|
||||
if (!string.IsNullOrEmpty(lang))
|
||||
{
|
||||
return Task.FromResult(lang);
|
||||
}
|
||||
throw new Exception();
|
||||
}
|
||||
catch
|
||||
{
|
||||
lang = _guildSettingsManager.GetSettings(guildId, false)?.Language ?? "EN";
|
||||
_serverLanguages[guildId] = lang;
|
||||
return Task.FromResult(lang);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(LogSource.Geekbot, "Could not get guild language", e);
|
||||
return Task.FromResult("EN");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetString(ulong guildId, string command, string stringName)
|
||||
{
|
||||
var serverLang = await GetServerLanguage(guildId);
|
||||
return GetString(serverLang, command, stringName);
|
||||
}
|
||||
|
||||
public string GetString(string language, string command, string stringName)
|
||||
{
|
||||
var translation = _translations[language][command][stringName];
|
||||
if (!string.IsNullOrWhiteSpace(translation)) return translation;
|
||||
translation = _translations[command][stringName]["EN"];
|
||||
if (string.IsNullOrWhiteSpace(translation))
|
||||
{
|
||||
_logger.Warning(LogSource.Geekbot, $"No translation found for {command} - {stringName}");
|
||||
}
|
||||
return translation;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> GetDict(ICommandContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var command = context.Message.Content.Split(' ').First().TrimStart('!').ToLower();
|
||||
var serverLanguage = await GetServerLanguage(context.Guild?.Id ?? 0);
|
||||
return _translations[serverLanguage][command];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(LogSource.Geekbot, "No translations for command found", e);
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TranslationGuildContext> GetGuildContext(ICommandContext context)
|
||||
{
|
||||
var dict = await GetDict(context);
|
||||
var language = await GetServerLanguage(context.Guild?.Id ?? 0);
|
||||
return new TranslationGuildContext(this, language, dict);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, string>> GetDict(ICommandContext context, string command)
|
||||
{
|
||||
try
|
||||
{
|
||||
var serverLanguage = await GetServerLanguage(context.Guild?.Id ?? 0);
|
||||
return _translations[serverLanguage][command];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(LogSource.Geekbot, "No translations for command found", e);
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SetLanguage(ulong guildId, string language)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!SupportedLanguages.Contains(language)) return false;
|
||||
var guild = _guildSettingsManager.GetSettings(guildId);
|
||||
guild.Language = language;
|
||||
await _guildSettingsManager.UpdateSettings(guild);
|
||||
_serverLanguages[guildId] = language;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(LogSource.Geekbot, "Error while changing language", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> SupportedLanguages { get; private set; }
|
||||
}
|
||||
}
|
207
src/Core/Localization/Translations.yml
Normal file
207
src/Core/Localization/Translations.yml
Normal file
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
dateTime:
|
||||
Days:
|
||||
EN: "day|days"
|
||||
CHDE: "tag|täg"
|
||||
Hours:
|
||||
EN: "hour|hours"
|
||||
CHDE: "stund|stunde"
|
||||
Minutes:
|
||||
EN: "minute|minutes"
|
||||
CHDE: "minute|minute"
|
||||
Seconds:
|
||||
EN: "second|seconds"
|
||||
CHDE: "sekunde|sekunde"
|
||||
And:
|
||||
EN: "and"
|
||||
CHDE: "und"
|
||||
admin:
|
||||
NewLanguageSet:
|
||||
EN: "I will reply in english from now on"
|
||||
CHDE: "I werd ab jetzt uf schwiizerdüütsch antworte, äuuä"
|
||||
GetLanguage:
|
||||
EN: "I'm talking english"
|
||||
CHDE: "I red schwiizerdüütsch"
|
||||
errorHandler:
|
||||
SomethingWentWrong:
|
||||
EN: "Something went wrong :confused:"
|
||||
CHDE: "Öppis isch schief gange :confused:"
|
||||
httpErrors:
|
||||
403:
|
||||
EN: "Seems like i don't have enough permission to that :confused:"
|
||||
CHDE: "Gseht danach us das ich nid gnueg recht han zum das mache :confused:"
|
||||
choose:
|
||||
Choice:
|
||||
EN: "I Choose **{0}**"
|
||||
CHDE: "I nimme **{0}**"
|
||||
good:
|
||||
CannotChangeOwn:
|
||||
EN: "Sorry {0}, but you can't give yourself karma"
|
||||
CHDE: "Sorry {0}, aber du chasch dr selber kei karma geh"
|
||||
WaitUntill:
|
||||
EN: "Sorry {0}, but you have to wait {1} before you can give karma again..."
|
||||
CHDE: "Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch geh..."
|
||||
Increased:
|
||||
EN: "Karma gained"
|
||||
CHDE: "Karma becho"
|
||||
By:
|
||||
EN: "By"
|
||||
CHDE: "Vo"
|
||||
Amount:
|
||||
EN: "Amount"
|
||||
CHDE: "Mengi"
|
||||
Current:
|
||||
EN: "Current"
|
||||
CHDE: "Jetzt"
|
||||
bad:
|
||||
CannotChangeOwn:
|
||||
EN: "Sorry {0}, but you can't lower your own karma"
|
||||
CHDE: "Sorry {0}, aber du chasch dr din eigete karma nid weg neh"
|
||||
WaitUntill:
|
||||
EN: "Sorry {0}, but you have to wait {1} before you can lower karma again..."
|
||||
CHDE: "Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch senke..."
|
||||
Decreased:
|
||||
EN: "Karma lowered"
|
||||
CHDE: "Karma gsenkt"
|
||||
By:
|
||||
EN: "By"
|
||||
CHDE: "Vo"
|
||||
Amount:
|
||||
EN: "Amount"
|
||||
CHDE: "Mengi"
|
||||
Current:
|
||||
EN: "Current"
|
||||
CHDE: "Jetzt"
|
||||
roll:
|
||||
Rolled:
|
||||
EN: "{0}, you rolled {1}, your guess was {2}"
|
||||
CHDE: "{0}, du hesch {1} grollt und hesch {2} grate"
|
||||
Gratz:
|
||||
EN: "Congratulations {0}, your guess was correct!"
|
||||
CHDE: "Gratuliere {0}, du hesch richtig grate!"
|
||||
RolledNoGuess:
|
||||
EN: "{0}, you rolled {1}"
|
||||
CHDE: "{0}, du hesch {1} grollt"
|
||||
NoPrevGuess:
|
||||
EN: ":red_circle: {0}, you can't guess the same number again, guess another number or wait {1}"
|
||||
CHDE: ":red_circle: {0}, du chasch nid nomol es gliche rate, rate öppis anders oder warte {1}"
|
||||
cookies: &cookiesAlias
|
||||
GetCookies:
|
||||
EN: "You got {0} cookies, there are now {1} cookies in you cookie jar"
|
||||
CHDE: "Du häsch {0} guetzli becho, du häsch jetzt {1} guetzli ih dr büchse"
|
||||
WaitForMoreCookies:
|
||||
EN: "You already got cookies today, you can have more cookies in {0}"
|
||||
CHDE: "Du hesch scho guetzli becho hüt, du chasch meh ha in {0}"
|
||||
InYourJar:
|
||||
EN: "There are {0} cookies in you cookie jar"
|
||||
CHDE: "Es hät {0} guetzli ih dineri büchs"
|
||||
Given:
|
||||
EN: "You gave {0} cookies to {1}"
|
||||
CHDE: "Du hesch {1} {0} guetzli geh"
|
||||
NotEnoughToGive:
|
||||
EN: "You don't have enough cookies"
|
||||
CHDE: "Du hesch nid gnueg guetzli"
|
||||
NotEnoughCookiesToEat:
|
||||
EN: "Your cookie jar looks almost empty, you should probably not eat a cookie"
|
||||
CHDE: "Du hesch chuum no guetzli ih dineri büchs, du sötsch warschinli keini esse"
|
||||
AteCookies:
|
||||
EN: "You ate {0} cookies, you've only got {1} cookies left"
|
||||
CHDE: "Du hesch {0} guetzli gesse und hesch jezt no {1} übrig"
|
||||
cookie:
|
||||
# because command aliases are to hard to deal with...
|
||||
<<: *cookiesAlias
|
||||
role:
|
||||
NoRolesConfigured:
|
||||
EN: "There are no roles configured for this server"
|
||||
CHDE: "Es sind kei rolle für dä server konfiguriert"
|
||||
ListHeader:
|
||||
EN: "**Self Service Roles on {0}**"
|
||||
CHDE: "**Self Service Rollene uf {0}**"
|
||||
ListInstruction:
|
||||
EN: "To get a role, use `!role [name]`"
|
||||
CHDE: "Zum ä rolle becho, schriib `!role [name]`"
|
||||
RoleNotFound:
|
||||
EN: "That role doesn't exist or is not on the whitelist"
|
||||
CHDE: "Die rolle gids nid or isch nid uf dr whitelist"
|
||||
RemovedUserFromRole:
|
||||
EN: "Removed you from {0}"
|
||||
CHDE: "Han di entfernt vo {0}"
|
||||
AddedUserFromRole:
|
||||
EN: "Added you to {0}"
|
||||
CHDE: "Han di hinzue gfüegt zu {0}"
|
||||
CannotAddManagedRole:
|
||||
EN: "You can't add a role that is managed by discord"
|
||||
CHDE: "Du chasch kei rolle hinzuefüge wo verwalted wird vo discord"
|
||||
CannotAddDangerousRole:
|
||||
EN: "You cannot add that role to self service because it contains one or more dangerous permissions"
|
||||
CHDE: "Du chasch die rolle nid hinzuefüge will er ein oder mehreri gföhrlichi berechtigunge het"
|
||||
AddedRoleToWhitelist:
|
||||
EN: "Added {0} to the whitelist"
|
||||
CHDE: "{0} isch zur whitelist hinzuegfüegt"
|
||||
RemovedRoleFromWhitelist:
|
||||
EN: "Removed {0} from the whitelist"
|
||||
CHDE: "{0} isch vo dr whitelist glöscht"
|
||||
quote:
|
||||
NoQuotesFound:
|
||||
EN: "This server doesn't seem to have any quotes yet. You can add a quote with `!quote save @user` or `!quote save <messageId>`"
|
||||
CHDE: "Dä server het no kei quotes. Du chasch quotes hinzuefüege mit `!quote save @user` oder `!quote save <messageId>`"
|
||||
CannotSaveOwnQuotes:
|
||||
EN: "You can't save your own quotes..."
|
||||
CHDE: "Du chasch kei quotes vo dir selber speichere..."
|
||||
CannotQuoteBots:
|
||||
EN: "You can't save quotes by a bot..."
|
||||
CHDE: "Du chasch kei quotes vomne bot speichere..."
|
||||
QuoteAdded:
|
||||
EN: "**Quote Added**"
|
||||
CHDE: "**Quote hinzugfüegt**"
|
||||
Removed:
|
||||
EN: "**Removed #{0}**"
|
||||
CHDE: "**#{0} glöscht**"
|
||||
NotFoundWithId:
|
||||
EN: "I couldn't find a quote with that ID :disappointed:"
|
||||
CHDE: "Ich chan kei quote finde mit därri ID :disappointed:"
|
||||
QuoteStats:
|
||||
EN: "Quote Stats"
|
||||
CHDE: "Quote statistike"
|
||||
TotalQuotes:
|
||||
EN: "Total"
|
||||
CHDE: "Total"
|
||||
MostQuotesPerson:
|
||||
EN: "Most quoted person"
|
||||
CHDE: "Meist quoteti person"
|
||||
rank:
|
||||
InvalidType:
|
||||
EN: "Valid types are '`messages`' '`karma`', '`rolls`' and '`cookies`'"
|
||||
CHDE: "Gültigi paramenter sind '`messages`' '`karma`', '`rolls`' und '`cookies`'"
|
||||
LimitingTo20Warning:
|
||||
EN: ":warning: Limiting to 20\n"
|
||||
CHDE: ":warning: Limitiert uf 20\n"
|
||||
NoTypeFoundForServer:
|
||||
EN: "No {0} found on this server"
|
||||
CHDE: "Kei {0} gfunde für dä server"
|
||||
FailedToResolveAllUsernames:
|
||||
EN: ":warning: I couldn't find all usernames. Maybe they left the server?\n"
|
||||
CHDE: ":warning: Ich han nid alli benutzername gfunde. villiicht hend sie de server verlah?\n"
|
||||
HighscoresFor:
|
||||
EN: ":bar_chart: **{0} Highscore for {1}**"
|
||||
CHDE: ":bar_chart: **{0} Highscore für {1}**"
|
||||
ship:
|
||||
Matchmaking:
|
||||
EN: "Matchmaking"
|
||||
CHDE: "Verkupple"
|
||||
NotGonnaToHappen:
|
||||
EN: "Not gonna happen"
|
||||
CHDE: "Wird nöd klappe"
|
||||
NotSuchAGoodIdea:
|
||||
EN: "Not such a good idea"
|
||||
CHDE: "Nöd so ä gueti idee"
|
||||
ThereMightBeAChance:
|
||||
EN: "There might be a chance"
|
||||
CHDE: "Es gid eventuel ä chance"
|
||||
CouldWork:
|
||||
EN: "Almost a match"
|
||||
CHDE: "Fasch en match"
|
||||
ItsAMatch:
|
||||
EN: "It's a match"
|
||||
CHDE: "Es isch es traumpaar"
|
||||
|
54
src/Core/Logger/DiscordLogger.cs
Normal file
54
src/Core/Logger/DiscordLogger.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public class DiscordLogger : IDiscordLogger
|
||||
{
|
||||
private readonly GeekbotLogger _logger;
|
||||
|
||||
public DiscordLogger(GeekbotLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task Log(LogMessage message)
|
||||
{
|
||||
LogSource source;
|
||||
try
|
||||
{
|
||||
source = Enum.Parse<LogSource>(message.Source);
|
||||
}
|
||||
catch
|
||||
{
|
||||
source = LogSource.Discord;
|
||||
_logger.Warning(LogSource.Geekbot, $"Could not parse {message.Source} to a LogSource Enum");
|
||||
}
|
||||
|
||||
var logMessage = $"[{message.Source}] {message.Message}";
|
||||
switch (message.Severity)
|
||||
{
|
||||
case LogSeverity.Verbose:
|
||||
_logger.Trace(source, message.Message);
|
||||
break;
|
||||
case LogSeverity.Debug:
|
||||
_logger.Debug(source, message.Message);
|
||||
break;
|
||||
case LogSeverity.Info:
|
||||
_logger.Information(source, message.Message);
|
||||
break;
|
||||
case LogSeverity.Critical:
|
||||
case LogSeverity.Error:
|
||||
case LogSeverity.Warning:
|
||||
if (logMessage.Contains("VOICE_STATE_UPDATE")) break;
|
||||
_logger.Error(source, message.Message, message.Exception);
|
||||
break;
|
||||
default:
|
||||
_logger.Information(source, $"{logMessage} --- {message.Severity}");
|
||||
break;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
84
src/Core/Logger/GeekbotLogger.cs
Normal file
84
src/Core/Logger/GeekbotLogger.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public class GeekbotLogger : IGeekbotLogger
|
||||
{
|
||||
private readonly bool _logAsJson;
|
||||
private readonly NLog.Logger _logger;
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
|
||||
public GeekbotLogger(RunParameters runParameters)
|
||||
{
|
||||
_logAsJson = !string.IsNullOrEmpty(runParameters.SumologicEndpoint) || runParameters.LogJson;
|
||||
_logger = LoggerFactory.CreateNLog(runParameters);
|
||||
_serializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
||||
NullValueHandling = NullValueHandling.Include
|
||||
};
|
||||
Information(LogSource.Geekbot, "Using GeekbotLogger");
|
||||
}
|
||||
|
||||
public void Trace(LogSource source, string message, object extra = null)
|
||||
{
|
||||
_logger.Trace(CreateLogString("Trace", source, message, null, extra));
|
||||
}
|
||||
|
||||
public void Debug(LogSource source, string message, object extra = null)
|
||||
{
|
||||
if (_logAsJson) _logger.Info(CreateLogString("Debug", source, message, null, extra));
|
||||
else _logger.Debug(CreateLogString("Debug", source, message, null, extra));
|
||||
}
|
||||
|
||||
public void Information(LogSource source, string message, object extra = null)
|
||||
{
|
||||
_logger.Info(CreateLogString("Information", source, message, null, extra));
|
||||
}
|
||||
|
||||
public void Warning(LogSource source, string message, Exception stackTrace = null, object extra = null)
|
||||
{
|
||||
if (_logAsJson) _logger.Info(CreateLogString("Warning", source, message, stackTrace, extra));
|
||||
else _logger.Warn(CreateLogString("Warning", source, message, stackTrace, extra));
|
||||
}
|
||||
|
||||
public void Error(LogSource source, string message, Exception stackTrace, object extra = null)
|
||||
{
|
||||
if (_logAsJson) _logger.Info(CreateLogString("Error", source, message, stackTrace, extra));
|
||||
else _logger.Error(stackTrace, CreateLogString("Error", source, message, stackTrace, extra));
|
||||
}
|
||||
|
||||
public NLog.Logger GetNLogger()
|
||||
{
|
||||
return _logger;
|
||||
}
|
||||
|
||||
public bool LogAsJson()
|
||||
{
|
||||
return _logAsJson;
|
||||
}
|
||||
|
||||
private string CreateLogString(string type, LogSource source, string message, Exception stackTrace = null, object extra = null)
|
||||
{
|
||||
if (_logAsJson)
|
||||
{
|
||||
var logObject = new GeekbotLoggerObject
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Type = type,
|
||||
Source = source,
|
||||
Message = message,
|
||||
StackTrace = stackTrace,
|
||||
Extra = extra
|
||||
};
|
||||
return JsonConvert.SerializeObject(logObject, Formatting.None, _serializerSettings);
|
||||
}
|
||||
|
||||
if (source != LogSource.Message) return $"[{source}] - {message}";
|
||||
|
||||
var m = (MessageDto) extra;
|
||||
return $"[{source}] - [{m?.Guild.Name} - {m?.Channel.Name}] {m?.User.Name}: {m?.Message.Content}";
|
||||
}
|
||||
}
|
||||
}
|
10
src/Core/Logger/IDiscordLogger.cs
Normal file
10
src/Core/Logger/IDiscordLogger.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public interface IDiscordLogger
|
||||
{
|
||||
Task Log(LogMessage message);
|
||||
}
|
||||
}
|
15
src/Core/Logger/IGeekbotLogger.cs
Normal file
15
src/Core/Logger/IGeekbotLogger.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public interface IGeekbotLogger
|
||||
{
|
||||
void Trace(LogSource source, string message, object extra = null);
|
||||
void Debug(LogSource source, string message, object extra = null);
|
||||
void Information(LogSource source, string message, object extra = null);
|
||||
void Warning(LogSource source, string message, Exception stackTrace = null, object extra = null);
|
||||
void Error(LogSource source, string message, Exception stackTrace, object extra = null);
|
||||
NLog.Logger GetNLogger();
|
||||
bool LogAsJson();
|
||||
}
|
||||
}
|
14
src/Core/Logger/LogDto.cs
Normal file
14
src/Core/Logger/LogDto.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public class GeekbotLoggerObject
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string Type { get; set; }
|
||||
public LogSource Source { get; set; }
|
||||
public string Message { get; set; }
|
||||
public Exception StackTrace { get; set; }
|
||||
public object Extra { get; set; }
|
||||
}
|
||||
}
|
22
src/Core/Logger/LogSource.cs
Normal file
22
src/Core/Logger/LogSource.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum LogSource
|
||||
{
|
||||
Geekbot,
|
||||
Rest,
|
||||
Gateway,
|
||||
Discord,
|
||||
Database,
|
||||
Message,
|
||||
UserRepository,
|
||||
Command,
|
||||
Api,
|
||||
Migration,
|
||||
HighscoreManager,
|
||||
Other
|
||||
}
|
||||
}
|
66
src/Core/Logger/LoggerFactory.cs
Normal file
66
src/Core/Logger/LoggerFactory.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using SumoLogic.Logging.NLog;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public class LoggerFactory
|
||||
{
|
||||
public static NLog.Logger CreateNLog(RunParameters runParameters)
|
||||
{
|
||||
var config = new LoggingConfiguration();
|
||||
|
||||
if (!string.IsNullOrEmpty(runParameters.SumologicEndpoint))
|
||||
{
|
||||
Console.WriteLine("Logging Geekbot Logs to Sumologic");
|
||||
config.LoggingRules.Add(
|
||||
new LoggingRule("*", LogLevel.Debug, LogLevel.Fatal,
|
||||
new SumoLogicTarget()
|
||||
{
|
||||
Url = runParameters.SumologicEndpoint,
|
||||
SourceName = "GeekbotLogger",
|
||||
Layout = "${message}",
|
||||
UseConsoleLog = false,
|
||||
OptimizeBufferReuse = true,
|
||||
Name = "Geekbot"
|
||||
})
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var minLevel = runParameters.Verbose ? LogLevel.Trace : LogLevel.Info;
|
||||
config.LoggingRules.Add(
|
||||
new LoggingRule("*", minLevel, LogLevel.Fatal,
|
||||
new ColoredConsoleTarget
|
||||
{
|
||||
Name = "Console",
|
||||
Encoding = Encoding.UTF8,
|
||||
Layout = "[${longdate} ${level:format=FirstCharacter}] ${message} ${exception:format=toString}"
|
||||
})
|
||||
);
|
||||
|
||||
config.LoggingRules.Add(
|
||||
new LoggingRule("*", minLevel, LogLevel.Fatal,
|
||||
new FileTarget
|
||||
{
|
||||
Name = "File",
|
||||
Layout = "[${longdate} ${level}] ${message}",
|
||||
Encoding = Encoding.UTF8,
|
||||
LineEnding = LineEndingMode.Default,
|
||||
MaxArchiveFiles = 30,
|
||||
ArchiveNumbering = ArchiveNumberingMode.Date,
|
||||
ArchiveEvery = FileArchivePeriod.Day,
|
||||
ArchiveFileName = "./Logs/Archive/{#####}.log",
|
||||
FileName = "./Logs/Geekbot.log"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
var loggerConfig = new LogFactory { Configuration = config };
|
||||
return loggerConfig.GetCurrentClassLogger();
|
||||
}
|
||||
}
|
||||
}
|
26
src/Core/Logger/MessageDto.cs
Normal file
26
src/Core/Logger/MessageDto.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public class MessageDto
|
||||
{
|
||||
public MessageContent Message { get; set; }
|
||||
public IdAndName User { get; set; }
|
||||
public IdAndName Guild { get; set; }
|
||||
public IdAndName Channel { get; set; }
|
||||
|
||||
public class MessageContent
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public string Id { get; set; }
|
||||
public int Attachments { get; set; }
|
||||
public int ChannelMentions { get; set; }
|
||||
public int UserMentions { get; set; }
|
||||
public int RoleMentions { get; set; }
|
||||
}
|
||||
|
||||
public class IdAndName
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
70
src/Core/Logger/SimpleConextConverter.cs
Normal file
70
src/Core/Logger/SimpleConextConverter.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace Geekbot.Core.Logger
|
||||
{
|
||||
public class SimpleConextConverter
|
||||
{
|
||||
public static MessageDto ConvertContext(ICommandContext context)
|
||||
{
|
||||
return new MessageDto
|
||||
{
|
||||
Message = new MessageDto.MessageContent
|
||||
{
|
||||
Content = context.Message.Content, // Only when an error occurs, including for diagnostic reason
|
||||
Id = context.Message.Id.ToString(),
|
||||
Attachments = context.Message.Attachments.Count,
|
||||
ChannelMentions = context.Message.MentionedChannelIds.Count,
|
||||
UserMentions = context.Message.MentionedUserIds.Count,
|
||||
RoleMentions = context.Message.MentionedRoleIds.Count
|
||||
},
|
||||
User = new MessageDto.IdAndName
|
||||
{
|
||||
Id = context.User.Id.ToString(),
|
||||
Name = $"{context.User.Username}#{context.User.Discriminator}"
|
||||
},
|
||||
Guild = new MessageDto.IdAndName
|
||||
{
|
||||
Id = context.Guild?.Id.ToString(),
|
||||
Name = context.Guild?.Name
|
||||
},
|
||||
Channel = new MessageDto.IdAndName
|
||||
{
|
||||
Id = context.Channel?.Id.ToString() ?? context.User.Id.ToString(),
|
||||
Name = context.Channel?.Name ?? "DM-Channel"
|
||||
}
|
||||
};
|
||||
}
|
||||
public static MessageDto ConvertSocketMessage(SocketMessage message, bool isPrivate = false)
|
||||
{
|
||||
var channel = isPrivate ? null : (SocketGuildChannel) message.Channel;
|
||||
return new MessageDto
|
||||
{
|
||||
Message = new MessageDto.MessageContent
|
||||
{
|
||||
Id = message.Id.ToString(),
|
||||
Attachments = message.Attachments.Count,
|
||||
ChannelMentions = message.MentionedChannels.Count,
|
||||
UserMentions = message.MentionedUsers.Count,
|
||||
RoleMentions = message.MentionedRoles.Count
|
||||
},
|
||||
User = new MessageDto.IdAndName
|
||||
{
|
||||
Id = message.Author.Id.ToString(),
|
||||
Name = $"{message.Author.Username}#{message.Author.Discriminator}"
|
||||
},
|
||||
Guild = new MessageDto.IdAndName
|
||||
{
|
||||
Id = channel?.Guild?.Id.ToString(),
|
||||
Name = channel?.Guild?.Name
|
||||
},
|
||||
Channel = new MessageDto.IdAndName
|
||||
{
|
||||
Id = channel?.Id.ToString() ?? message.Author.Id.ToString(),
|
||||
Name = channel?.Name ?? "DM-Channel"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
12
src/Core/MalClient/IMalClient.cs
Normal file
12
src/Core/MalClient/IMalClient.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Threading.Tasks;
|
||||
using MyAnimeListSharp.Core;
|
||||
|
||||
namespace Geekbot.Core.MalClient
|
||||
{
|
||||
public interface IMalClient
|
||||
{
|
||||
bool IsLoggedIn();
|
||||
Task<AnimeEntry> GetAnime(string query);
|
||||
Task<MangaEntry> GetManga(string query);
|
||||
}
|
||||
}
|
63
src/Core/MalClient/MalClient.cs
Normal file
63
src/Core/MalClient/MalClient.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.GlobalSettings;
|
||||
using Geekbot.Core.Logger;
|
||||
using MyAnimeListSharp.Auth;
|
||||
using MyAnimeListSharp.Core;
|
||||
using MyAnimeListSharp.Facade.Async;
|
||||
|
||||
namespace Geekbot.Core.MalClient
|
||||
{
|
||||
public class MalClient : IMalClient
|
||||
{
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IGeekbotLogger _logger;
|
||||
private ICredentialContext _credentials;
|
||||
private AnimeSearchMethodsAsync _animeSearch;
|
||||
private MangaSearchMethodsAsync _mangaSearch;
|
||||
|
||||
public MalClient(IGlobalSettings globalSettings, IGeekbotLogger logger)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
ReloadClient();
|
||||
}
|
||||
|
||||
private bool ReloadClient()
|
||||
{
|
||||
var malCredentials = _globalSettings.GetKey("MalCredentials");
|
||||
if (!string.IsNullOrEmpty(malCredentials))
|
||||
{
|
||||
var credSplit = malCredentials.Split('|');
|
||||
_credentials = new CredentialContext()
|
||||
{
|
||||
UserName = credSplit[0],
|
||||
Password = credSplit[1]
|
||||
};
|
||||
_animeSearch = new AnimeSearchMethodsAsync(_credentials);
|
||||
_mangaSearch = new MangaSearchMethodsAsync(_credentials);
|
||||
_logger.Debug(LogSource.Geekbot, "Logged in to MAL");
|
||||
return true;
|
||||
}
|
||||
_logger.Debug(LogSource.Geekbot, "No MAL Credentials Set!");
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public bool IsLoggedIn()
|
||||
{
|
||||
return _credentials != null;
|
||||
}
|
||||
|
||||
public async Task<AnimeEntry> GetAnime(string query)
|
||||
{
|
||||
var response = await _animeSearch.SearchDeserializedAsync(query);
|
||||
return response.Entries.Count == 0 ? null : response.Entries[0];
|
||||
}
|
||||
|
||||
public async Task<MangaEntry> GetManga(string query)
|
||||
{
|
||||
var response = await _mangaSearch.SearchDeserializedAsync(query);
|
||||
return response.Entries.Count == 0 ? null : response.Entries[0];
|
||||
}
|
||||
}
|
||||
}
|
33
src/Core/Media/FortunesProvider.cs
Normal file
33
src/Core/Media/FortunesProvider.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Geekbot.Core.Logger;
|
||||
|
||||
namespace Geekbot.Core.Media
|
||||
{
|
||||
public class FortunesProvider : IFortunesProvider
|
||||
{
|
||||
private readonly string[] _fortuneArray;
|
||||
private readonly int _totalFortunes;
|
||||
|
||||
public FortunesProvider(IGeekbotLogger logger)
|
||||
{
|
||||
var path = Path.GetFullPath("./Storage/fortunes");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var rawFortunes = File.ReadAllText(path);
|
||||
_fortuneArray = rawFortunes.Split("%");
|
||||
_totalFortunes = _fortuneArray.Length;
|
||||
logger.Trace(LogSource.Geekbot, $"Loaded {_totalFortunes} Fortunes");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Information(LogSource.Geekbot, $"Fortunes File not found at {path}");
|
||||
}
|
||||
}
|
||||
|
||||
public string GetRandomFortune()
|
||||
{
|
||||
return _fortuneArray[new Random().Next(0, _totalFortunes)];
|
||||
}
|
||||
}
|
||||
}
|
7
src/Core/Media/IFortunesProvider.cs
Normal file
7
src/Core/Media/IFortunesProvider.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Core.Media
|
||||
{
|
||||
public interface IFortunesProvider
|
||||
{
|
||||
string GetRandomFortune();
|
||||
}
|
||||
}
|
7
src/Core/Media/IMediaProvider.cs
Normal file
7
src/Core/Media/IMediaProvider.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Core.Media
|
||||
{
|
||||
public interface IMediaProvider
|
||||
{
|
||||
string GetMedia(MediaType type);
|
||||
}
|
||||
}
|
61
src/Core/Media/MediaProvider.cs
Normal file
61
src/Core/Media/MediaProvider.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System.IO;
|
||||
using Geekbot.Core.Logger;
|
||||
using Geekbot.Core.RandomNumberGenerator;
|
||||
|
||||
namespace Geekbot.Core.Media
|
||||
{
|
||||
public class MediaProvider : IMediaProvider
|
||||
{
|
||||
private readonly IRandomNumberGenerator _random;
|
||||
private readonly IGeekbotLogger _logger;
|
||||
private readonly string[] _pandaImages;
|
||||
private readonly string[] _croissantImages;
|
||||
private readonly string[] _squirrelImages;
|
||||
private readonly string[] _pumpkinImages;
|
||||
private readonly string[] _turtlesImages;
|
||||
private readonly string[] _penguinImages;
|
||||
private readonly string[] _foxImages;
|
||||
private readonly string[] _dabImages;
|
||||
|
||||
public MediaProvider(IGeekbotLogger logger, IRandomNumberGenerator random)
|
||||
{
|
||||
_random = random;
|
||||
_logger = logger;
|
||||
|
||||
logger.Information(LogSource.Geekbot, "Loading Media Files");
|
||||
LoadMedia("./Storage/pandas", ref _pandaImages);
|
||||
LoadMedia("./Storage/croissant", ref _croissantImages);
|
||||
LoadMedia("./Storage/squirrel", ref _squirrelImages);
|
||||
LoadMedia("./Storage/pumpkin", ref _pumpkinImages);
|
||||
LoadMedia("./Storage/turtles", ref _turtlesImages);
|
||||
LoadMedia("./Storage/penguins", ref _penguinImages);
|
||||
LoadMedia("./Storage/foxes", ref _foxImages);
|
||||
LoadMedia("./Storage/dab", ref _dabImages);
|
||||
}
|
||||
|
||||
private void LoadMedia(string path, ref string[] storage)
|
||||
{
|
||||
var rawLinks = File.ReadAllText(Path.GetFullPath(path));
|
||||
storage = rawLinks.Split("\n");
|
||||
_logger.Trace(LogSource.Geekbot, $"Loaded {storage.Length} Images from ${path}");
|
||||
}
|
||||
|
||||
public string GetMedia(MediaType type)
|
||||
{
|
||||
var collection = type switch
|
||||
{
|
||||
MediaType.Panda => _pandaImages,
|
||||
MediaType.Croissant => _croissantImages,
|
||||
MediaType.Squirrel => _squirrelImages,
|
||||
MediaType.Pumpkin => _pumpkinImages,
|
||||
MediaType.Turtle => _turtlesImages,
|
||||
MediaType.Penguin => _penguinImages,
|
||||
MediaType.Fox => _foxImages,
|
||||
MediaType.Dab => _dabImages,
|
||||
_ => new string[0]
|
||||
};
|
||||
|
||||
return collection[_random.Next(0, collection.Length)];
|
||||
}
|
||||
}
|
||||
}
|
14
src/Core/Media/MediaType.cs
Normal file
14
src/Core/Media/MediaType.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace Geekbot.Core.Media
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
Panda,
|
||||
Croissant,
|
||||
Squirrel,
|
||||
Pumpkin,
|
||||
Turtle,
|
||||
Penguin,
|
||||
Fox,
|
||||
Dab
|
||||
}
|
||||
}
|
38
src/Core/Polyfills/UserPolyfillDto.cs
Normal file
38
src/Core/Polyfills/UserPolyfillDto.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace Geekbot.Core.Polyfills
|
||||
{
|
||||
public class UserPolyfillDto : IUser
|
||||
{
|
||||
public ulong Id { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public string Mention { get; set; }
|
||||
public IActivity Activity { get; }
|
||||
public UserStatus Status { get; set; }
|
||||
public IImmutableSet<ClientType> ActiveClients { get; }
|
||||
public string AvatarId { get; set; }
|
||||
public string Discriminator { get; set; }
|
||||
public ushort DiscriminatorValue { get; set; }
|
||||
public bool IsBot { get; set; }
|
||||
public bool IsWebhook { get; set; }
|
||||
public string Username { get; set; }
|
||||
|
||||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
|
||||
{
|
||||
return "https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png";
|
||||
}
|
||||
|
||||
public string GetDefaultAvatarUrl()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
7
src/Core/RandomNumberGenerator/IRandomNumberGenerator.cs
Normal file
7
src/Core/RandomNumberGenerator/IRandomNumberGenerator.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Geekbot.Core.RandomNumberGenerator
|
||||
{
|
||||
public interface IRandomNumberGenerator
|
||||
{
|
||||
int Next(int minValue, int maxExclusiveValue);
|
||||
}
|
||||
}
|
46
src/Core/RandomNumberGenerator/RandomNumberGenerator.cs
Normal file
46
src/Core/RandomNumberGenerator/RandomNumberGenerator.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Geekbot.Core.RandomNumberGenerator
|
||||
{
|
||||
public class RandomNumberGenerator : IRandomNumberGenerator
|
||||
{
|
||||
readonly RNGCryptoServiceProvider csp;
|
||||
|
||||
public RandomNumberGenerator()
|
||||
{
|
||||
csp = new RNGCryptoServiceProvider();
|
||||
}
|
||||
|
||||
public int Next(int minValue, int maxExclusiveValue)
|
||||
{
|
||||
if (minValue >= maxExclusiveValue)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("minValue must be lower than maxExclusiveValue");
|
||||
}
|
||||
|
||||
var diff = (long)maxExclusiveValue - minValue;
|
||||
var upperBound = uint.MaxValue / diff * diff;
|
||||
|
||||
uint ui;
|
||||
do
|
||||
{
|
||||
ui = GetRandomUInt();
|
||||
} while (ui >= upperBound);
|
||||
return (int)(minValue + (ui % diff));
|
||||
}
|
||||
|
||||
private uint GetRandomUInt()
|
||||
{
|
||||
var randomBytes = GenerateRandomBytes(sizeof(uint));
|
||||
return BitConverter.ToUInt32(randomBytes, 0);
|
||||
}
|
||||
|
||||
private byte[] GenerateRandomBytes(int bytesNumber)
|
||||
{
|
||||
var buffer = new byte[bytesNumber];
|
||||
csp.GetBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
15
src/Core/ReactionListener/IReactionListener.cs
Normal file
15
src/Core/ReactionListener/IReactionListener.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace Geekbot.Core.ReactionListener
|
||||
{
|
||||
public interface IReactionListener
|
||||
{
|
||||
bool IsListener(ulong id);
|
||||
Task AddRoleToListener(ulong messageId, ulong guildId, string emoji, IRole role);
|
||||
void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction);
|
||||
void GiveRole(ISocketMessageChannel message, SocketReaction reaction);
|
||||
IEmote ConvertStringToEmote(string emoji);
|
||||
}
|
||||
}
|
88
src/Core/ReactionListener/ReactionListener.cs
Normal file
88
src/Core/ReactionListener/ReactionListener.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.Database.Models;
|
||||
using Geekbot.Core.Extensions;
|
||||
|
||||
namespace Geekbot.Core.ReactionListener
|
||||
{
|
||||
public class ReactionListener : IReactionListener
|
||||
{
|
||||
private readonly DatabaseContext _database;
|
||||
// <messageId, <reaction, roleId>
|
||||
private Dictionary<ulong, Dictionary<IEmote, ulong>> _listener;
|
||||
|
||||
public ReactionListener(DatabaseContext database)
|
||||
{
|
||||
_database = database;
|
||||
LoadListeners();
|
||||
}
|
||||
|
||||
private void LoadListeners()
|
||||
{
|
||||
_listener = new Dictionary<ulong, Dictionary<IEmote, ulong>>();
|
||||
foreach (var row in _database.ReactionListeners)
|
||||
{
|
||||
var messageId = row.MessageId.AsUlong();
|
||||
if (!_listener.ContainsKey(messageId))
|
||||
{
|
||||
_listener.Add(messageId, new Dictionary<IEmote, ulong>());
|
||||
}
|
||||
|
||||
_listener[messageId].Add(ConvertStringToEmote(row.Reaction), row.RoleId.AsUlong());
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsListener(ulong id)
|
||||
{
|
||||
return _listener.ContainsKey(id);
|
||||
}
|
||||
|
||||
public async Task AddRoleToListener(ulong messageId, ulong guildId, string emoji, IRole role)
|
||||
{
|
||||
var emote = ConvertStringToEmote(emoji);
|
||||
|
||||
await _database.ReactionListeners.AddAsync(new ReactionListenerModel()
|
||||
{
|
||||
GuildId = guildId.AsLong(),
|
||||
MessageId = messageId.AsLong(),
|
||||
RoleId = role.Id.AsLong(),
|
||||
Reaction = emoji
|
||||
});
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
if (!_listener.ContainsKey(messageId))
|
||||
{
|
||||
_listener.Add(messageId, new Dictionary<IEmote, ulong>());
|
||||
}
|
||||
_listener[messageId].Add(emote, role.Id);
|
||||
}
|
||||
|
||||
public async void RemoveRole(ISocketMessageChannel channel, SocketReaction reaction)
|
||||
{
|
||||
var roleId = _listener[reaction.MessageId][reaction.Emote];
|
||||
var guild = (SocketGuildChannel) channel;
|
||||
var role = guild.Guild.GetRole(roleId);
|
||||
await ((IGuildUser) reaction.User.Value).RemoveRoleAsync(role);
|
||||
}
|
||||
|
||||
public async void GiveRole(ISocketMessageChannel channel, SocketReaction reaction)
|
||||
{
|
||||
var roleId = _listener[reaction.MessageId][reaction.Emote];
|
||||
var guild = (SocketGuildChannel) channel;
|
||||
var role = guild.Guild.GetRole(roleId);
|
||||
await ((IGuildUser) reaction.User.Value).AddRoleAsync(role);
|
||||
}
|
||||
|
||||
public IEmote ConvertStringToEmote(string emoji)
|
||||
{
|
||||
if (!emoji.StartsWith('<'))
|
||||
{
|
||||
return new Emoji(emoji);
|
||||
}
|
||||
return Emote.Parse(emoji);
|
||||
}
|
||||
}
|
||||
}
|
116
src/Core/RunParameters.cs
Normal file
116
src/Core/RunParameters.cs
Normal file
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using CommandLine;
|
||||
|
||||
namespace Geekbot.Core
|
||||
{
|
||||
public class RunParameters
|
||||
{
|
||||
/************************************
|
||||
* General *
|
||||
************************************/
|
||||
|
||||
[Option("token", HelpText = "Set a new bot token. By default it will use your previous bot token which was stored in the database (default: null) (env: TOKEN)")]
|
||||
public string Token { get; set; } = ParamFallback("TOKEN");
|
||||
|
||||
[Option('V', "verbose", HelpText = "Logs everything. (default: false) (env: LOG_VERBOSE)")]
|
||||
public bool Verbose { get; set; } = ParamFallback("LOG_VERBOSE", false);
|
||||
|
||||
[Option('j', "log-json", HelpText = "Logger outputs json (default: false ) (env: LOG_JSON)")]
|
||||
public bool LogJson { get; set; } = ParamFallback("LOG_JSON", false);
|
||||
|
||||
[Option('e', "expose-errors", HelpText = "Shows internal errors in the chat (default: false) (env: EXPOSE_ERRORS)")]
|
||||
public bool ExposeErrors { get; set; } = ParamFallback("EXPOSE_ERRORS", false);
|
||||
|
||||
/************************************
|
||||
* Database *
|
||||
************************************/
|
||||
|
||||
[Option("in-memory", HelpText = "Uses the in-memory database instead of postgresql (default: false) (env: DB_INMEMORY)")]
|
||||
public bool InMemory { get; set; } = ParamFallback("DB_INMEMORY", false);
|
||||
|
||||
// Postresql connection
|
||||
[Option("database", HelpText = "Select a postgresql database (default: geekbot) (env: DB_DATABASE)")]
|
||||
public string DbDatabase { get; set; } = ParamFallback("DB_DATABASE", "geekbot");
|
||||
|
||||
[Option("db-host", HelpText = "Set a postgresql host (default: localhost) (env: DB_HOST)")]
|
||||
public string DbHost { get; set; } = ParamFallback("DB_HOST", "localhost");
|
||||
|
||||
[Option("db-port", HelpText = "Set a postgresql host (default: 5432) (env: DB_PORT)")]
|
||||
public string DbPort { get; set; } = ParamFallback("DB_PORT", "5432");
|
||||
|
||||
[Option("db-user", HelpText = "Set a postgresql user (default: geekbot) (env: DB_USER)")]
|
||||
public string DbUser { get; set; } = ParamFallback("DB_USER", "geekbot");
|
||||
|
||||
[Option("db-password", HelpText = "Set a posgresql password (default: empty) (env: DB_PASSWORD)")]
|
||||
public string DbPassword { get; set; } = ParamFallback("DB_PASSWORD", "");
|
||||
|
||||
[Option("db-require-ssl", HelpText = "Require SSL to connect to the database (default: false) (env: DB_REQUIRE_SSL)")]
|
||||
public bool DbSsl { get; set; } = ParamFallback("DB_REQUIRE_SSL", false);
|
||||
|
||||
[Option("db-trust-cert", HelpText = "Trust the database certificate, regardless if it is valid (default: false) (env: DB_TRUST_CERT)")]
|
||||
public bool DbTrustCert { get; set; } = ParamFallback("DB_TRUST_CERT", false);
|
||||
|
||||
[Option("db-redshift-compat", HelpText = "Enable compatibility for AWS Redshift and DigitalOcean Managed Database (default: false) (env: DB_REDSHIFT_COMPAT)")]
|
||||
public bool DbRedshiftCompatibility { get; set; } = ParamFallback("DB_REDSHIFT_COMPAT", false);
|
||||
|
||||
// Logging
|
||||
[Option("db-logging", HelpText = "Enable database logging (default: false) (env: DB_LOGGING)")]
|
||||
public bool DbLogging { get; set; } = ParamFallback("DB_LOGGING", false);
|
||||
|
||||
/************************************
|
||||
* WebApi *
|
||||
************************************/
|
||||
|
||||
[Option('a', "disable-api", HelpText = "Disables the WebApi (default: false) (env: API_DISABLE)")]
|
||||
public bool DisableApi { get; set; } = ParamFallback("API_DISABLE", false);
|
||||
|
||||
[Option("api-host", HelpText = "Host on which the WebApi listens (default: localhost) (env: API_HOST)")]
|
||||
public string ApiHost { get; set; } = ParamFallback("API_HOST", "localhost");
|
||||
|
||||
[Option("api-port", HelpText = "Port on which the WebApi listens (default: 12995) (env: API_PORT)")]
|
||||
public string ApiPort { get; set; } = ParamFallback("API_PORT", "12995");
|
||||
|
||||
/************************************
|
||||
* Intergrations *
|
||||
************************************/
|
||||
|
||||
[Option("sumologic", HelpText = "Sumologic endpoint for logging (default: null) (env: SUMOLOGIC)")]
|
||||
public string SumologicEndpoint { get; set; } = ParamFallback("SUMOLOGIC");
|
||||
|
||||
[Option("sentry", HelpText = "Sentry endpoint for error reporting (default: null) (env: SENTRY)")]
|
||||
public string SentryEndpoint { get; set; } = ParamFallback("SENTRY");
|
||||
|
||||
/************************************
|
||||
* Helper Functions *
|
||||
************************************/
|
||||
|
||||
private static string ParamFallback(string key, string defaultValue = null)
|
||||
{
|
||||
var envVar = GetEnvironmentVariable(key);
|
||||
return !string.IsNullOrEmpty(envVar) ? envVar : defaultValue;
|
||||
}
|
||||
|
||||
private static bool ParamFallback(string key, bool defaultValue)
|
||||
{
|
||||
var envVar = GetEnvironmentVariable(key);
|
||||
if (!string.IsNullOrEmpty(envVar))
|
||||
{
|
||||
return envVar.ToLower() switch
|
||||
{
|
||||
"true" => true,
|
||||
"1" => true,
|
||||
"false" => false,
|
||||
"0" => false,
|
||||
_ => defaultValue
|
||||
};
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static string GetEnvironmentVariable(string name)
|
||||
{
|
||||
return Environment.GetEnvironmentVariable($"GEEKBOT_{name}");
|
||||
}
|
||||
}
|
||||
}
|
12
src/Core/UserRepository/IUserRepository.cs
Normal file
12
src/Core/UserRepository/IUserRepository.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.Core.Database.Models;
|
||||
|
||||
namespace Geekbot.Core.UserRepository
|
||||
{
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<bool> Update(SocketUser user);
|
||||
UserModel Get(ulong userId);
|
||||
}
|
||||
}
|
75
src/Core/UserRepository/UserRepository.cs
Normal file
75
src/Core/UserRepository/UserRepository.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using Geekbot.Core.Database;
|
||||
using Geekbot.Core.Database.Models;
|
||||
using Geekbot.Core.Extensions;
|
||||
using Geekbot.Core.Logger;
|
||||
|
||||
namespace Geekbot.Core.UserRepository
|
||||
{
|
||||
public class UserRepository : IUserRepository
|
||||
{
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly IGeekbotLogger _logger;
|
||||
public UserRepository(DatabaseContext database, IGeekbotLogger logger)
|
||||
{
|
||||
_database = database;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> Update(SocketUser user)
|
||||
{
|
||||
try
|
||||
{
|
||||
var savedUser = Get(user.Id);
|
||||
var isNew = false;
|
||||
if (savedUser == null)
|
||||
{
|
||||
savedUser = new UserModel();
|
||||
isNew = true;
|
||||
}
|
||||
savedUser.UserId = user.Id.AsLong();
|
||||
savedUser.Username = user.Username;
|
||||
savedUser.Discriminator = user.Discriminator;
|
||||
savedUser.AvatarUrl = user.GetAvatarUrl() ?? "";
|
||||
savedUser.IsBot = user.IsBot;
|
||||
savedUser.Joined = user.CreatedAt;
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
await _database.Users.AddAsync(savedUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
_database.Users.Update(savedUser);
|
||||
}
|
||||
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
_logger.Information(LogSource.UserRepository, "Updated User", savedUser);
|
||||
await Task.Delay(500);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(LogSource.UserRepository, $"Failed to update user: {user.Username}#{user.Discriminator} ({user.Id})", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public UserModel Get(ulong userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _database.Users.FirstOrDefault(u => u.UserId.Equals(userId.AsLong()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(LogSource.UserRepository, $"Failed to get {userId} from repository", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
src/Core/WikipediaClient/IWikipediaClient.cs
Normal file
10
src/Core/WikipediaClient/IWikipediaClient.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.WikipediaClient.Page;
|
||||
|
||||
namespace Geekbot.Core.WikipediaClient
|
||||
{
|
||||
public interface IWikipediaClient
|
||||
{
|
||||
Task<PagePreview> GetPreview(string pageName, string language = "en");
|
||||
}
|
||||
}
|
14
src/Core/WikipediaClient/Page/PageApiUrls.cs
Normal file
14
src/Core/WikipediaClient/Page/PageApiUrls.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PageApiUrls
|
||||
{
|
||||
public Uri Summary { get; set; }
|
||||
public Uri Metadata { get; set; }
|
||||
public Uri References { get; set; }
|
||||
public Uri Media { get; set; }
|
||||
public Uri EditHtml { get; set; }
|
||||
public Uri TalkPageHtml { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PageContentUrlCollection
|
||||
{
|
||||
public PageContentUrls Desktop { get; set; }
|
||||
public PageContentUrls Mobile { get; set; }
|
||||
}
|
||||
}
|
12
src/Core/WikipediaClient/Page/PageContentUrls.cs
Normal file
12
src/Core/WikipediaClient/Page/PageContentUrls.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PageContentUrls
|
||||
{
|
||||
public Uri Page { get; set; }
|
||||
public Uri Revisions { get; set; }
|
||||
public Uri Edit { get; set; }
|
||||
public Uri Talk { get; set; }
|
||||
}
|
||||
}
|
8
src/Core/WikipediaClient/Page/PageCoordinates.cs
Normal file
8
src/Core/WikipediaClient/Page/PageCoordinates.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PageCoordinates
|
||||
{
|
||||
public float Lat { get; set; }
|
||||
public float Lon { get; set; }
|
||||
}
|
||||
}
|
12
src/Core/WikipediaClient/Page/PageImage.cs
Normal file
12
src/Core/WikipediaClient/Page/PageImage.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PageImage
|
||||
{
|
||||
public Uri Source { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
|
||||
}
|
||||
}
|
8
src/Core/WikipediaClient/Page/PageNamespace.cs
Normal file
8
src/Core/WikipediaClient/Page/PageNamespace.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PageNamespace
|
||||
{
|
||||
public ulong Id { get; set; }
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
67
src/Core/WikipediaClient/Page/PagePreview.cs
Normal file
67
src/Core/WikipediaClient/Page/PagePreview.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PagePreview
|
||||
{
|
||||
[JsonProperty("type")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PageTypes Type { get; set; }
|
||||
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("displaytitle")]
|
||||
public string Displaytitle { get; set; }
|
||||
|
||||
[JsonProperty("namespace")]
|
||||
public PageNamespace Namespace { get; set; }
|
||||
|
||||
[JsonProperty("titles")]
|
||||
public PageTitles Titles { get; set; }
|
||||
|
||||
[JsonProperty("pageid")]
|
||||
public ulong Pageid { get; set; }
|
||||
|
||||
[JsonProperty("thumbnail")]
|
||||
public PageImage Thumbnail { get; set; }
|
||||
|
||||
[JsonProperty("originalimage")]
|
||||
public PageImage Originalimage { get; set; }
|
||||
|
||||
[JsonProperty("lang")]
|
||||
public string Lang { get; set; }
|
||||
|
||||
[JsonProperty("dir")]
|
||||
public string Dir { get; set; }
|
||||
|
||||
[JsonProperty("revision")]
|
||||
public ulong Revision { get; set; }
|
||||
|
||||
[JsonProperty("tid")]
|
||||
public string Tid { get; set; }
|
||||
|
||||
[JsonProperty("timestamp")]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("coordinates")]
|
||||
public PageCoordinates Coordinates { get; set; }
|
||||
|
||||
[JsonProperty("content_urls")]
|
||||
public PageContentUrlCollection ContentUrls { get; set; }
|
||||
|
||||
[JsonProperty("api_urls")]
|
||||
public PageApiUrls ApiUrls { get; set; }
|
||||
|
||||
[JsonProperty("extract")]
|
||||
public string Extract { get; set; }
|
||||
|
||||
[JsonProperty("extract_html")]
|
||||
public string ExtractHtml { get; set; }
|
||||
}
|
||||
}
|
10
src/Core/WikipediaClient/Page/PageTitles.cs
Normal file
10
src/Core/WikipediaClient/Page/PageTitles.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public class PageTitles
|
||||
{
|
||||
public string Canonical { get; set; }
|
||||
public string Normalized { get; set; }
|
||||
public string Display { get; set; }
|
||||
|
||||
}
|
||||
}
|
19
src/Core/WikipediaClient/Page/PageTypes.cs
Normal file
19
src/Core/WikipediaClient/Page/PageTypes.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Geekbot.Core.WikipediaClient.Page
|
||||
{
|
||||
public enum PageTypes
|
||||
{
|
||||
[EnumMember(Value = "standard")]
|
||||
Standard,
|
||||
|
||||
[EnumMember(Value = "disambiguation")]
|
||||
Disambiguation,
|
||||
|
||||
[EnumMember(Value = "mainpage")]
|
||||
MainPage,
|
||||
|
||||
[EnumMember(Value = "no-extract")]
|
||||
NoExtract
|
||||
}
|
||||
}
|
25
src/Core/WikipediaClient/WikipediaClient.cs
Normal file
25
src/Core/WikipediaClient/WikipediaClient.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Geekbot.Core.WikipediaClient.Page;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Geekbot.Core.WikipediaClient
|
||||
{
|
||||
public class WikipediaClient : IWikipediaClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public WikipediaClient()
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PagePreview> GetPreview(string pageName, string language = "en")
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"https://{language}.wikipedia.org/api/rest_v1/page/summary/{pageName}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<PagePreview>(stringResponse);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue