mirror of
https://github.com/NoiTheCat/WorldTime.git
synced 2024-11-21 06:34:36 +00:00
commit
71b8b4a274
11 changed files with 120 additions and 85 deletions
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "8.0.4",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,13 +13,13 @@ tab_width = 4
|
|||
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
insert_final_newline = true
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# Organize usings
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
dotnet_sort_system_directives_first = true
|
||||
file_header_template = unset
|
||||
|
||||
# this. and Me. preferences
|
||||
|
@ -63,7 +63,7 @@ dotnet_style_prefer_simplified_interpolation = true
|
|||
dotnet_style_readonly_field = true
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all
|
||||
dotnet_code_quality_unused_parameters = true
|
||||
|
||||
# Suppression preferences
|
||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
|
@ -75,59 +75,58 @@ dotnet_style_allow_statement_immediately_after_block_experimental = true
|
|||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false
|
||||
csharp_style_var_for_built_in_types = false
|
||||
csharp_style_var_when_type_is_apparent = false
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
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
|
||||
csharp_style_expression_bodied_constructors = true
|
||||
csharp_style_expression_bodied_indexers = true
|
||||
csharp_style_expression_bodied_lambdas = true
|
||||
csharp_style_expression_bodied_local_functions = true
|
||||
csharp_style_expression_bodied_methods = true
|
||||
csharp_style_expression_bodied_operators = true
|
||||
csharp_style_expression_bodied_properties = true
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_constructors = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = true:silent
|
||||
csharp_style_expression_bodied_methods = true:silent
|
||||
csharp_style_expression_bodied_operators = true:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
csharp_style_prefer_not_pattern = true
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_prefer_pattern_matching = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true
|
||||
csharp_style_prefer_switch_expression = true:suggestion
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# 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
|
||||
csharp_prefer_static_local_function = true:suggestion
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = when_multiline
|
||||
csharp_prefer_simple_using_statement = true
|
||||
csharp_style_namespace_declarations = file_scoped
|
||||
csharp_prefer_braces = when_multiline:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
|
||||
# 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
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true
|
||||
csharp_style_prefer_index_operator = true
|
||||
csharp_style_prefer_null_check_over_type_check = true
|
||||
csharp_style_prefer_range_operator = true
|
||||
csharp_style_throw_expression = true
|
||||
csharp_style_unused_value_assignment_preference = discard_variable
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
csharp_style_prefer_index_operator = true:suggestion
|
||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||
csharp_style_prefer_range_operator = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
|
||||
# New line preferences
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
|
@ -217,3 +216,8 @@ 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
|
||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_prefer_tuple_swap = true:suggestion
|
||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -10,8 +10,8 @@
|
|||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/bin/Debug/net6.0/WorldTime.dll",
|
||||
"args": [ "-c", "${workspaceFolder}/bin/Debug/net6.0/settings.json" ],
|
||||
"program": "${workspaceFolder}/bin/Debug/net8.0/WorldTime.dll",
|
||||
"args": [ "-c", "${workspaceFolder}/bin/Debug/settings.json" ],
|
||||
"cwd": "${workspaceFolder}",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
|
|
|
@ -7,10 +7,9 @@ using WorldTime.Data;
|
|||
|
||||
namespace WorldTime.Commands;
|
||||
public class CommandsBase : InteractionModuleBase<ShardedInteractionContext> {
|
||||
protected const string ErrInvalidZone = ":x: Not a valid zone name."
|
||||
+ " To find your time zone, refer to: <https://kevinnovak.github.io/Time-Zone-Picker/>.";
|
||||
protected const string ErrNoUserCache = ":warning: Please try the command again.";
|
||||
protected const string ErrNotAllowed = ":x: Only server moderators may use this command.";
|
||||
protected const string ErrInvalidZone =
|
||||
":x: Not a valid zone name. To find your zone, you may refer to a site such as <https://zones.arilyn.cc/>.";
|
||||
protected const string ErrNoUserCache = ":warning: Oops, bot wasn't ready. Please try again in a moment.";
|
||||
|
||||
private static readonly ReadOnlyDictionary<string, string> _tzNameMap;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ using Discord.Interactions;
|
|||
namespace WorldTime.Commands;
|
||||
[Group("config", "Configuration commands for World Time.")]
|
||||
[DefaultMemberPermissions(GuildPermission.ManageGuild)]
|
||||
[EnabledInDm(false)]
|
||||
[CommandContextType(InteractionContextType.Guild)]
|
||||
public class ConfigCommands : CommandsBase {
|
||||
internal const string HelpUse12 = "Sets whether to use the 12-hour (AM/PM) format in time zone listings.";
|
||||
internal const string HelpSetFor = "Sets/updates time zone for a given user.";
|
||||
|
|
|
@ -3,17 +3,6 @@ using System.Text;
|
|||
|
||||
namespace WorldTime.Commands;
|
||||
public class UserCommands : CommandsBase {
|
||||
const string EmbedHelpField1 =
|
||||
$"`/help` - {HelpHelp}\n"
|
||||
+ $"`/list` - {HelpList}\n"
|
||||
+ $"`/set` - {HelpSet}\n"
|
||||
+ $"`/remove` - {HelpRemove}";
|
||||
const string EmbedHelpField2 =
|
||||
$"`/config use-12hour` - {ConfigCommands.HelpUse12}\n"
|
||||
+ $"`/config private-confirms` - {ConfigCommands.HelpPrivateConfirms}\n"
|
||||
+ $"`/set-for` - {ConfigCommands.HelpSetFor}\n"
|
||||
+ $"`/remove-for` - {ConfigCommands.HelpRemoveFor}";
|
||||
|
||||
#region Help strings
|
||||
const string HelpHelp = "Displays a list of available bot commands.";
|
||||
const string HelpList = "Shows the current time for all recently active known users.";
|
||||
|
@ -23,6 +12,7 @@ public class UserCommands : CommandsBase {
|
|||
#endregion
|
||||
|
||||
[SlashCommand("help", HelpHelp)]
|
||||
[CommandContextType(InteractionContextType.Guild, InteractionContextType.BotDm)]
|
||||
public async Task CmdHelp() {
|
||||
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version!.ToString(3);
|
||||
var guildct = ShardedClient.Guilds.Count;
|
||||
|
@ -30,7 +20,8 @@ public class UserCommands : CommandsBase {
|
|||
var uniquetz = db.GetDistinctZoneCount();
|
||||
await RespondAsync(embed: new EmbedBuilder() {
|
||||
Title = "Help & About",
|
||||
Description = $"World Time v{version} - Serving {guildct} communities across {uniquetz} time zones.\n\n"
|
||||
Description =
|
||||
$"World Time v{version} - Serving {guildct} communities across {uniquetz} time zones.\n\n"
|
||||
+ "This bot is provided for free, without any paywalled 'premium' features. "
|
||||
+ "If you've found this bot useful, please consider contributing via the "
|
||||
+ "bot author's page on Ko-fi: https://ko-fi.com/noithecat.",
|
||||
|
@ -38,16 +29,28 @@ public class UserCommands : CommandsBase {
|
|||
IconUrl = Context.Client.CurrentUser.GetAvatarUrl(),
|
||||
Text = "World Time"
|
||||
}
|
||||
}.AddField(inline: false, name: "Commands", value: EmbedHelpField1
|
||||
).AddField(inline: false, name: "Admin commands", value: EmbedHelpField2
|
||||
}.AddField(inline: false, name: "Commands", value:
|
||||
$"""
|
||||
`/help` - {HelpHelp}
|
||||
`/list` - {HelpList}
|
||||
`/set` - {HelpSet}
|
||||
`/remove` - {HelpRemove}
|
||||
"""
|
||||
).AddField(inline: false, name: "Admin commands", value:
|
||||
$"""
|
||||
`/config use-12hour` - {ConfigCommands.HelpUse12}
|
||||
`/config private-confirms` - {ConfigCommands.HelpPrivateConfirms}
|
||||
`/set-for` - {ConfigCommands.HelpSetFor}
|
||||
`/remove-for` - {ConfigCommands.HelpRemoveFor}
|
||||
"""
|
||||
).AddField(inline: false, name: "Zones", value:
|
||||
"This bot accepts zone names from the IANA Time Zone Database (a.k.a. Olson Database). " +
|
||||
"A useful tool to determine yours can be found at: https://kevinnovak.github.io/Time-Zone-Picker/"
|
||||
"A useful tool to determine yours can be found at: https://zones.arilyn.cc/"
|
||||
).Build());
|
||||
}
|
||||
|
||||
[SlashCommand("list", HelpList)]
|
||||
[EnabledInDm(false)]
|
||||
[CommandContextType(InteractionContextType.Guild)]
|
||||
public async Task CmdList([Summary(description: "A specific user whose time to look up.")]SocketGuildUser? user = null) {
|
||||
if (!await AreUsersDownloadedAsync(Context.Guild)) {
|
||||
await RespondAsync(ErrNoUserCache, ephemeral: true);
|
||||
|
@ -76,9 +79,9 @@ public class UserCommands : CommandsBase {
|
|||
// Generate time and zone names to be displayed, group with associated user IDs
|
||||
var sortedlist = new SortedDictionary<string, List<ulong>>();
|
||||
var ampm = db.GuildSettings.Where(s => s.GuildId == Context.Guild.Id).SingleOrDefault()?.Use12HourTime ?? false;
|
||||
foreach ((string area, List<ulong> users) in userlist.OrderByDescending(o => o.Value.Count)) {
|
||||
foreach ((var area, List<ulong> users) in userlist.OrderByDescending(o => o.Value.Count)) {
|
||||
var areaprint = TzPrint(area, ampm);
|
||||
if (!sortedlist.ContainsKey(areaprint)) sortedlist.Add(areaprint, new List<ulong>());
|
||||
if (!sortedlist.ContainsKey(areaprint)) sortedlist[areaprint] = [];
|
||||
sortedlist[areaprint].AddRange(users);
|
||||
}
|
||||
|
||||
|
@ -87,10 +90,10 @@ public class UserCommands : CommandsBase {
|
|||
|
||||
// Build zone listings with users
|
||||
var outputlines = new List<string>();
|
||||
foreach ((string area, List<ulong> users) in sortedlist) {
|
||||
foreach ((var area, List<ulong> users) in sortedlist) {
|
||||
var buffer = new StringBuilder();
|
||||
buffer.Append(area[6..] + ": ");
|
||||
bool empty = true;
|
||||
var empty = true;
|
||||
foreach (var userid in users) {
|
||||
var userinstance = Context.Guild.GetUser(userid);
|
||||
if (userinstance == null) continue;
|
||||
|
@ -107,7 +110,7 @@ public class UserCommands : CommandsBase {
|
|||
|
||||
// Prepare for output - send buffers out if they become too large
|
||||
outputlines.Sort();
|
||||
bool hasOutputOneLine = false;
|
||||
var hasOutputOneLine = false;
|
||||
// First output is shown as an interaction response, followed then as regular channel messages
|
||||
async Task doOutput(Embed msg) {
|
||||
if (!hasOutputOneLine) {
|
||||
|
@ -137,7 +140,7 @@ public class UserCommands : CommandsBase {
|
|||
using var db = DbContext;
|
||||
var result = db.GetUserZone(parameter);
|
||||
if (result == null) {
|
||||
bool isself = Context.User.Id == parameter.Id;
|
||||
var isself = Context.User.Id == parameter.Id;
|
||||
if (isself) await RespondAsync(":x: You do not have a time zone. Set it with `tz.set`.", ephemeral: true);
|
||||
else await RespondAsync(":x: The given user does not have a time zone set.", ephemeral: true);
|
||||
return;
|
||||
|
@ -149,7 +152,7 @@ public class UserCommands : CommandsBase {
|
|||
}
|
||||
|
||||
[SlashCommand("set", HelpSet)]
|
||||
[EnabledInDm(false)]
|
||||
[CommandContextType(InteractionContextType.Guild)]
|
||||
public async Task CmdSet([Summary(description: "The new time zone to set.")]string zone) {
|
||||
var parsedzone = ParseTimeZone(zone);
|
||||
if (parsedzone == null) {
|
||||
|
@ -164,7 +167,7 @@ public class UserCommands : CommandsBase {
|
|||
}
|
||||
|
||||
[SlashCommand("remove", HelpRemove)]
|
||||
[EnabledInDm(false)]
|
||||
[CommandContextType(InteractionContextType.Guild)]
|
||||
public async Task CmdRemove() {
|
||||
using var db = DbContext;
|
||||
var success = db.DeleteUser((SocketGuildUser)Context.User);
|
||||
|
|
|
@ -102,7 +102,7 @@ public class BotDatabaseContext : DbContext {
|
|||
select Tuple.Create(entry.TimeZone, (ulong)entry.UserId);
|
||||
var resultSet = new Dictionary<string, List<ulong>>();
|
||||
foreach (var (tz, user) in query) {
|
||||
if (!resultSet.ContainsKey(tz)) resultSet.Add(tz, new List<ulong>());
|
||||
if (!resultSet.ContainsKey(tz)) resultSet[tz] = [];
|
||||
resultSet[tz].Add(user);
|
||||
}
|
||||
return resultSet;
|
||||
|
|
4
Data/Migrations/.editorconfig
Normal file
4
Data/Migrations/.editorconfig
Normal file
|
@ -0,0 +1,4 @@
|
|||
[*.cs]
|
||||
generated_code = true
|
||||
dotnet_analyzer_diagnostic.category-CodeQuality.severity = none
|
||||
dotnet_diagnostic.CS1591.severity = none
|
|
@ -9,9 +9,8 @@ A social time zone reference tool!
|
|||
|
||||
#### Running your own instance
|
||||
You need:
|
||||
* .NET 6 (https://dotnet.microsoft.com/en-us/)
|
||||
* .NET 8 (https://dotnet.microsoft.com/en-us/)
|
||||
* PostgreSQL (https://www.postgresql.org/)
|
||||
* EF Core tools (https://learn.microsoft.com/en-us/ef/core/get-started/overview/install#get-the-entity-framework-core-tools)
|
||||
* A Discord bot token (https://discord.com/developers/applications)
|
||||
|
||||
Get your bot token and set up your database user and schema, then create a JSON file containing the following:
|
||||
|
@ -28,10 +27,11 @@ Get your bot token and set up your database user and schema, then create a JSON
|
|||
Then run the following commands:
|
||||
```sh
|
||||
$ dotnet restore
|
||||
$ dotnet tool restore
|
||||
$ dotnet ef database update -- -c path/to/config.json
|
||||
```
|
||||
|
||||
And finally, to run the bot:
|
||||
```
|
||||
```sh
|
||||
$ dotnet run -c Release -- -c path/to/config.json
|
||||
```
|
|
@ -104,7 +104,7 @@ internal class WorldTime : IDisposable {
|
|||
// Discord Bots
|
||||
if (!string.IsNullOrEmpty(Config.DBotsToken)) {
|
||||
try {
|
||||
string dBotsApiUrl = $"https://discord.bots.gg/api/v1/bots/{ botId }/stats";
|
||||
var dBotsApiUrl = $"https://discord.bots.gg/api/v1/bots/{ botId }/stats";
|
||||
var body = $"{{ \"guildCount\": {guildCount} }}";
|
||||
var uri = new Uri(string.Format(dBotsApiUrl));
|
||||
|
||||
|
|
|
@ -1,28 +1,41 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>2.3.3</Version>
|
||||
<Authors>NoiTheCat</Authors>
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>2.3.2</Version>
|
||||
<Authors>NoiTheCat</Authors>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<WarningLevel>0</WarningLevel>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="Discord.Net" Version="3.12.0" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="7.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
|
||||
<PackageReference Include="Discord.Net" Version="3.14.1" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.9" />
|
||||
<PackageReference Include="Npgsql" Version="7.0.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.11" />
|
||||
<PackageReference Include="Npgsql" Version="8.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
Loading…
Reference in a new issue