mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
- Credentials are now loading from creds.yml
- Removed/commented out obsolete credentials code - Added missing properties to creds.yml - Updated README.md with some tasks and progress
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
|
## [3.0.0] - 01.07.2021
|
||||||
|
|
||||||
|
WIP
|
||||||
|
|
||||||
## [2.46.0] - 17.06.2021
|
## [2.46.0] - 17.06.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
18
README.md
18
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Experimental Branch. Things will break.
|
# :warning: Experimental Branch. Things will break. :warning:
|
||||||
|
|
||||||
## Migration from 2.x
|
## Migration from 2.x
|
||||||
|
|
||||||
@@ -6,6 +6,16 @@
|
|||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
|
|
||||||
- Command attributes cleaned up
|
- explain properly: Command attributes cleaned up
|
||||||
- Database migrations cleaned up/squashed
|
- explain properly: Database migrations cleaned up/squashed
|
||||||
|
- explain properly: coord and coord.yml
|
||||||
|
- wip: credentials.json moved to `creds.yml`, example is in `creds_example.json`
|
||||||
|
- todo: from source run location is nadekobot/output
|
||||||
|
- todo: votes functionality changed
|
||||||
|
- todo: code cleanup tasks
|
||||||
|
- todo: remove colors from bot.cs
|
||||||
|
- todo: creds
|
||||||
|
- todo:
|
||||||
|
- todo?: maybe use https://github.com/Humanizr/Humanizer for trimto, time printing, date printing, etc
|
||||||
|
- todo?: use guild locale more in the code (from guild settings) (for dates, currency, etc?)
|
||||||
|
- todo?: Write a sourcegen for response strings and use const/static fields (maybe even typed to enforce correct number of arguments)
|
@@ -18,7 +18,6 @@ using System.Net.Http;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord.Net;
|
using Discord.Net;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Common.Configs;
|
using NadekoBot.Common.Configs;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
@@ -32,14 +31,14 @@ namespace NadekoBot
|
|||||||
{
|
{
|
||||||
public class Bot
|
public class Bot
|
||||||
{
|
{
|
||||||
public BotCredentials Credentials { get; }
|
private readonly IBotCredentials _creds;
|
||||||
public DiscordSocketClient Client { get; }
|
public DiscordSocketClient Client { get; }
|
||||||
public CommandService CommandService { get; }
|
public CommandService CommandService { get; }
|
||||||
|
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
|
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
|
||||||
|
|
||||||
/* Will have to be removed soon, it's been way too long */
|
// todo remove colors from here
|
||||||
public static Color OkColor { get; set; }
|
public static Color OkColor { get; set; }
|
||||||
public static Color ErrorColor { get; set; }
|
public static Color ErrorColor { get; set; }
|
||||||
public static Color PendingColor { get; set; }
|
public static Color PendingColor { get; set; }
|
||||||
@@ -60,10 +59,12 @@ namespace NadekoBot
|
|||||||
|
|
||||||
TerribleElevatedPermissionCheck();
|
TerribleElevatedPermissionCheck();
|
||||||
|
|
||||||
Credentials = new BotCredentials();
|
_creds = BotCredentialsProvider.CreateBotCredentials();
|
||||||
Cache = new RedisCache(Credentials, shardId);
|
|
||||||
LinqToDBForEFTools.Initialize();
|
// todo no need for cache prop
|
||||||
_db = new DbService(Credentials);
|
Cache = new RedisCache(_creds, shardId);
|
||||||
|
|
||||||
|
_db = new DbService(_creds);
|
||||||
|
|
||||||
if (shardId == 0)
|
if (shardId == 0)
|
||||||
{
|
{
|
||||||
@@ -75,7 +76,7 @@ namespace NadekoBot
|
|||||||
MessageCacheSize = 50,
|
MessageCacheSize = 50,
|
||||||
LogLevel = LogSeverity.Warning,
|
LogLevel = LogSeverity.Warning,
|
||||||
ConnectionTimeout = int.MaxValue,
|
ConnectionTimeout = int.MaxValue,
|
||||||
TotalShards = Credentials.TotalShards,
|
TotalShards = _creds.TotalShards,
|
||||||
ShardId = shardId,
|
ShardId = shardId,
|
||||||
AlwaysDownloadUsers = false,
|
AlwaysDownloadUsers = false,
|
||||||
ExclusiveBulkDelete = true,
|
ExclusiveBulkDelete = true,
|
||||||
@@ -116,7 +117,7 @@ namespace NadekoBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
var svcs = new ServiceCollection()
|
var svcs = new ServiceCollection()
|
||||||
.AddSingleton<IBotCredentials>(Credentials)
|
.AddSingleton<IBotCredentials>(_creds)
|
||||||
.AddSingleton(_db)
|
.AddSingleton(_db)
|
||||||
.AddSingleton(Client)
|
.AddSingleton(Client)
|
||||||
.AddSingleton(CommandService)
|
.AddSingleton(CommandService)
|
||||||
@@ -300,7 +301,7 @@ namespace NadekoBot
|
|||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
await LoginAsync(Credentials.Token).ConfigureAwait(false);
|
await LoginAsync(_creds.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
Mention = Client.CurrentUser.Mention;
|
Mention = Client.CurrentUser.Mention;
|
||||||
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
||||||
|
@@ -1,86 +1,120 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
|
using NadekoBot.Services;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace Nadeko.Common
|
namespace Nadeko.Common
|
||||||
{
|
{
|
||||||
public sealed record Creds
|
public sealed class Creds : IBotCredentials
|
||||||
{
|
{
|
||||||
public Creds()
|
public Creds()
|
||||||
{
|
{
|
||||||
Token = string.Empty;
|
Token = string.Empty;
|
||||||
OwnerIds = new()
|
OwnerIds = new();
|
||||||
{
|
|
||||||
105635576866156544
|
|
||||||
};
|
|
||||||
TotalShards = 1;
|
TotalShards = 1;
|
||||||
GoogleApiKey = string.Empty;
|
GoogleApiKey = string.Empty;
|
||||||
Votes = new(string.Empty, string.Empty);
|
Votes = new(string.Empty, string.Empty);
|
||||||
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||||
BotListToken = string.Empty;
|
BotListToken = string.Empty;
|
||||||
CleverbotApiKey = string.Empty;
|
CleverbotApiKey = string.Empty;
|
||||||
RedisOptions = "redis:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
|
RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
|
||||||
Db = new DbOptions();
|
Db = new()
|
||||||
|
{
|
||||||
|
Type = "sqlite",
|
||||||
|
ConnectionString = "Data Source=data/NadekoBot.db"
|
||||||
|
};
|
||||||
Version = 1;
|
Version = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
|
[Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
|
||||||
public string Token { get; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
[Comment(@"List of Ids of the users who have bot owner permissions
|
[Comment(@"List of Ids of the users who have bot owner permissions
|
||||||
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
|
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
|
||||||
public HashSet<ulong> OwnerIds { get; }
|
public List<ulong> OwnerIds { get; set; }
|
||||||
|
|
||||||
|
// todo update total shards on startup
|
||||||
[Comment(@"The number of shards that the bot will running on.
|
[Comment(@"The number of shards that the bot will running on.
|
||||||
Leave at 1 if you don't know what you're doing.")]
|
Leave at 1 if you don't know what you're doing.")]
|
||||||
public int TotalShards { get; }
|
public int TotalShards { get; set; }
|
||||||
|
|
||||||
[Comment(@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
[Comment(@"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.
|
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||||
Used only for Youtube Data Api (at the moment).")]
|
Used only for Youtube Data Api (at the moment).")]
|
||||||
public string GoogleApiKey { get; }
|
public string GoogleApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
||||||
public VotesSettings Votes { get; }
|
public VotesSettings Votes { get; set; }
|
||||||
|
|
||||||
[Comment(@"Patreon auto reward system settings.
|
[Comment(@"Patreon auto reward system settings.
|
||||||
go to https://www.patreon.com/portal -> my clients -> create client")]
|
go to https://www.patreon.com/portal -> my clients -> create client")]
|
||||||
public PatreonSettings Patreon { get; }
|
public PatreonSettings Patreon { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
||||||
public string BotListToken { get; }
|
public string BotListToken { get; set; }
|
||||||
|
|
||||||
[Comment(@"Official cleverbot api key.")]
|
[Comment(@"Official cleverbot api key.")]
|
||||||
public string CleverbotApiKey { get; }
|
public string CleverbotApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
|
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
|
||||||
public string RedisOptions { get; }
|
public string RedisOptions { get; set; }
|
||||||
|
|
||||||
[Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
|
[Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
|
||||||
public DbOptions Db { get; }
|
public DbOptions Db { get; set; }
|
||||||
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
[Comment(@"DO NOT CHANGE")]
|
||||||
public int Version { get; }
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public RestartConfig RestartCommand { get; set; }
|
||||||
|
|
||||||
|
[YamlIgnore]
|
||||||
|
public string PatreonCampaignId => Patreon?.CampaignId;
|
||||||
|
[YamlIgnore]
|
||||||
|
public string PatreonAccessToken => Patreon?.AccessToken;
|
||||||
|
|
||||||
|
public string VotesUrl { get; set; }
|
||||||
|
public string VotesToken { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||||
|
public string RapidApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
||||||
|
Used only for .time command.")]
|
||||||
|
public string LocationIqApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
|
||||||
|
Used only for .time command")]
|
||||||
|
public string TimezoneDbApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
||||||
|
Used for cryptocurrency related commands.")]
|
||||||
|
public string CoinmarketcapApiKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
||||||
|
public string OsuApiKey { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public class DbOptions
|
public class DbOptions
|
||||||
{
|
{
|
||||||
[Comment(@"Database type. Only sqlite supported atm")]
|
[Comment(@"Database type. Only sqlite supported atm")]
|
||||||
public string Type { get; } = "";
|
public string Type { get; set; }
|
||||||
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
||||||
public string ConnectionString { get; } = string.Empty;
|
public string ConnectionString { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo fixup patreon
|
||||||
public sealed record PatreonSettings
|
public sealed record PatreonSettings
|
||||||
{
|
{
|
||||||
[Comment(@"")]
|
[Comment(@"Access token. You have to manually update this 1st of each month by refreshing the token on https://patreon.com/portal")]
|
||||||
public string AccessToken { get; }
|
public string AccessToken { get; set; }
|
||||||
[Comment(@"")]
|
[Comment(@"Unused atm")]
|
||||||
public string RefreshToken { get; }
|
public string RefreshToken { get; set; }
|
||||||
[Comment(@"")]
|
[Comment(@"Unused atm")]
|
||||||
public string ClientSecret { get; }
|
public string ClientSecret { get; set; }
|
||||||
|
|
||||||
[Comment(@"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)")]
|
[Comment(@"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)")]
|
||||||
public string CampaignId { get; }
|
public string CampaignId { get; set; }
|
||||||
|
|
||||||
public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
|
public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
|
||||||
{
|
{
|
||||||
@@ -94,9 +128,9 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
|
|||||||
public sealed record VotesSettings
|
public sealed record VotesSettings
|
||||||
{
|
{
|
||||||
[Comment(@"")]
|
[Comment(@"")]
|
||||||
public string Url { get; }
|
public string Url { get; set; }
|
||||||
[Comment(@"")]
|
[Comment(@"")]
|
||||||
public string Key { get; }
|
public string Key { get; set; }
|
||||||
|
|
||||||
public VotesSettings(string url, string key)
|
public VotesSettings(string url, string key)
|
||||||
{
|
{
|
||||||
@@ -107,31 +141,31 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
|
|||||||
|
|
||||||
public class Old
|
public class Old
|
||||||
{
|
{
|
||||||
public string Token { get; } = string.Empty;
|
public string Token { get; set; } = string.Empty;
|
||||||
public ulong[] OwnerIds { get; } = new ulong[1];
|
public ulong[] OwnerIds { get; set; } = new ulong[1];
|
||||||
public string LoLApiKey { get; } = string.Empty;
|
public string LoLApiKey { get; set; } = string.Empty;
|
||||||
public string GoogleApiKey { get; } = string.Empty;
|
public string GoogleApiKey { get; set; } = string.Empty;
|
||||||
public string MashapeKey { get; } = string.Empty;
|
public string MashapeKey { get; set; } = string.Empty;
|
||||||
public string OsuApiKey { get; } = string.Empty;
|
public string OsuApiKey { get; set; } = string.Empty;
|
||||||
public string SoundCloudClientId { get; } = string.Empty;
|
public string SoundCloudClientId { get; set; } = string.Empty;
|
||||||
public string CleverbotApiKey { get; } = string.Empty;
|
public string CleverbotApiKey { get; set; } = string.Empty;
|
||||||
public string CarbonKey { get; } = string.Empty;
|
public string CarbonKey { get; set; } = string.Empty;
|
||||||
public int TotalShards { get; } = 1;
|
public int TotalShards { get; set; } = 1;
|
||||||
public string PatreonAccessToken { get; } = string.Empty;
|
public string PatreonAccessToken { get; set; } = string.Empty;
|
||||||
public string PatreonCampaignId { get; } = "334038";
|
public string PatreonCampaignId { get; set; } = "334038";
|
||||||
public RestartConfig? RestartCommand { get; } = null;
|
public RestartConfig? RestartCommand { get; set; } = null;
|
||||||
|
|
||||||
public string ShardRunCommand { get; } = string.Empty;
|
public string ShardRunCommand { get; set; } = string.Empty;
|
||||||
public string ShardRunArguments { get; } = string.Empty;
|
public string ShardRunArguments { get; set; } = string.Empty;
|
||||||
public int? ShardRunPort { get; } = null;
|
public int? ShardRunPort { get; set; } = null;
|
||||||
public string MiningProxyUrl { get; } = string.Empty;
|
public string MiningProxyUrl { get; set; } = string.Empty;
|
||||||
public string MiningProxyCreds { get; } = string.Empty;
|
public string MiningProxyCreds { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string BotListToken { get; } = string.Empty;
|
public string BotListToken { get; set; } = string.Empty;
|
||||||
public string TwitchClientId { get; } = string.Empty;
|
public string TwitchClientId { get; set; } = string.Empty;
|
||||||
public string VotesToken { get; } = string.Empty;
|
public string VotesToken { get; set; } = string.Empty;
|
||||||
public string VotesUrl { get; } = string.Empty;
|
public string VotesUrl { get; set; } = string.Empty;
|
||||||
public string RedisOptions { get; } = string.Empty;
|
public string RedisOptions { get; set; } = string.Empty;
|
||||||
|
|
||||||
public class RestartConfig
|
public class RestartConfig
|
||||||
{
|
{
|
||||||
@@ -141,8 +175,8 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
|
|||||||
this.Args = args;
|
this.Args = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Cmd { get; }
|
public string Cmd { get; set; }
|
||||||
public string Args { get; }
|
public string Args { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ using NadekoBot.Services;
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Nadeko.Common;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Services.Database
|
namespace NadekoBot.Services.Database
|
||||||
@@ -16,7 +17,7 @@ namespace NadekoBot.Services.Database
|
|||||||
{
|
{
|
||||||
LogSetup.SetupLogger(-2);
|
LogSetup.SetupLogger(-2);
|
||||||
var optionsBuilder = new DbContextOptionsBuilder<NadekoContext>();
|
var optionsBuilder = new DbContextOptionsBuilder<NadekoContext>();
|
||||||
IBotCredentials creds = new BotCredentials();
|
IBotCredentials creds = BotCredentialsProvider.CreateBotCredentials();
|
||||||
var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString);
|
var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString);
|
||||||
builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource);
|
builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource);
|
||||||
optionsBuilder.UseSqlite(builder.ToString());
|
optionsBuilder.UseSqlite(builder.ToString());
|
||||||
|
@@ -218,7 +218,7 @@ namespace NadekoBot.Modules.Administration.Services
|
|||||||
Log.Warning(
|
Log.Warning(
|
||||||
"No owner channels created! Make sure you've specified the correct OwnerId in the credentials.json file and invited the bot to a Discord server.");
|
"No owner channels created! Make sure you've specified the correct OwnerId in the credentials.json file and invited the bot to a Discord server.");
|
||||||
else
|
else
|
||||||
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Length} owner message channels.");
|
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Count} owner message channels.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task LeaveGuild(string guildStr)
|
public Task LeaveGuild(string guildStr)
|
||||||
|
@@ -430,7 +430,7 @@ namespace NadekoBot.Modules.Searches
|
|||||||
if (!await ValidateQuery(ctx.Channel, name).ConfigureAwait(false))
|
if (!await ValidateQuery(ctx.Channel, name).ConfigureAwait(false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
|
if (string.IsNullOrWhiteSpace(_creds.RapidApiKey))
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalizedAsync("mashape_api_missing").ConfigureAwait(false);
|
await ReplyErrorLocalizedAsync("mashape_api_missing").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
|
@@ -575,7 +575,7 @@ namespace NadekoBot.Modules.Searches.Services
|
|||||||
using (var http = _httpFactory.CreateClient())
|
using (var http = _httpFactory.CreateClient())
|
||||||
{
|
{
|
||||||
http.DefaultRequestHeaders.Clear();
|
http.DefaultRequestHeaders.Clear();
|
||||||
http.DefaultRequestHeaders.Add("x-rapidapi-key", _creds.MashapeKey);
|
http.DefaultRequestHeaders.Add("x-rapidapi-key", _creds.RapidApiKey);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.rapidapi.com/" +
|
var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.rapidapi.com/" +
|
||||||
|
@@ -34,6 +34,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||||
|
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||||
@@ -56,8 +57,13 @@
|
|||||||
<ProjectReference Include="..\ayu\Ayu.Discord.Voice\Ayu.Discord.Voice.csproj" />
|
<ProjectReference Include="..\ayu\Ayu.Discord.Voice\Ayu.Discord.Voice.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<None Update="creds.yml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="credentials.json" />
|
|
||||||
<Protobuf Include="..\NadekoBot.Coordinator\Protos\coordinator.proto" GrpcServices="Client">
|
<Protobuf Include="..\NadekoBot.Coordinator\Protos\coordinator.proto" GrpcServices="Client">
|
||||||
<Link>Protos\coordinator.proto</Link>
|
<Link>Protos\coordinator.proto</Link>
|
||||||
</Protobuf>
|
</Protobuf>
|
||||||
@@ -73,7 +79,7 @@
|
|||||||
<None Update="data\xp_template_backup.json">
|
<None Update="data\xp_template_backup.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="credentials_example.json">
|
<None Update="creds_example.yml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -4,7 +4,7 @@ using NadekoBot.Services.Database;
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NadekoBot.Db;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace NadekoBot.Services
|
namespace NadekoBot.Services
|
||||||
{
|
{
|
||||||
@@ -15,6 +15,8 @@ namespace NadekoBot.Services
|
|||||||
|
|
||||||
public DbService(IBotCredentials creds)
|
public DbService(IBotCredentials creds)
|
||||||
{
|
{
|
||||||
|
LinqToDBForEFTools.Initialize();
|
||||||
|
|
||||||
var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString);
|
var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString);
|
||||||
builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource);
|
builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource);
|
||||||
|
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
using Discord;
|
using System.Collections.Generic;
|
||||||
|
using Discord;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Nadeko.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Services
|
namespace NadekoBot.Services
|
||||||
{
|
{
|
||||||
@@ -7,31 +10,32 @@ namespace NadekoBot.Services
|
|||||||
{
|
{
|
||||||
string Token { get; }
|
string Token { get; }
|
||||||
string GoogleApiKey { get; }
|
string GoogleApiKey { get; }
|
||||||
ImmutableArray<ulong> OwnerIds { get; }
|
List<ulong> OwnerIds { get; }
|
||||||
string MashapeKey { get; }
|
string RapidApiKey { get; }
|
||||||
string PatreonAccessToken { get; }
|
string PatreonAccessToken { get; }
|
||||||
string CarbonKey { get; }
|
|
||||||
|
|
||||||
DBConfig Db { get; }
|
Creds.DbOptions Db { get; }
|
||||||
string OsuApiKey { get; }
|
string OsuApiKey { get; }
|
||||||
|
|
||||||
bool IsOwner(IUser u);
|
|
||||||
int TotalShards { get; }
|
int TotalShards { get; }
|
||||||
string ShardRunCommand { get; }
|
|
||||||
string ShardRunArguments { get; }
|
|
||||||
string PatreonCampaignId { get; }
|
string PatreonCampaignId { get; }
|
||||||
string CleverbotApiKey { get; }
|
string CleverbotApiKey { get; }
|
||||||
RestartConfig RestartCommand { get; }
|
RestartConfig RestartCommand { get; }
|
||||||
string VotesUrl { get; }
|
string VotesUrl { get; }
|
||||||
string VotesToken { get; }
|
string VotesToken { get; }
|
||||||
string BotListToken { get; }
|
string BotListToken { get; }
|
||||||
string TwitchClientId { get; }
|
|
||||||
string RedisOptions { get; }
|
string RedisOptions { get; }
|
||||||
string LocationIqApiKey { get; }
|
string LocationIqApiKey { get; }
|
||||||
string TimezoneDbApiKey { get; }
|
string TimezoneDbApiKey { get; }
|
||||||
string CoinmarketcapApiKey { get; }
|
string CoinmarketcapApiKey { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo move somewhere else
|
||||||
|
public static class IBotCredentialsExtensions
|
||||||
|
{
|
||||||
|
public static bool IsOwner(this IBotCredentials creds, IUser user)
|
||||||
|
=> creds.OwnerIds.Contains(user.Id);
|
||||||
|
}
|
||||||
|
|
||||||
public class RestartConfig
|
public class RestartConfig
|
||||||
{
|
{
|
||||||
public RestartConfig(string cmd, string args)
|
public RestartConfig(string cmd, string args)
|
||||||
@@ -43,15 +47,4 @@ namespace NadekoBot.Services
|
|||||||
public string Cmd { get; }
|
public string Cmd { get; }
|
||||||
public string Args { get; }
|
public string Args { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DBConfig
|
|
||||||
{
|
|
||||||
public DBConfig(string type, string connectionString)
|
|
||||||
{
|
|
||||||
this.Type = type;
|
|
||||||
this.ConnectionString = connectionString;
|
|
||||||
}
|
|
||||||
public string Type { get; }
|
|
||||||
public string ConnectionString { get; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,206 +1,119 @@
|
|||||||
using Discord;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using NadekoBot.Common;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using Nadeko.Common;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace NadekoBot.Services
|
namespace NadekoBot.Services
|
||||||
{
|
{
|
||||||
public class BotCredentials : IBotCredentials
|
public static class BotCredentialsProvider
|
||||||
{
|
{
|
||||||
public string GoogleApiKey { get; }
|
private const string _credsFileName = "creds.yml";
|
||||||
public string MashapeKey { get; }
|
private static string _oldCredsJsonFilename = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
|
||||||
public string Token { get; }
|
|
||||||
|
|
||||||
public ImmutableArray<ulong> OwnerIds { get; }
|
public static Creds CreateBotCredentials()
|
||||||
|
|
||||||
public string OsuApiKey { get; }
|
|
||||||
public string CleverbotApiKey { get; }
|
|
||||||
public RestartConfig RestartCommand { get; }
|
|
||||||
public DBConfig Db { get; }
|
|
||||||
public int TotalShards { get; }
|
|
||||||
public string CarbonKey { get; }
|
|
||||||
|
|
||||||
private readonly string _credsFileName = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
|
|
||||||
public string PatreonAccessToken { get; }
|
|
||||||
public string ShardRunCommand { get; }
|
|
||||||
public string ShardRunArguments { get; }
|
|
||||||
public int ShardRunPort { get; }
|
|
||||||
|
|
||||||
public string PatreonCampaignId { get; }
|
|
||||||
|
|
||||||
public string TwitchClientId { get; }
|
|
||||||
|
|
||||||
public string VotesUrl { get; }
|
|
||||||
public string VotesToken { get; }
|
|
||||||
public string BotListToken { get; }
|
|
||||||
public string RedisOptions { get; }
|
|
||||||
public string LocationIqApiKey { get; }
|
|
||||||
public string TimezoneDbApiKey { get; }
|
|
||||||
public string CoinmarketcapApiKey { get; }
|
|
||||||
|
|
||||||
public BotCredentials()
|
|
||||||
{
|
{
|
||||||
try
|
|
||||||
{
|
|
||||||
File.WriteAllText("./credentials_example.json",
|
|
||||||
JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(_credsFileName))
|
if (!File.Exists(_credsFileName))
|
||||||
Log.Warning(
|
Log.Warning($"{_credsFileName} is missing. " +
|
||||||
$"credentials.json is missing. Attempting to load creds from environment variables prefixed with 'NadekoBot_'. Example is in {Path.GetFullPath("./credentials_example.json")}");
|
$"Attempting to load creds from environment variables prefixed with 'NadekoBot_'. " +
|
||||||
try
|
$"Example is in {Path.GetFullPath("./creds-example.yml")}");
|
||||||
{
|
|
||||||
var configBuilder = new ConfigurationBuilder();
|
|
||||||
configBuilder.AddJsonFile(_credsFileName, true)
|
|
||||||
.AddEnvironmentVariables("NadekoBot_");
|
|
||||||
|
|
||||||
var data = configBuilder.Build();
|
|
||||||
|
|
||||||
Token = data[nameof(Token)];
|
IConfigurationBuilder configBuilder = new ConfigurationBuilder();
|
||||||
if (string.IsNullOrWhiteSpace(Token))
|
var creds = configBuilder
|
||||||
{
|
.AddYamlFile(_credsFileName, false, true)
|
||||||
Log.Error(
|
.AddEnvironmentVariables("NadekoBot_")
|
||||||
"Token is missing from credentials.json or Environment variables. Add it and restart the program.");
|
.Build()
|
||||||
Helpers.ReadErrorAndExit(5);
|
.Get<Creds>();
|
||||||
}
|
|
||||||
|
|
||||||
OwnerIds = data.GetSection("OwnerIds").GetChildren().Select(c => ulong.Parse(c.Value))
|
// if(string.IsNullOrWhiteSpace(creds.RedisOptions))
|
||||||
.ToImmutableArray();
|
// creds.RedisOptions = ""
|
||||||
GoogleApiKey = data[nameof(GoogleApiKey)];
|
|
||||||
MashapeKey = data[nameof(MashapeKey)];
|
|
||||||
OsuApiKey = data[nameof(OsuApiKey)];
|
|
||||||
PatreonAccessToken = data[nameof(PatreonAccessToken)];
|
|
||||||
PatreonCampaignId = data[nameof(PatreonCampaignId)] ?? "334038";
|
|
||||||
ShardRunCommand = data[nameof(ShardRunCommand)];
|
|
||||||
ShardRunArguments = data[nameof(ShardRunArguments)];
|
|
||||||
CleverbotApiKey = data[nameof(CleverbotApiKey)];
|
|
||||||
LocationIqApiKey = data[nameof(LocationIqApiKey)];
|
|
||||||
TimezoneDbApiKey = data[nameof(TimezoneDbApiKey)];
|
|
||||||
CoinmarketcapApiKey = data[nameof(CoinmarketcapApiKey)];
|
|
||||||
if (string.IsNullOrWhiteSpace(CoinmarketcapApiKey))
|
|
||||||
{
|
|
||||||
CoinmarketcapApiKey = "e79ec505-0913-439d-ae07-069e296a6079";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(data[nameof(RedisOptions)]))
|
return creds;
|
||||||
RedisOptions = data[nameof(RedisOptions)];
|
|
||||||
else
|
|
||||||
RedisOptions = "127.0.0.1,syncTimeout=3000";
|
|
||||||
|
|
||||||
VotesToken = data[nameof(VotesToken)];
|
// try
|
||||||
VotesUrl = data[nameof(VotesUrl)];
|
// {
|
||||||
BotListToken = data[nameof(BotListToken)];
|
//
|
||||||
|
//
|
||||||
var restartSection = data.GetSection(nameof(RestartCommand));
|
// var data = configBuilder.Build();
|
||||||
var cmd = restartSection["cmd"];
|
//
|
||||||
var args = restartSection["args"];
|
// Token = data[nameof(Token)];
|
||||||
if (!string.IsNullOrWhiteSpace(cmd))
|
// if (string.IsNullOrWhiteSpace(Token))
|
||||||
RestartCommand = new RestartConfig(cmd, args);
|
// {
|
||||||
|
// Log.Error("Token is missing from credentials.json or Environment variables. Add it and restart the program.");
|
||||||
if (Environment.OSVersion.Platform == PlatformID.Unix)
|
// Helpers.ReadErrorAndExit(5);
|
||||||
{
|
// }
|
||||||
if (string.IsNullOrWhiteSpace(ShardRunCommand))
|
//
|
||||||
ShardRunCommand = "dotnet";
|
// OwnerIds = data.GetSection("OwnerIds").GetChildren().Select(c => ulong.Parse(c.Value))
|
||||||
if (string.IsNullOrWhiteSpace(ShardRunArguments))
|
// .ToImmutableArray();
|
||||||
ShardRunArguments = "run -c Release --no-build -- {0} {1}";
|
// GoogleApiKey = data[nameof(GoogleApiKey)];
|
||||||
}
|
// MashapeKey = data[nameof(MashapeKey)];
|
||||||
else //windows
|
// OsuApiKey = data[nameof(OsuApiKey)];
|
||||||
{
|
// PatreonAccessToken = data[nameof(PatreonAccessToken)];
|
||||||
if (string.IsNullOrWhiteSpace(ShardRunCommand))
|
// PatreonCampaignId = data[nameof(PatreonCampaignId)] ?? "334038";
|
||||||
ShardRunCommand = "NadekoBot.exe";
|
// ShardRunCommand = data[nameof(ShardRunCommand)];
|
||||||
if (string.IsNullOrWhiteSpace(ShardRunArguments))
|
// ShardRunArguments = data[nameof(ShardRunArguments)];
|
||||||
ShardRunArguments = "{0} {1}";
|
// CleverbotApiKey = data[nameof(CleverbotApiKey)];
|
||||||
}
|
// LocationIqApiKey = data[nameof(LocationIqApiKey)];
|
||||||
|
// TimezoneDbApiKey = data[nameof(TimezoneDbApiKey)];
|
||||||
var portStr = data[nameof(ShardRunPort)];
|
// CoinmarketcapApiKey = data[nameof(CoinmarketcapApiKey)];
|
||||||
if (string.IsNullOrWhiteSpace(portStr))
|
// if (string.IsNullOrWhiteSpace(CoinmarketcapApiKey))
|
||||||
ShardRunPort = new NadekoRandom().Next(5000, 6000);
|
// {
|
||||||
else
|
// CoinmarketcapApiKey = "e79ec505-0913-439d-ae07-069e296a6079";
|
||||||
ShardRunPort = int.Parse(portStr);
|
// }
|
||||||
|
//
|
||||||
if (!int.TryParse(data[nameof(TotalShards)], out var ts))
|
// if (!string.IsNullOrWhiteSpace(data[nameof(RedisOptions)]))
|
||||||
ts = 0;
|
// RedisOptions = data[nameof(RedisOptions)];
|
||||||
TotalShards = ts < 1 ? 1 : ts;
|
// else
|
||||||
|
// RedisOptions = "127.0.0.1,syncTimeout=3000";
|
||||||
CarbonKey = data[nameof(CarbonKey)];
|
//
|
||||||
var dbSection = data.GetSection("db");
|
// VotesToken = data[nameof(VotesToken)];
|
||||||
Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"])
|
// VotesUrl = data[nameof(VotesUrl)];
|
||||||
? "sqlite"
|
// BotListToken = data[nameof(BotListToken)];
|
||||||
: dbSection["Type"],
|
//
|
||||||
string.IsNullOrWhiteSpace(dbSection["ConnectionString"])
|
// var restartSection = data.GetSection(nameof(RestartCommand));
|
||||||
? "Data Source=data/NadekoBot.db"
|
// var cmd = restartSection["cmd"];
|
||||||
: dbSection["ConnectionString"]);
|
// var args = restartSection["args"];
|
||||||
|
// if (!string.IsNullOrWhiteSpace(cmd))
|
||||||
TwitchClientId = data[nameof(TwitchClientId)];
|
// RestartCommand = new RestartConfig(cmd, args);
|
||||||
if (string.IsNullOrWhiteSpace(TwitchClientId))
|
//
|
||||||
{
|
// if (Environment.OSVersion.Platform == PlatformID.Unix)
|
||||||
TwitchClientId = "67w6z9i09xv2uoojdm9l0wsyph4hxo6";
|
// {
|
||||||
}
|
// if (string.IsNullOrWhiteSpace(ShardRunCommand))
|
||||||
}
|
// ShardRunCommand = "dotnet";
|
||||||
catch (Exception ex)
|
// if (string.IsNullOrWhiteSpace(ShardRunArguments))
|
||||||
{
|
// ShardRunArguments = "run -c Release --no-build -- {0} {1}";
|
||||||
Log.Error("JSON serialization has failed. Fix your credentials file and restart the bot.");
|
// }
|
||||||
Log.Fatal(ex.ToString());
|
// else //windows
|
||||||
Helpers.ReadErrorAndExit(6);
|
// {
|
||||||
}
|
// if (string.IsNullOrWhiteSpace(ShardRunCommand))
|
||||||
|
// ShardRunCommand = "NadekoBot.exe";
|
||||||
|
// if (string.IsNullOrWhiteSpace(ShardRunArguments))
|
||||||
|
// ShardRunArguments = "{0} {1}";
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!int.TryParse(data[nameof(TotalShards)], out var ts))
|
||||||
|
// ts = 0;
|
||||||
|
// TotalShards = ts < 1 ? 1 : ts;
|
||||||
|
//
|
||||||
|
// CarbonKey = data[nameof(CarbonKey)];
|
||||||
|
// var dbSection = data.GetSection("db");
|
||||||
|
// Db = new DBConfig(@"sqlite",
|
||||||
|
// string.IsNullOrWhiteSpace(dbSection["ConnectionString"])
|
||||||
|
// ? "Data Source=data/NadekoBot.db"
|
||||||
|
// : dbSection["ConnectionString"]);
|
||||||
|
//
|
||||||
|
// TwitchClientId = data[nameof(TwitchClientId)];
|
||||||
|
// if (string.IsNullOrWhiteSpace(TwitchClientId))
|
||||||
|
// {
|
||||||
|
// TwitchClientId = "67w6z9i09xv2uoojdm9l0wsyph4hxo6";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// Log.Error("JSON serialization has failed. Fix your credentials file and restart the bot.");
|
||||||
|
// Log.Fatal(ex.ToString());
|
||||||
|
// Helpers.ReadErrorAndExit(6);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// No idea why this thing exists
|
|
||||||
/// </summary>
|
|
||||||
private class CredentialsModel : IBotCredentials
|
|
||||||
{
|
|
||||||
public string Token { get; set; } = "";
|
|
||||||
|
|
||||||
public ulong[] OwnerIds { get; set; } = new ulong[]
|
|
||||||
{
|
|
||||||
105635576866156544
|
|
||||||
};
|
|
||||||
|
|
||||||
public string GoogleApiKey { get; set; } = "";
|
|
||||||
public string MashapeKey { get; set; } = "";
|
|
||||||
public string OsuApiKey { get; set; } = "";
|
|
||||||
public string SoundCloudClientId { get; set; } = "";
|
|
||||||
public string CleverbotApiKey { get; } = "";
|
|
||||||
public string CarbonKey { get; set; } = "";
|
|
||||||
public DBConfig Db { get; set; } = new DBConfig("sqlite", "Data Source=data/NadekoBot.db");
|
|
||||||
public int TotalShards { get; set; } = 1;
|
|
||||||
public string PatreonAccessToken { get; set; } = "";
|
|
||||||
public string PatreonCampaignId { get; set; } = "334038";
|
|
||||||
public string RestartCommand { get; set; } = null;
|
|
||||||
|
|
||||||
public string ShardRunCommand { get; set; } = "";
|
|
||||||
public string ShardRunArguments { get; set; } = "";
|
|
||||||
public int? ShardRunPort { get; set; } = null;
|
|
||||||
|
|
||||||
public string BotListToken { get; set; }
|
|
||||||
public string TwitchClientId { get; set; }
|
|
||||||
public string VotesToken { get; set; }
|
|
||||||
public string VotesUrl { get; set; }
|
|
||||||
public string RedisOptions { get; set; }
|
|
||||||
public string LocationIqApiKey { get; set; }
|
|
||||||
public string TimezoneDbApiKey { get; set; }
|
|
||||||
public string CoinmarketcapApiKey { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore] ImmutableArray<ulong> IBotCredentials.OwnerIds => throw new NotImplementedException();
|
|
||||||
|
|
||||||
[JsonIgnore] RestartConfig IBotCredentials.RestartCommand => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public bool IsOwner(IUser u)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsOwner(IUser u) => OwnerIds.Contains(u.Id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user