Added Ellie core project
Signed-off-by: Emotion <emotion@emotionchild.com>
This commit is contained in:
parent
a34c221952
commit
d7dd6a4817
14 changed files with 1316 additions and 6 deletions
359
src/Ellie/.editorconfig
Normal file
359
src/Ellie/.editorconfig
Normal file
|
@ -0,0 +1,359 @@
|
|||
root = true
|
||||
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||
|
||||
[obj/**]
|
||||
generated_code = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
|
||||
|
||||
#### Core EditorConfig Options ####
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# Organize usings
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false
|
||||
dotnet_style_qualification_for_field = false
|
||||
dotnet_style_qualification_for_method = false
|
||||
dotnet_style_qualification_for_property = false
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = always:error
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_coalesce_expression = true
|
||||
dotnet_style_collection_initializer = true
|
||||
dotnet_style_explicit_tuple_names = true
|
||||
dotnet_style_namespace_match_folder = true
|
||||
dotnet_style_null_propagation = true
|
||||
dotnet_style_object_initializer = true
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_auto_properties = true:warning
|
||||
dotnet_style_prefer_compound_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_return = false:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_style_prefer_inferred_tuple_names = true
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_style_prefer_simplified_interpolation = true
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all:warning
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = true
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true:suggestion
|
||||
csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
|
||||
csharp_style_expression_bodied_indexers = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||
csharp_style_expression_bodied_local_functions = true:suggestion
|
||||
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
|
||||
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:error
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:error
|
||||
csharp_style_prefer_not_pattern = true:error
|
||||
csharp_style_prefer_pattern_matching = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true:error
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = when_multiline:warning
|
||||
csharp_prefer_simple_using_statement = true
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true
|
||||
csharp_style_deconstructed_variable_declaration = true
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:error
|
||||
csharp_style_inlined_variable_declaration = true:warning
|
||||
csharp_style_pattern_local_over_anonymous_function = true
|
||||
csharp_style_prefer_index_operator = true
|
||||
csharp_style_prefer_range_operator = true
|
||||
csharp_style_throw_expression = true:error
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace:error
|
||||
|
||||
# Enforce file-scoped namespaces
|
||||
csharp_style_namespace_declarations = file_scoped:error
|
||||
|
||||
# New line preferences
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = false
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = false
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.private_readonly_field.symbols = private_readonly_field
|
||||
dotnet_naming_rule.private_readonly_field.style = begins_with_underscore
|
||||
dotnet_naming_rule.private_readonly_field.severity = warning
|
||||
|
||||
dotnet_naming_rule.private_field.symbols = private_field
|
||||
dotnet_naming_rule.private_field.style = camel_case
|
||||
dotnet_naming_rule.private_field.severity = warning
|
||||
|
||||
dotnet_naming_rule.const_fields.symbols = const_fields
|
||||
dotnet_naming_rule.const_fields.style = all_upper
|
||||
dotnet_naming_rule.const_fields.severity = warning
|
||||
|
||||
# dotnet_naming_rule.class_should_be_pascal_case.severity = error
|
||||
# dotnet_naming_rule.class_should_be_pascal_case.symbols = class
|
||||
# dotnet_naming_rule.class_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.struct_should_be_pascal_case.severity = error
|
||||
dotnet_naming_rule.struct_should_be_pascal_case.symbols = struct
|
||||
dotnet_naming_rule.struct_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = error
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
# dotnet_naming_rule.types_should_be_pascal_case.severity = error
|
||||
# dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
# dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# dotnet_naming_rule.enum_should_be_pascal_case.severity = error
|
||||
# dotnet_naming_rule.enum_should_be_pascal_case.symbols = enum
|
||||
# dotnet_naming_rule.enum_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# dotnet_naming_rule.property_should_be_pascal_case.severity = error
|
||||
# dotnet_naming_rule.property_should_be_pascal_case.symbols = property
|
||||
# dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.method_should_be_pascal_case.severity = error
|
||||
dotnet_naming_rule.method_should_be_pascal_case.symbols = method
|
||||
dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.async_method_should_be_ends_with_async.severity = error
|
||||
dotnet_naming_rule.async_method_should_be_ends_with_async.symbols = async_method
|
||||
dotnet_naming_rule.async_method_should_be_ends_with_async.style = ends_with_async
|
||||
|
||||
# dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error
|
||||
# dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
# dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.local_variable_should_be_camel_case.severity = error
|
||||
dotnet_naming_rule.local_variable_should_be_camel_case.symbols = local_variable
|
||||
dotnet_naming_rule.local_variable_should_be_camel_case.style = camel_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.const_fields.required_modifiers = const
|
||||
dotnet_naming_symbols.const_fields.applicable_kinds = field
|
||||
|
||||
dotnet_naming_symbols.class.applicable_kinds = class
|
||||
dotnet_naming_symbols.class.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.class.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.struct.applicable_kinds = struct
|
||||
dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.struct.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.enum.applicable_kinds = enum
|
||||
dotnet_naming_symbols.enum.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.enum.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.method.applicable_kinds = method
|
||||
dotnet_naming_symbols.method.applicable_accessibilities = public
|
||||
dotnet_naming_symbols.method.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.property.applicable_kinds = property
|
||||
dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.property.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.private_readonly_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_readonly_field.applicable_accessibilities = private, protected
|
||||
dotnet_naming_symbols.private_readonly_field.required_modifiers = readonly
|
||||
|
||||
dotnet_naming_symbols.private_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_field.applicable_accessibilities = private, protected
|
||||
dotnet_naming_symbols.private_field.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.async_method.applicable_kinds = method, local_function
|
||||
dotnet_naming_symbols.async_method.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.async_method.required_modifiers = async
|
||||
|
||||
dotnet_naming_symbols.local_variable.applicable_kinds = parameter, local
|
||||
dotnet_naming_symbols.local_variable.applicable_accessibilities = local
|
||||
dotnet_naming_symbols.local_variable.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
|
||||
dotnet_naming_style.all_upper.capitalization = all_upper
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_underscore.required_prefix = _
|
||||
dotnet_naming_style.begins_with_underscore.required_suffix =
|
||||
dotnet_naming_style.begins_with_underscore.word_separator =
|
||||
dotnet_naming_style.begins_with_underscore.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.ends_with_async.required_prefix =
|
||||
# dotnet_naming_style.ends_with_async.required_suffix = Async
|
||||
dotnet_naming_style.ends_with_async.word_separator =
|
||||
dotnet_naming_style.ends_with_async.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.camel_case.required_prefix =
|
||||
dotnet_naming_style.camel_case.required_suffix =
|
||||
dotnet_naming_style.camel_case.word_separator =
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
# CA1822: Mark members as static
|
||||
dotnet_diagnostic.ca1822.severity = suggestion
|
||||
|
||||
# IDE0004: Cast is redundant
|
||||
dotnet_diagnostic.ide0004.severity = warning
|
||||
|
||||
# IDE0058: Expression value is never used
|
||||
dotnet_diagnostic.ide0058.severity = none
|
||||
|
||||
# # IDE0011: Add braces to 'if'/'else' statement
|
||||
# dotnet_diagnostic.ide0011.severity = none
|
||||
|
||||
resharper_wrap_after_invocation_lpar = false
|
||||
resharper_wrap_before_invocation_rpar = false
|
||||
|
||||
# ReSharper properties
|
||||
resharper_align_multiline_calls_chain = true
|
||||
resharper_csharp_wrap_after_declaration_lpar = true
|
||||
resharper_csharp_wrap_after_invocation_lpar = false
|
||||
resharper_csharp_wrap_before_binary_opsign = true
|
||||
resharper_csharp_wrap_before_invocation_rpar = false
|
||||
resharper_csharp_wrap_parameters_style = chop_if_long
|
||||
resharper_force_chop_compound_if_expression = false
|
||||
resharper_keep_existing_linebreaks = true
|
||||
resharper_keep_user_linebreaks = true
|
||||
resharper_max_formal_parameters_on_line = 3
|
||||
resharper_place_simple_embedded_statement_on_same_line = false
|
||||
resharper_wrap_chained_binary_expressions = chop_if_long
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
resharper_wrap_object_and_collection_initializer_style = chop_always
|
||||
|
||||
resharper_csharp_wrap_before_first_type_parameter_constraint = true
|
||||
resharper_csharp_place_type_constraints_on_same_line = false
|
||||
resharper_csharp_wrap_before_extends_colon = true
|
||||
resharper_csharp_place_constructor_initializer_on_same_line = false
|
||||
resharper_force_attribute_style = separate
|
||||
resharper_csharp_braces_for_ifelse = required_for_multiline_statement
|
||||
resharper_csharp_braces_for_foreach = required_for_multiline
|
||||
resharper_csharp_braces_for_while = required_for_multiline
|
||||
resharper_csharp_braces_for_for = required_for_multiline
|
||||
resharper_arrange_redundant_parentheses_highlighting = hint
|
||||
|
||||
# IDE0011: Add braces
|
||||
dotnet_diagnostic.IDE0011.severity = warning
|
412
src/Ellie/Bot.cs
Normal file
412
src/Ellie/Bot.cs
Normal file
|
@ -0,0 +1,412 @@
|
|||
#nullable disable
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ellie.Common.Configs;
|
||||
using Ellie.Common.ModuleBehaviors;
|
||||
using Ellie.Db;
|
||||
using Ellie.Modules.Administration;
|
||||
using Ellie.Modules.Gambling;
|
||||
using Ellie.Modules.Help;
|
||||
using Ellie.Modules.Music;
|
||||
using Ellie.Modules.EllieExpressions;
|
||||
using Ellie.Modules.Patronage;
|
||||
using Ellie.Modules.Permissions;
|
||||
using Ellie.Modules.Searches;
|
||||
using Ellie.Modules.Utility;
|
||||
using Ellie.Modules.Xp;
|
||||
using Ellie.Services.Database;
|
||||
using Ellie.Services.Database.Models;
|
||||
using Ninject;
|
||||
using Ninject.Planning;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using RunMode = Discord.Commands.RunMode;
|
||||
|
||||
|
||||
namespace Ellie;
|
||||
|
||||
public class Bot : IBot
|
||||
{
|
||||
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||
|
||||
public DiscordSocketClient Client { get; set; }
|
||||
public IReadOnlyCollection<GuildConfig> AllGuildConfigs { get; private set; }
|
||||
|
||||
private IKernel Services { get; set; }
|
||||
|
||||
// todo remove
|
||||
public string Mention { get; private set; }
|
||||
public bool IsReady { get; private set; }
|
||||
public int ShardId { get; set; }
|
||||
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly CommandService _commandService;
|
||||
private readonly DbService _db;
|
||||
|
||||
private readonly IBotCredsProvider _credsProvider;
|
||||
|
||||
private readonly Assembly[] _loadedAssemblies;
|
||||
// private readonly InteractionService _interactionService;
|
||||
|
||||
public Bot(int shardId, int? totalShards, string credPath = null)
|
||||
{
|
||||
if (shardId < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(shardId));
|
||||
|
||||
ShardId = shardId;
|
||||
_credsProvider = new BotCredsProvider(totalShards, credPath);
|
||||
_creds = _credsProvider.GetCreds();
|
||||
|
||||
_db = new EllieDbService(_credsProvider);
|
||||
|
||||
var messageCacheSize =
|
||||
#if GLOBAL_ELLIE
|
||||
0;
|
||||
#else
|
||||
50;
|
||||
#endif
|
||||
|
||||
if (!_creds.UsePrivilegedIntents)
|
||||
Log.Warning("You are not using privileged intents. Some features will not work properly");
|
||||
|
||||
Client = new(new()
|
||||
{
|
||||
MessageCacheSize = messageCacheSize,
|
||||
LogLevel = LogSeverity.Warning,
|
||||
ConnectionTimeout = int.MaxValue,
|
||||
TotalShards = _creds.TotalShards,
|
||||
ShardId = shardId,
|
||||
AlwaysDownloadUsers = false,
|
||||
AlwaysResolveStickers = false,
|
||||
AlwaysDownloadDefaultStickers = false,
|
||||
GatewayIntents = _creds.UsePrivilegedIntents
|
||||
? GatewayIntents.All
|
||||
: GatewayIntents.AllUnprivileged,
|
||||
LogGatewayIntentWarnings = false,
|
||||
FormatUsersInBidirectionalUnicode = false,
|
||||
DefaultRetryMode = RetryMode.Retry502
|
||||
});
|
||||
|
||||
_commandService = new(new()
|
||||
{
|
||||
CaseSensitiveCommands = false,
|
||||
DefaultRunMode = RunMode.Sync,
|
||||
});
|
||||
|
||||
// _interactionService = new(Client.Rest);
|
||||
|
||||
Client.Log += Client_Log;
|
||||
_loadedAssemblies = new[]
|
||||
{
|
||||
typeof(Bot).Assembly, // bot
|
||||
typeof(Creds).Assembly, // bot.common
|
||||
|
||||
// modules
|
||||
typeof(EllieExpression).Assembly, typeof(Administration).Assembly, typeof(Gambling).Assembly,
|
||||
typeof(Help).Assembly, typeof(Music).Assembly, typeof(Patronage).Assembly, typeof(Permissions).Assembly,
|
||||
typeof(Searches).Assembly, typeof(Utility).Assembly, typeof(Xp).Assembly,
|
||||
};
|
||||
}
|
||||
|
||||
public IReadOnlyList<ulong> GetCurrentGuildIds()
|
||||
=> Client.Guilds.Select(x => x.Id).ToList();
|
||||
|
||||
private void AddServices()
|
||||
{
|
||||
var startingGuildIdList = GetCurrentGuildIds();
|
||||
var sw = Stopwatch.StartNew();
|
||||
var bot = Client.CurrentUser;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId);
|
||||
AllGuildConfigs = uow.Set<GuildConfig>().GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
||||
}
|
||||
|
||||
var svcs = new StandardKernel(new NinjectSettings()
|
||||
{
|
||||
ThrowOnGetServiceNotFound = true,
|
||||
ActivationCacheDisabled = true,
|
||||
});
|
||||
|
||||
// this is required in order for medusa unloading to work
|
||||
svcs.Components.Remove<IPlanner, Planner>();
|
||||
svcs.Components.Add<IPlanner, RemovablePlanner>();
|
||||
|
||||
svcs.AddSingleton<IBotCredentials, IBotCredentials>(_ => _credsProvider.GetCreds());
|
||||
svcs.AddSingleton<DbService, DbService>(_db);
|
||||
svcs.AddSingleton<IBotCredsProvider>(_credsProvider);
|
||||
svcs.AddSingleton<DiscordSocketClient>(Client);
|
||||
svcs.AddSingleton<CommandService>(_commandService);
|
||||
svcs.AddSingleton<Bot>(this);
|
||||
svcs.AddSingleton<IBot>(this);
|
||||
|
||||
svcs.AddSingleton<ISeria, JsonSeria>();
|
||||
svcs.AddSingleton<IConfigSeria, YamlSeria>();
|
||||
svcs.AddSingleton<IMemoryCache, MemoryCache>(new MemoryCache(new MemoryCacheOptions()));
|
||||
svcs.AddSingleton<IBehaviorHandler, BehaviorHandler>();
|
||||
|
||||
|
||||
foreach (var a in _loadedAssemblies)
|
||||
{
|
||||
svcs.AddConfigServices(a)
|
||||
.AddConfigMigrators(a)
|
||||
.AddLifetimeServices(a);
|
||||
}
|
||||
|
||||
svcs.AddMusic()
|
||||
.AddCache(_creds)
|
||||
.AddHttpClients();
|
||||
|
||||
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
||||
{
|
||||
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
||||
}
|
||||
else
|
||||
{
|
||||
svcs.AddSingleton<RemoteGrpcCoordinator>();
|
||||
svcs.AddSingleton<ICoordinator>(_ => svcs.GetRequiredService<RemoteGrpcCoordinator>());
|
||||
svcs.AddSingleton<IReadyExecutor>(_ => svcs.GetRequiredService<RemoteGrpcCoordinator>());
|
||||
}
|
||||
|
||||
svcs.AddSingleton<IServiceProvider>(svcs);
|
||||
|
||||
//initialize Services
|
||||
Services = svcs;
|
||||
Services.GetRequiredService<IBehaviorHandler>().Initialize();
|
||||
|
||||
if (Client.ShardId == 0)
|
||||
ApplyConfigMigrations();
|
||||
|
||||
foreach (var a in _loadedAssemblies)
|
||||
{
|
||||
LoadTypeReaders(a);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
Log.Information("All services loaded in {ServiceLoadTime:F2}s", sw.Elapsed.TotalSeconds);
|
||||
}
|
||||
|
||||
private void ApplyConfigMigrations()
|
||||
{
|
||||
// execute all migrators
|
||||
var migrators = Services.GetServices<IConfigMigrator>();
|
||||
foreach (var migrator in migrators)
|
||||
migrator.EnsureMigrated();
|
||||
}
|
||||
|
||||
private void LoadTypeReaders(Assembly assembly)
|
||||
{
|
||||
var filteredTypes = assembly.GetTypes()
|
||||
.Where(x => x.IsSubclassOf(typeof(TypeReader))
|
||||
&& x.BaseType?.GetGenericArguments().Length > 0
|
||||
&& !x.IsAbstract);
|
||||
|
||||
foreach (var ft in filteredTypes)
|
||||
{
|
||||
var baseType = ft.BaseType;
|
||||
if (baseType is null)
|
||||
continue;
|
||||
|
||||
var typeReader = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
|
||||
var typeArgs = baseType.GetGenericArguments();
|
||||
_commandService.AddTypeReader(typeArgs[0], typeReader);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoginAsync(string token)
|
||||
{
|
||||
var clientReady = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
async Task SetClientReady()
|
||||
{
|
||||
clientReady.TrySetResult(true);
|
||||
try
|
||||
{
|
||||
foreach (var chan in await Client.GetDMChannelsAsync())
|
||||
await chan.CloseAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
//connect
|
||||
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
|
||||
try
|
||||
{
|
||||
Client.Ready += SetClientReady;
|
||||
|
||||
await Client.LoginAsync(TokenType.Bot, token);
|
||||
await Client.StartAsync();
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
LoginErrorHandler.Handle(ex);
|
||||
Helpers.ReadErrorAndExit(3);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoginErrorHandler.Handle(ex);
|
||||
Helpers.ReadErrorAndExit(4);
|
||||
}
|
||||
|
||||
await clientReady.Task.ConfigureAwait(false);
|
||||
Client.Ready -= SetClientReady;
|
||||
|
||||
Client.JoinedGuild += Client_JoinedGuild;
|
||||
Client.LeftGuild += Client_LeftGuild;
|
||||
|
||||
// _ = Client.SetStatusAsync(UserStatus.Online);
|
||||
Log.Information("Shard {ShardId} logged in", Client.ShardId);
|
||||
}
|
||||
|
||||
private Task Client_LeftGuild(SocketGuild arg)
|
||||
{
|
||||
Log.Information("Left server: {GuildName} [{GuildId}]", arg?.Name, arg?.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Client_JoinedGuild(SocketGuild arg)
|
||||
{
|
||||
Log.Information("Joined server: {GuildName} [{GuildId}]", arg.Name, arg.Id);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
GuildConfig gc;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
gc = uow.GuildConfigsForId(arg.Id, null);
|
||||
}
|
||||
|
||||
await JoinedGuild.Invoke(gc);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task RunAsync()
|
||||
{
|
||||
if (ShardId == 0)
|
||||
await _db.SetupAsync();
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
await LoginAsync(_creds.Token);
|
||||
|
||||
Mention = Client.CurrentUser.Mention;
|
||||
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
||||
try
|
||||
{
|
||||
AddServices();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error adding services");
|
||||
Helpers.ReadErrorAndExit(9);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
|
||||
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
||||
|
||||
// start handling messages received in commandhandler
|
||||
await commandHandler.StartHandling();
|
||||
|
||||
foreach (var a in _loadedAssemblies)
|
||||
{
|
||||
await _commandService.AddModulesAsync(a, Services);
|
||||
}
|
||||
|
||||
// await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||
IsReady = true;
|
||||
|
||||
await EnsureBotOwnershipAsync();
|
||||
_ = Task.Run(ExecuteReadySubscriptions);
|
||||
Log.Information("Shard {ShardId} ready", Client.ShardId);
|
||||
}
|
||||
|
||||
private async ValueTask EnsureBotOwnershipAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_creds.OwnerIds.Count != 0)
|
||||
return;
|
||||
|
||||
Log.Information("Initializing Owner Id...");
|
||||
var info = await Client.GetApplicationInfoAsync();
|
||||
_credsProvider.ModifyCredsFile(x => x.OwnerIds = new[] { info.Owner.Id });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning("Getting application info failed: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ExecuteReadySubscriptions()
|
||||
{
|
||||
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
||||
var tasks = readyExecutors.Select(async toExec =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await toExec.OnReadyAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex,
|
||||
"Failed running OnReadyAsync method on {Type} type: {Message}",
|
||||
toExec.GetType().Name,
|
||||
ex.Message);
|
||||
}
|
||||
});
|
||||
|
||||
return tasks.WhenAll();
|
||||
}
|
||||
|
||||
private Task Client_Log(LogMessage arg)
|
||||
{
|
||||
if (arg.Message?.Contains("unknown dispatch", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (arg.Exception is { InnerException: WebSocketClosedException { CloseCode: 4014 } })
|
||||
{
|
||||
Log.Error("""
|
||||
Login failed.
|
||||
|
||||
*** Please enable privileged intents ***
|
||||
|
||||
Certain Ellie features require Discord's privileged gateway intents.
|
||||
These include greeting and goodbye messages, as well as creating the Owner message channels for DM forwarding.
|
||||
|
||||
How to enable privileged intents:
|
||||
1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
|
||||
2. Select your Application.
|
||||
3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
|
||||
4. Enable all intents.
|
||||
5. Restart your bot.
|
||||
|
||||
Read this only if your bot is in 100 or more servers:
|
||||
|
||||
You'll need to apply to use the intents with Discord, but for small selfhosts, all that is required is enabling the intents in the developer portal.
|
||||
Yes, this is a new thing from Discord, as of October 2020. No, there's nothing we can do about it. Yes, we're aware it worked before.
|
||||
While waiting for your bot to be accepted, you can change the 'usePrivilegedIntents' inside your creds.yml to 'false', although this will break many of the ellie's features
|
||||
""");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#if GLOBAL_ELLIE || DEBUG
|
||||
if (arg.Exception is not null)
|
||||
Log.Warning(arg.Exception, "{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);
|
||||
else
|
||||
Log.Warning("{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);
|
||||
#endif
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task RunAndBlockAsync()
|
||||
{
|
||||
await RunAsync();
|
||||
await Task.Delay(-1);
|
||||
}
|
||||
}
|
|
@ -97,6 +97,7 @@
|
|||
<PackageReference Include="TwitchLib.Api" Version="3.4.1" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ayu\Ayu.Discord.Voice\Ayu.Discord.Voice.csproj" />
|
||||
<ProjectReference Include="..\Ellie.Econ\Ellie.Econ.csproj" />
|
||||
|
@ -129,10 +130,11 @@
|
|||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\Mysql\" />
|
||||
<Folder Include="Migrations\Postgresql\" />
|
||||
<Folder Include="Migrations\Sqlite\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Version)' == '' ">
|
||||
<VersionPrefix Condition=" '$(VersionPrefix)' == '' ">5.0.0</VersionPrefix>
|
||||
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionPrefix).$(VersionSuffix)</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
31
src/Ellie/GlobalUsings.cs
Normal file
31
src/Ellie/GlobalUsings.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
// global using System.Collections.Concurrent;
|
||||
global using NonBlocking;
|
||||
|
||||
// packages
|
||||
global using Serilog;
|
||||
global using Humanizer;
|
||||
|
||||
// ellie
|
||||
global using Ellie;
|
||||
global using Ellie.Services;
|
||||
global using Ellise.Common; // new project
|
||||
global using Ellie.Common; // old + ellie specific things
|
||||
global using Ellie.Common.Attributes;
|
||||
global using Ellie.Extensions;
|
||||
global using Ellie.Marmalade;
|
||||
|
||||
// discord
|
||||
global using Discord;
|
||||
global using Discord.Commands;
|
||||
global using Discord.Net;
|
||||
global using Discord.WebSocket;
|
||||
|
||||
// aliases
|
||||
global using GuildPerm = Discord.GuildPermission;
|
||||
global using ChannelPerm = Discord.ChannelPermission;
|
||||
global using BotPermAttribute = Discord.Commands.RequireBotPermissionAttribute;
|
||||
global using LeftoverAttribute = Discord.Commands.RemainderAttribute;
|
||||
global using TypeReaderResult = Ellie.Common.TypeReaders.TypeReaderResult;
|
||||
|
||||
// non-essential
|
||||
global using JetBrains.Annotations;
|
18
src/Ellie/Modules/Gambling/CurrencyProvider.cs
Normal file
18
src/Ellie/Modules/Gambling/CurrencyProvider.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using Ellie.Bot.Common;
|
||||
using Ellie.Modules.Gambling.Services;
|
||||
|
||||
namespace Ellie.Modules.Gambling;
|
||||
|
||||
// todo do we need both currencyprovider and currencyservice
|
||||
public sealed class CurrencyProvider : ICurrencyProvider, IEService
|
||||
{
|
||||
private readonly GamblingConfigService _cs;
|
||||
|
||||
public CurrencyProvider(GamblingConfigService cs)
|
||||
{
|
||||
_cs = cs;
|
||||
}
|
||||
|
||||
public string GetCurrencySign()
|
||||
=> _cs.Data.Currency.Sign;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace Ellie.Modules;
|
||||
|
||||
public interface IMarmaladesRepositoryService
|
||||
{
|
||||
Task<List<ModuleItem>> GetModuleItemsAsync();
|
||||
}
|
229
src/Ellie/Modules/Marmalades/Marmalade.cs
Normal file
229
src/Ellie/Modules/Marmalades/Marmalade.cs
Normal file
|
@ -0,0 +1,229 @@
|
|||
using Ellie.Marmalade;
|
||||
|
||||
namespace Ellie.Modules;
|
||||
|
||||
[OwnerOnly]
|
||||
public partial class Marmalade : EllieModule<IMarmaladeLoaderSevice>
|
||||
{
|
||||
private readonly IMarmaladesRepositoryService _repo;
|
||||
|
||||
public Marmalade(IMarmaladesRepositoryService repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task MarmaladeLoad(string? name = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
var loaded = _service.GetLoadedMarmalades()
|
||||
.Select(x => x.Name)
|
||||
.ToHashSet();
|
||||
|
||||
var unloaded = _service.GetAllMarmalades()
|
||||
.Where(x => !loaded.Contains(x))
|
||||
.Select(x => Format.Code(x.ToString()))
|
||||
.ToArray();
|
||||
|
||||
if (unloaded.Length == 0)
|
||||
{
|
||||
await ReplyPendingLocalizedAsync(strs.no_marmalade_available);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(0,
|
||||
page =>
|
||||
{
|
||||
return _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.list_of_unloaded))
|
||||
.WithDescription(unloaded.Skip(10 * page).Take(10).Join('\n'));
|
||||
},
|
||||
unloaded.Length,
|
||||
10);
|
||||
return;
|
||||
}
|
||||
|
||||
var res = await _service.LoadMarmaladeAsync(name);
|
||||
if (res == MarmaladeLoadResult.Success)
|
||||
await ReplyConfirmLocalizedAsync(strs.marmalade_loaded(Format.Code(name)));
|
||||
else
|
||||
{
|
||||
var locStr = res switch
|
||||
{
|
||||
MarmaladeLoadResult.Empty => strs.marmalade_empty,
|
||||
MarmaladeLoadResult.AlreadyLoaded => strs.marmalade_already_loaded(Format.Code(name)),
|
||||
MarmaladeLoadResult.NotFound => strs.marmalade_invalid_not_found,
|
||||
MarmaladeLoadResult.UnknownError => strs.error_occured,
|
||||
_ => strs.error_occured
|
||||
};
|
||||
|
||||
await ReplyErrorLocalizedAsync(locStr);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task MarmaladeUnload(string? name = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
var loaded = _service.GetLoadedMarmalades();
|
||||
if (loaded.Count == 0)
|
||||
{
|
||||
await ReplyPendingLocalizedAsync(strs.no_marmalade_loaded);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(_eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.loaded_marmalades))
|
||||
.WithDescription(loaded.Select(x => x.Name)
|
||||
.Join("\n")));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var res = await _service.UnloadMarmaladeAsync(name);
|
||||
if (res == MarmaladeUnloadResult.Success)
|
||||
await ReplyConfirmLocalizedAsync(strs.marmalade_unloaded(Format.Code(name)));
|
||||
else
|
||||
{
|
||||
var locStr = res switch
|
||||
{
|
||||
MarmaladeUnloadResult.NotFound => strs.marmalade_not_loaded,
|
||||
MarmaladeUnloadResult.PossiblyUnable => strs.marmalade_possibly_cant_unload,
|
||||
_ => strs.error_occured
|
||||
};
|
||||
|
||||
await ReplyErrorLocalizedAsync(locStr);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task MarmaladeList()
|
||||
{
|
||||
var all = _service.GetAllMarmalades();
|
||||
|
||||
if (all.Count == 0)
|
||||
{
|
||||
await ReplyPendingLocalizedAsync(strs.no_marmalade_available);
|
||||
return;
|
||||
}
|
||||
|
||||
var loaded = _service.GetLoadedMarmalades()
|
||||
.Select(x => x.Name)
|
||||
.ToHashSet();
|
||||
|
||||
var output = all
|
||||
.Select(m =>
|
||||
{
|
||||
var emoji = loaded.Contains(m) ? "`✅`" : "`🔴`";
|
||||
return $"{emoji} `{m}`";
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(0,
|
||||
page => _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.list_of_marmalades))
|
||||
.WithDescription(output.Skip(page * 10).Take(10).Join('\n')),
|
||||
output.Length,
|
||||
10);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task MarmaladeInfo(string? name = null)
|
||||
{
|
||||
var marmalades = _service.GetLoadedMarmalades();
|
||||
|
||||
if (name is not null)
|
||||
{
|
||||
var found = marmalades.FirstOrDefault(x => string.Equals(x.Name,
|
||||
name,
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (found is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.marmalade_name_not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
var cmdCount = found.Canaries.Sum(x => x.Commands.Count);
|
||||
var cmdNames = found.Canaries
|
||||
.SelectMany(x => Format.Code(string.IsNullOrWhiteSpace(x.Prefix)
|
||||
? x.Name
|
||||
: $"{x.Prefix} {x.Name}"))
|
||||
.Join("\n");
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithAuthor(GetText(strs.marmalade_info))
|
||||
.WithTitle(found.Name)
|
||||
.WithDescription(found.Description)
|
||||
.AddField(GetText(strs.sneks_count(found.Canaries.Count)),
|
||||
found.Canaries.Count == 0
|
||||
? "-"
|
||||
: found.Canaries.Select(x => x.Name).Join('\n'),
|
||||
true)
|
||||
.AddField(GetText(strs.commands_count(cmdCount)),
|
||||
string.IsNullOrWhiteSpace(cmdNames)
|
||||
? "-"
|
||||
: cmdNames,
|
||||
true);
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (marmalades.Count == 0)
|
||||
{
|
||||
await ReplyPendingLocalizedAsync(strs.no_marmalade_loaded);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(0,
|
||||
page =>
|
||||
{
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor();
|
||||
|
||||
foreach (var marmalade in marmalades.Skip(page * 9).Take(9))
|
||||
{
|
||||
eb.AddField(marmalade.Name,
|
||||
$"""
|
||||
`Canaries:` {marmalade.Canaries.Count}
|
||||
`Commands:` {marmalade.Canaries.Sum(x => x.Commands.Count)}
|
||||
--
|
||||
{marmalade.Description}
|
||||
""");
|
||||
}
|
||||
|
||||
return eb;
|
||||
}, marmalades.Count, 9);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task MarmaladeSearch()
|
||||
{
|
||||
var eb = _eb.Create()
|
||||
.WithTitle(GetText(strs.list_of_marmalades))
|
||||
.WithOkColor();
|
||||
|
||||
foreach (var item in await _repo.GetModuleItemsAsync())
|
||||
{
|
||||
eb.AddField(item.Name, $"""
|
||||
{item.Description}
|
||||
`{item.Command}`
|
||||
""", true);
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
}
|
8
src/Ellie/Modules/Marmalades/MarmaladeItem.cs
Normal file
8
src/Ellie/Modules/Marmalades/MarmaladeItem.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ellie.Modules;
|
||||
|
||||
public sealed class ModuleItem
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required string Command { get; init; }
|
||||
}
|
22
src/Ellie/Modules/Marmalades/MarmaladesRepositoryService.cs
Normal file
22
src/Ellie/Modules/Marmalades/MarmaladesRepositoryService.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
namespace Ellie.Modules;
|
||||
|
||||
public class MarmaladesRepositoryService : IMarmaladesRepositoryService, IEService
|
||||
{
|
||||
public async Task<List<ModuleItem>> GetModuleItemsAsync()
|
||||
{
|
||||
// Simulate retrieving data from a database or API
|
||||
await Task.Delay(100);
|
||||
return new List<ModuleItem>
|
||||
{
|
||||
new ModuleItem { Name = "RSS Reader", Description = "Keep up to date with your favorite websites", Command = ".mainstall rss" },
|
||||
new ModuleItem { Name = "Password Manager", Description = "Safely store and manage all your passwords", Command = ".mainstall passwordmanager" },
|
||||
new ModuleItem { Name = "Browser Extension", Description = "Enhance your browsing experience with useful tools", Command = ".mainstall browserextension" },
|
||||
new ModuleItem { Name = "Video Downloader", Description = "Download videos from popular websites", Command = ".mainstall videodownloader" },
|
||||
new ModuleItem { Name = "Virtual Private Network", Description = "Securely browse the web and protect your privacy", Command = ".mainstall vpn" },
|
||||
new ModuleItem { Name = "Ad Blocker", Description = "Block annoying ads and improve page load times", Command = ".mainstall adblocker" },
|
||||
new ModuleItem { Name = "Cloud Storage", Description = "Store and share your files online", Command = ".mainstall cloudstorage" },
|
||||
new ModuleItem { Name = "Social Media Manager", Description = "Manage all your social media accounts in one place", Command = ".mainstall socialmediamanager" },
|
||||
new ModuleItem { Name = "Code Editor", Description = "Write and edit code online", Command = ".mainstall codeeditor" }
|
||||
};
|
||||
}
|
||||
}
|
80
src/Ellie/PermissionChecker.cs
Normal file
80
src/Ellie/PermissionChecker.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using Ellie.Bot.Common;
|
||||
using Ellie.Modules.Permissions.Common;
|
||||
using Ellie.Modules.Permissions.Services;
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
|
||||
namespace Ellie;
|
||||
|
||||
public sealed class PermissionChecker : IPermissionChecker, IEService
|
||||
{
|
||||
private readonly PermissionService _perms;
|
||||
private readonly GlobalPermissionService _gperm;
|
||||
private readonly CmdCdService _cmdCds;
|
||||
|
||||
public PermissionChecker(PermissionService perms, GlobalPermissionService gperm, CmdCdService cmdCds)
|
||||
{
|
||||
_perms = perms;
|
||||
_gperm = gperm;
|
||||
_cmdCds = cmdCds;
|
||||
}
|
||||
|
||||
public async Task<OneOf<Success, Error<LocStr>>> CheckAsync(
|
||||
IGuild guild,
|
||||
IMessageChannel channel,
|
||||
IUser author,
|
||||
string module,
|
||||
string? cmd)
|
||||
{
|
||||
module = module.ToLowerInvariant();
|
||||
cmd = cmd?.ToLowerInvariant();
|
||||
// todo add proper string
|
||||
if (cmd is not null && await _cmdCds.TryBlock(guild, author, cmd))
|
||||
return new Error<LocStr>(new());
|
||||
|
||||
try
|
||||
{
|
||||
if (_gperm.BlockedModules.Contains(module))
|
||||
{
|
||||
Log.Information("u:{UserId} tried to use module {Module} which is globally disabled.",
|
||||
author.Id,
|
||||
module
|
||||
);
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
if (guild is SocketGuild sg)
|
||||
{
|
||||
var pc = _perms.GetCacheFor(guild.Id);
|
||||
if (!pc.Permissions.CheckPermissions(author, channel, cmd, "ACTUALEXPRESSIONS", out var index))
|
||||
{
|
||||
if (pc.Verbose)
|
||||
{
|
||||
// todo fix
|
||||
// var permissionMessage = strs.perm_prevent(index + 1,
|
||||
// Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)));
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// await msg.Channel.SendErrorAsync(_eb, permissionMessage);
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// Log.Information("{PermissionMessage}", permissionMessage);
|
||||
}
|
||||
|
||||
// todo add proper string
|
||||
return new Error<LocStr>(new());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return new Success();
|
||||
}
|
||||
}
|
28
src/Ellie/Program.cs
Normal file
28
src/Ellie/Program.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
var pid = Environment.ProcessId;
|
||||
|
||||
var shardId = 0;
|
||||
int? totalShards = null; // 0 to read from creds.yml
|
||||
if (args.Length > 0 && args[0] != "run")
|
||||
{
|
||||
if (!int.TryParse(args[0], out shardId))
|
||||
{
|
||||
Console.Error.WriteLine("Invalid first argument (shard id): {0}", args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 1)
|
||||
{
|
||||
if (!int.TryParse(args[1], out var shardCount))
|
||||
{
|
||||
Console.Error.WriteLine("Invalid second argument (total shards): {0}", args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
totalShards = shardCount;
|
||||
}
|
||||
}
|
||||
|
||||
LogSetup.SetupLogger(shardId);
|
||||
Log.Information("Pid: {ProcessId}", pid);
|
||||
|
||||
await new Bot(shardId, totalShards, Environment.GetEnvironmentVariable("Ellie__creds")).RunAndBlockAsync();
|
|
@ -133,7 +133,7 @@ public static class ServiceCollectionExtensions
|
|||
{
|
||||
scan.From(a)
|
||||
.SelectAllClasses()
|
||||
.Where(c => (c.IsAssignableTo(typeof(INService))
|
||||
.Where(c => (c.IsAssignableTo(typeof(IEService))
|
||||
|| c.IsAssignableTo(typeof(IExecOnMessage))
|
||||
|| c.IsAssignableTo(typeof(IInputTransformer))
|
||||
|| c.IsAssignableTo(typeof(IExecPreCommand))
|
||||
|
|
115
src/Ellie/creds_example.yml
Normal file
115
src/Ellie/creds_example.yml
Normal file
|
@ -0,0 +1,115 @@
|
|||
# DO NOT CHANGE
|
||||
version: 7
|
||||
# Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/
|
||||
token: ''
|
||||
# List of Ids of the users who have bot owner permissions
|
||||
# **DO NOT ADD PEOPLE YOU DON'T TRUST**
|
||||
ownerIds: []
|
||||
# Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted
|
||||
usePrivilegedIntents: true
|
||||
# The number of shards that the bot will be running on.
|
||||
# Leave at 1 if you don't know what you're doing.
|
||||
#
|
||||
# note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
|
||||
# Also, in that case you should be using Ellie.Coordinator to start the bot, and it will correctly override this value.
|
||||
totalShards: 1
|
||||
# Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||
# Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||
# Used only for Youtube Data Api (at the moment).
|
||||
googleApiKey: ''
|
||||
# Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
|
||||
# Enable SafeSearch
|
||||
# Remove all Sites to Search
|
||||
# Enable Search the entire web
|
||||
# Copy the 'Search Engine ID' to the SearchId field
|
||||
#
|
||||
# Do all steps again but enable image search for the ImageSearchId
|
||||
google:
|
||||
searchId:
|
||||
imageSearchId:
|
||||
# Settings for voting system for discordbots. Meant for use on global Nadeko.
|
||||
votes:
|
||||
# top.gg votes service url
|
||||
# This is the url of your instance of the Ellie.Votes api
|
||||
# Example: https://votes.my.cool.bot.com
|
||||
topggServiceUrl: ''
|
||||
# Authorization header value sent to the TopGG service url with each request
|
||||
# This should be equivalent to the TopggKey in your Ellie.Votes api appsettings.json file
|
||||
topggKey: ''
|
||||
# discords.com votes service url
|
||||
# This is the url of your instance of the Ellie.Votes api
|
||||
# Example: https://votes.my.cool.bot.com
|
||||
discordsServiceUrl: ''
|
||||
# Authorization header value sent to the Discords service url with each request
|
||||
# This should be equivalent to the DiscordsKey in your Ellie.Votes api appsettings.json file
|
||||
discordsKey: ''
|
||||
# Patreon auto reward system settings.
|
||||
# go to https://www.patreon.com/portal -> my clients -> create client
|
||||
patreon:
|
||||
clientId:
|
||||
accessToken: ''
|
||||
refreshToken: ''
|
||||
clientSecret: ''
|
||||
# Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type "prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);" in the console. (ctrl + shift + i)
|
||||
campaignId: ''
|
||||
# Api key for sending stats to DiscordBotList.
|
||||
botListToken: ''
|
||||
# Official cleverbot api key.
|
||||
cleverbotApiKey: ''
|
||||
# Official GPT-3 api key.
|
||||
gpt3ApiKey: ''
|
||||
# Which cache implementation should bot use.
|
||||
# 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset.
|
||||
# 'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml
|
||||
botCache: Memory
|
||||
# Redis connection string. Don't change if you don't know what you're doing.
|
||||
# Only used if botCache is set to 'redis'
|
||||
redisOptions: localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=
|
||||
# Database options. Don't change if you don't know what you're doing. Leave null for default values
|
||||
db:
|
||||
# Database type. "sqlite", "mysql" and "postgresql" are supported.
|
||||
# Default is "sqlite"
|
||||
type: sqlite
|
||||
# Database connection string.
|
||||
# You MUST change this if you're not using "sqlite" type.
|
||||
# Default is "Data Source=data/Ellie.db"
|
||||
# Example for mysql: "Server=localhost;Port=3306;Uid=root;Pwd=my_super_secret_mysql_password;Database=nadeko"
|
||||
# Example for postgresql: "Server=localhost;Port=5432;User Id=postgres;Password=my_super_secret_postgres_password;Database=nadeko;"
|
||||
connectionString: Data Source=data/Ellie.db
|
||||
# Address and port of the coordinator endpoint. Leave empty for default.
|
||||
# Change only if you've changed the coordinator address or port.
|
||||
coordinatorUrl: http://localhost:3442
|
||||
# Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)
|
||||
rapidApiKey:
|
||||
# https://locationiq.com api key (register and you will receive the token in the email).
|
||||
# Used only for .time command.
|
||||
locationIqApiKey:
|
||||
# https://timezonedb.com api key (register and you will receive the token in the email).
|
||||
# Used only for .time command
|
||||
timezoneDbApiKey:
|
||||
# https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
||||
# Used for cryptocurrency related commands.
|
||||
coinmarketcapApiKey:
|
||||
# Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api
|
||||
osuApiKey:
|
||||
# Optional Trovo client id.
|
||||
# You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.
|
||||
trovoClientId:
|
||||
# Obtain by creating an application at https://dev.twitch.tv/console/apps
|
||||
twitchClientId:
|
||||
# Obtain by creating an application at https://dev.twitch.tv/console/apps
|
||||
twitchClientSecret:
|
||||
# Command and args which will be used to restart the bot.
|
||||
# Only used if bot is executed directly (NOT through the coordinator)
|
||||
# placeholders:
|
||||
# {0} -> shard id
|
||||
# {1} -> total shards
|
||||
# Linux default
|
||||
# cmd: dotnet
|
||||
# args: "Ellie.dll -- {0}"
|
||||
# Windows default
|
||||
# cmd: Ellie.exe
|
||||
# args: "{0}"
|
||||
restartCommand:
|
||||
cmd:
|
||||
args:
|
BIN
src/Ellie/ellie_icon.ico
Normal file
BIN
src/Ellie/ellie_icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Loading…
Reference in a new issue