5.1.6
This commit is contained in:
commit
3244bad7da
149 changed files with 398 additions and 1351 deletions
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -2,6 +2,33 @@
|
||||||
|
|
||||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
|
## [5.1.6] - 08.08.2024
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `'serverlist` is now paginated
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `'listservers` renamed to `'serverlist`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `'afk` messages can no longer ping, and the response is moved to DMs to avoid abuse
|
||||||
|
- Possible fix for `'remind` timestamp
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed old bloat / semi broken / dumb commands
|
||||||
|
- `'memelist` / `'memegen` (too inconvenient to use)
|
||||||
|
- `'activity` (useless owner-only command)
|
||||||
|
- `'rafflecur` (Just use raffle and then award manually instead)
|
||||||
|
- `'rollduel` (we had this command?)
|
||||||
|
- You can no longer bet on `'connect4`
|
||||||
|
- `'economy` Removed.
|
||||||
|
- Was buggy and didn't really show the real state of the economy.
|
||||||
|
- It might come back improved in the future
|
||||||
|
- `'mal` Removed. Useless information / semi broken
|
||||||
|
|
||||||
## [5.1.5] - 01.08.2024
|
## [5.1.5] - 01.08.2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -25,6 +52,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
|
||||||
- You can once again disable cleverbot responses using fake 'cleverbot:response' module name in permission commands
|
- You can once again disable cleverbot responses using fake 'cleverbot:response' module name in permission commands
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed 'rip command
|
- Removed 'rip command
|
||||||
|
|
||||||
## [5.1.4] - 15.07.2024
|
## [5.1.4] - 15.07.2024
|
||||||
|
|
23
Dockerfile
23
Dockerfile
|
@ -1,16 +1,24 @@
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
# Use the .NET 8.0 SDK as the base image for the build stage
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
WORKDIR /source
|
WORKDIR /source
|
||||||
|
|
||||||
|
# Copy the .csproj files for each project
|
||||||
COPY src/Ellie.Marmalade/*.csproj src/Ellie.Marmalade/
|
COPY src/Ellie.Marmalade/*.csproj src/Ellie.Marmalade/
|
||||||
COPY src/EllieBot/*.csproj src/EllieBot/
|
COPY src/EllieBot/*.csproj src/EllieBot/
|
||||||
COPY src/EllieBot.Coordinator/*.csproj src/EllieBot.Coordinator/
|
COPY src/EllieBot.Coordinator/*.csproj src/EllieBot.Coordinator/
|
||||||
COPY src/EllieBot.Generators/*.csproj src/EllieBot.Generators/
|
COPY src/EllieBot.Generators/*.csproj src/EllieBot.Generators/
|
||||||
COPY src/EllieBot.Voice/*.csproj src/EllieBot.Voice/
|
COPY src/EllieBot.Voice/*.csproj src/EllieBot.Voice/
|
||||||
COPY NuGet.Config ./
|
|
||||||
|
# Restore the dependencies for the EllieBot project
|
||||||
RUN dotnet restore src/EllieBot/
|
RUN dotnet restore src/EllieBot/
|
||||||
|
|
||||||
|
# Copy the rest of the source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Set the working directory to the EllieBot project
|
||||||
WORKDIR /source/src/EllieBot
|
WORKDIR /source/src/EllieBot
|
||||||
|
|
||||||
|
# Build and publish the EllieBot project, then clean up unnecessary files
|
||||||
RUN set -xe; \
|
RUN set -xe; \
|
||||||
dotnet --version; \
|
dotnet --version; \
|
||||||
dotnet publish -c Release -o /app --no-restore; \
|
dotnet publish -c Release -o /app --no-restore; \
|
||||||
|
@ -19,28 +27,33 @@ RUN set -xe; \
|
||||||
find /app -type f -exec chmod -x {} \; ;\
|
find /app -type f -exec chmod -x {} \; ;\
|
||||||
chmod +x /app/EllieBot
|
chmod +x /app/EllieBot
|
||||||
|
|
||||||
# final stage/image
|
# Use the .NET 8.0 runtime as the base image for the final stage
|
||||||
FROM mcr.microsoft.com/dotnet/runtime:6.0
|
FROM mcr.microsoft.com/dotnet/runtime:8.0
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Create a new user, install dependencies, and set up sudoers file
|
||||||
RUN set -xe; \
|
RUN set -xe; \
|
||||||
useradd -m ellie; \
|
useradd -m ellie; \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
apt-get install -y --no-install-recommends libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 sudo; \
|
apt-get install -y --no-install-recommends libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 sudo; \
|
||||||
update-alternatives --install /usr/bin/python python /usr/bin/python3.9 1; \
|
|
||||||
echo 'Defaults>ellie env_keep+="ASPNETCORE_* DOTNET_* EllieBot_* shard_id total_shards TZ"' > /etc/sudoers.d/ellie; \
|
echo 'Defaults>ellie env_keep+="ASPNETCORE_* DOTNET_* EllieBot_* shard_id total_shards TZ"' > /etc/sudoers.d/ellie; \
|
||||||
curl -Lo /usr/local/bin/yt-dlp https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp; \
|
curl -Lo /usr/local/bin/yt-dlp https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp; \
|
||||||
chmod a+rx /usr/local/bin/yt-dlp; \
|
chmod a+rx /usr/local/bin/yt-dlp; \
|
||||||
apt-get autoremove -y; \
|
apt-get autoremove -y; \
|
||||||
apt-get autoclean -y
|
apt-get autoclean -y
|
||||||
|
|
||||||
|
# Copy the built application and the entrypoint script from the build stage
|
||||||
COPY --from=build /app ./
|
COPY --from=build /app ./
|
||||||
COPY docker-entrypoint.sh /usr/local/sbin
|
COPY docker-entrypoint.sh /usr/local/sbin
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
ENV shard_id=0
|
ENV shard_id=0
|
||||||
ENV total_shards=1
|
ENV total_shards=1
|
||||||
ENV EllieBot__creds=/app/data/creds.yml
|
ENV EllieBot__creds=/app/data/creds.yml
|
||||||
|
|
||||||
|
# Define the data directory as a volume
|
||||||
VOLUME [" /app/data "]
|
VOLUME [" /app/data "]
|
||||||
|
|
||||||
|
# Set the entrypoint and default command
|
||||||
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
|
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
|
||||||
CMD dotnet EllieBot.dll "$shard_id" "$total_shards"
|
CMD dotnet EllieBot.dll "$shard_id" "$total_shards"
|
||||||
|
|
|
@ -13,7 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
migrate.ps1 = migrate.ps1
|
migrate.ps1 = migrate.ps1
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
remove-migrations.ps1 = remove-migrations.ps1
|
remove-migrations.ps1 = remove-migrations.ps1
|
||||||
TODO.md = TODO.md
|
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot", "src\EllieBot\EllieBot.csproj", "{4D9001F7-B3E8-48FE-97AA-CFD36DA65A64}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot", "src\EllieBot\EllieBot.csproj", "{4D9001F7-B3E8-48FE-97AA-CFD36DA65A64}"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using Discord;
|
using Discord;
|
||||||
using EllieBot;
|
|
||||||
|
|
||||||
namespace EllieBot.Marmalade;
|
namespace EllieBot.Marmalade;
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ public sealed class Bot : IBot
|
||||||
|
|
||||||
|
|
||||||
public IReadOnlyList<ulong> GetCurrentGuildIds()
|
public IReadOnlyList<ulong> GetCurrentGuildIds()
|
||||||
=> Client.Guilds.Select(x => x.Id).ToList();
|
=> Client.Guilds.Select(x => x.Id).ToList().ToList();
|
||||||
|
|
||||||
private void AddServices()
|
private void AddServices()
|
||||||
{
|
{
|
||||||
|
@ -114,7 +114,7 @@ public sealed class Bot : IBot
|
||||||
// svcs.Components.Remove<IPlanner, Planner>();
|
// svcs.Components.Remove<IPlanner, Planner>();
|
||||||
// svcs.Components.Add<IPlanner, RemovablePlanner>();
|
// svcs.Components.Add<IPlanner, RemovablePlanner>();
|
||||||
|
|
||||||
svcs.AddSingleton<IBotCredentials, IBotCredentials>(_ => _credsProvider.GetCreds());
|
svcs.AddSingleton<IBotCredentials>(_ => _credsProvider.GetCreds());
|
||||||
svcs.AddSingleton<DbService, DbService>(_db);
|
svcs.AddSingleton<DbService, DbService>(_db);
|
||||||
svcs.AddSingleton<IBotCredsProvider>(_credsProvider);
|
svcs.AddSingleton<IBotCredsProvider>(_credsProvider);
|
||||||
svcs.AddSingleton<DiscordSocketClient>(Client);
|
svcs.AddSingleton<DiscordSocketClient>(Client);
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
|
|
||||||
namespace EllieBot.Db.Models;
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.1.5</Version>
|
<Version>5.1.6</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using EllieBot.Db.Models;
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations;
|
namespace EllieBot.Migrations;
|
||||||
|
|
||||||
|
@ -50,5 +49,9 @@ left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;")
|
||||||
builder.Sql($"""
|
builder.Sql($"""
|
||||||
DELETE FROM "DelMsgOnCmdChannel" WHERE "GuildConfigId" is NULL;
|
DELETE FROM "DelMsgOnCmdChannel" WHERE "GuildConfigId" is NULL;
|
||||||
""");
|
""");
|
||||||
|
|
||||||
|
builder.Sql("""
|
||||||
|
DELETE FROM "WarningPunishment" WHERE "GuildConfigId" NOT IN (SELECT "Id" from "GuildConfigs");
|
||||||
|
""");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations
|
namespace EllieBot.Migrations
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations
|
namespace EllieBot.Migrations
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations
|
namespace EllieBot.Migrations
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations
|
namespace EllieBot.Migrations
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations
|
namespace EllieBot.Migrations
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Administration._common.results;
|
using EllieBot.Modules.Administration._common.results;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ using System.Net;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Administration.Services;
|
namespace EllieBot.Modules.Administration.Services;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Db;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Administration.Services;
|
namespace EllieBot.Modules.Administration.Services;
|
||||||
|
|
||||||
public class GameVoiceChannelService : IEService
|
public class GameVoiceChannelService : IEService
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Administration.Services;
|
namespace EllieBot.Modules.Administration.Services;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Patronage;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using OneOf;
|
using OneOf;
|
||||||
using OneOf.Types;
|
using OneOf.Types;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using OneOf.Types;
|
using OneOf.Types;
|
||||||
|
|
|
@ -3,7 +3,6 @@ using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Administration;
|
namespace EllieBot.Modules.Administration;
|
||||||
|
|
||||||
|
|
|
@ -546,7 +546,7 @@ public partial class Administration
|
||||||
text = await repSvc.ReplaceAsync(text, repCtx);
|
text = await repSvc.ReplaceAsync(text, repCtx);
|
||||||
await Response().Channel(ch).Text(text).SendAsync();
|
await Response().Channel(ch).Text(text).SendAsync();
|
||||||
|
|
||||||
await ctx.OkAsync();;
|
await ctx.OkAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Administration.Services;
|
namespace EllieBot.Modules.Administration.Services;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Administration.Services;
|
using EllieBot.Modules.Administration.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Common.TypeReaders.Models;
|
using EllieBot.Common.TypeReaders.Models;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Permissions.Services;
|
using EllieBot.Modules.Permissions.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Administration.Services;
|
namespace EllieBot.Modules.Administration.Services;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Common.Yml;
|
using EllieBot.Common.Yml;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
|
|
@ -3,7 +3,6 @@ using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Modules.Gambling.Common;
|
using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Blackjack;
|
using EllieBot.Modules.Gambling.Common.Blackjack;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
using EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
|
|
@ -38,11 +38,11 @@ public sealed class Connect4Game : IDisposable
|
||||||
|
|
||||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||||
|
|
||||||
public ImmutableArray<Field> GameState
|
public IReadOnlyList<Field> GameState
|
||||||
=> _gameState.ToImmutableArray();
|
=> _gameState.AsReadOnly();
|
||||||
|
|
||||||
public ImmutableArray<(ulong UserId, string Username)?> Players
|
public IReadOnlyCollection<(ulong UserId, string Username)?> Players
|
||||||
=> _players.ToImmutableArray();
|
=> _players.AsReadOnly();
|
||||||
|
|
||||||
public (ulong UserId, string Username) CurrentPlayer
|
public (ulong UserId, string Username) CurrentPlayer
|
||||||
=> CurrentPhase == Phase.P1Move ? _players[0].Value : _players[1].Value;
|
=> CurrentPhase == Phase.P1Move ? _players[0].Value : _players[1].Value;
|
||||||
|
@ -56,7 +56,6 @@ public sealed class Connect4Game : IDisposable
|
||||||
|
|
||||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||||
private readonly Options _options;
|
private readonly Options _options;
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
private readonly EllieRandom _rng;
|
private readonly EllieRandom _rng;
|
||||||
|
|
||||||
private Timer playerTimeoutTimer;
|
private Timer playerTimeoutTimer;
|
||||||
|
@ -73,12 +72,11 @@ public sealed class Connect4Game : IDisposable
|
||||||
public Connect4Game(
|
public Connect4Game(
|
||||||
ulong userId,
|
ulong userId,
|
||||||
string userName,
|
string userName,
|
||||||
Options options,
|
Options options
|
||||||
ICurrencyService cs)
|
)
|
||||||
{
|
{
|
||||||
_players[0] = (userId, userName);
|
_players[0] = (userId, userName);
|
||||||
_options = options;
|
_options = options;
|
||||||
_cs = cs;
|
|
||||||
|
|
||||||
_rng = new();
|
_rng = new();
|
||||||
for (var i = 0; i < NUMBER_OF_COLUMNS * NUMBER_OF_ROWS; i++)
|
for (var i = 0; i < NUMBER_OF_COLUMNS * NUMBER_OF_ROWS; i++)
|
||||||
|
@ -99,14 +97,13 @@ public sealed class Connect4Game : IDisposable
|
||||||
{
|
{
|
||||||
_ = OnGameFailedToStart?.Invoke(this);
|
_ = OnGameFailedToStart?.Invoke(this);
|
||||||
CurrentPhase = Phase.Ended;
|
CurrentPhase = Phase.Ended;
|
||||||
await _cs.AddAsync(_players[0].Value.UserId, _options.Bet, new("connect4", "refund"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally { _locker.Release(); }
|
finally { _locker.Release(); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> Join(ulong userId, string userName, int bet)
|
public async Task<bool> Join(ulong userId, string userName)
|
||||||
{
|
{
|
||||||
await _locker.WaitAsync();
|
await _locker.WaitAsync();
|
||||||
try
|
try
|
||||||
|
@ -117,11 +114,6 @@ public sealed class Connect4Game : IDisposable
|
||||||
if (_players[0].Value.UserId == userId) // same user can't join own game
|
if (_players[0].Value.UserId == userId) // same user can't join own game
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (bet != _options.Bet) // can't join if bet amount is not the same
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!await _cs.RemoveAsync(userId, bet, new("connect4", "bet"))) // user doesn't have enough money to gamble
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
|
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
|
||||||
{
|
{
|
||||||
|
@ -133,14 +125,14 @@ public sealed class Connect4Game : IDisposable
|
||||||
|
|
||||||
CurrentPhase = Phase.P1Move; //start the game
|
CurrentPhase = Phase.P1Move; //start the game
|
||||||
playerTimeoutTimer = new(async _ =>
|
playerTimeoutTimer = new(async _ =>
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await _locker.WaitAsync();
|
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
||||||
try
|
}
|
||||||
{
|
finally { _locker.Release(); }
|
||||||
EndGame(Result.OtherPlayerWon, OtherPlayer.UserId);
|
},
|
||||||
}
|
|
||||||
finally { _locker.Release(); }
|
|
||||||
},
|
|
||||||
null,
|
null,
|
||||||
TimeSpan.FromSeconds(_options.TurnTimer),
|
TimeSpan.FromSeconds(_options.TurnTimer),
|
||||||
TimeSpan.FromSeconds(_options.TurnTimer));
|
TimeSpan.FromSeconds(_options.TurnTimer));
|
||||||
|
@ -351,13 +343,8 @@ public sealed class Connect4Game : IDisposable
|
||||||
|
|
||||||
if (result == Result.Draw)
|
if (result == Result.Draw)
|
||||||
{
|
{
|
||||||
_cs.AddAsync(CurrentPlayer.UserId, _options.Bet, new("connect4", "draw"));
|
|
||||||
_cs.AddAsync(OtherPlayer.UserId, _options.Bet, new("connect4", "draw"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (winId is not null)
|
|
||||||
_cs.AddAsync(winId.Value, (long)(_options.Bet * 1.98), new("connect4", "win"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Field GetPlayerPiece(ulong userId)
|
private Field GetPlayerPiece(ulong userId)
|
||||||
|
@ -394,16 +381,10 @@ public sealed class Connect4Game : IDisposable
|
||||||
HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
|
HelpText = "Turn time in seconds. It has to be between 5 and 60. Default 15.")]
|
||||||
public int TurnTimer { get; set; } = 15;
|
public int TurnTimer { get; set; } = 15;
|
||||||
|
|
||||||
[Option('b', "bet", Required = false, Default = 0, HelpText = "Amount you bet. Default 0.")]
|
|
||||||
public int Bet { get; set; }
|
|
||||||
|
|
||||||
public void NormalizeOptions()
|
public void NormalizeOptions()
|
||||||
{
|
{
|
||||||
if (TurnTimer is < 5 or > 60)
|
if (TurnTimer is < 5 or > 60)
|
||||||
TurnTimer = 15;
|
TurnTimer = 15;
|
||||||
|
|
||||||
if (Bet < 0)
|
|
||||||
Bet = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -29,17 +29,15 @@ public partial class Gambling
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
|
|
||||||
private IUserMessage msg;
|
private IUserMessage msg;
|
||||||
|
|
||||||
private int repostCounter;
|
private int repostCounter;
|
||||||
|
|
||||||
public Connect4Commands(DiscordSocketClient client, ICurrencyService cs, GamblingConfigService gamb)
|
public Connect4Commands(DiscordSocketClient client, GamblingConfigService gamb)
|
||||||
: base(gamb)
|
: base(gamb)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_cs = cs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
@ -48,10 +46,8 @@ public partial class Gambling
|
||||||
public async Task Connect4(params string[] args)
|
public async Task Connect4(params string[] args)
|
||||||
{
|
{
|
||||||
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
|
var (options, _) = OptionsParser.ParseFrom(new Connect4Game.Options(), args);
|
||||||
if (!await CheckBetOptional(options.Bet))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options, _cs);
|
var newGame = new Connect4Game(ctx.User.Id, ctx.User.ToString(), options);
|
||||||
Connect4Game game;
|
Connect4Game game;
|
||||||
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
|
if ((game = _service.Connect4Games.GetOrAdd(ctx.Channel.Id, newGame)) != newGame)
|
||||||
{
|
{
|
||||||
|
@ -60,31 +56,17 @@ public partial class Gambling
|
||||||
|
|
||||||
newGame.Dispose();
|
newGame.Dispose();
|
||||||
//means game already exists, try to join
|
//means game already exists, try to join
|
||||||
await game.Join(ctx.User.Id, ctx.User.ToString(), options.Bet);
|
await game.Join(ctx.User.Id, ctx.User.ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.Bet > 0)
|
|
||||||
{
|
|
||||||
if (!await _cs.RemoveAsync(ctx.User.Id, options.Bet, new("connect4", "bet")))
|
|
||||||
{
|
|
||||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
|
||||||
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _);
|
|
||||||
game.Dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
game.OnGameStateUpdated += Game_OnGameStateUpdated;
|
game.OnGameStateUpdated += Game_OnGameStateUpdated;
|
||||||
game.OnGameFailedToStart += GameOnGameFailedToStart;
|
game.OnGameFailedToStart += GameOnGameFailedToStart;
|
||||||
game.OnGameEnded += GameOnGameEnded;
|
game.OnGameEnded += GameOnGameEnded;
|
||||||
_client.MessageReceived += ClientMessageReceived;
|
_client.MessageReceived += ClientMessageReceived;
|
||||||
|
|
||||||
game.Initialize();
|
game.Initialize();
|
||||||
if (options.Bet == 0)
|
await Response().Confirm(strs.connect4_created).SendAsync();
|
||||||
await Response().Confirm(strs.connect4_created).SendAsync();
|
|
||||||
else
|
|
||||||
await Response().Error(strs.connect4_created_bet(N(options.Bet))).SendAsync();
|
|
||||||
|
|
||||||
Task ClientMessageReceived(SocketMessage arg)
|
Task ClientMessageReceived(SocketMessage arg)
|
||||||
{
|
{
|
||||||
|
@ -99,7 +81,8 @@ public partial class Gambling
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
try { await arg.DeleteAsync(); }
|
try
|
||||||
|
{ await arg.DeleteAsync(); }
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -109,7 +92,8 @@ public partial class Gambling
|
||||||
RepostCounter++;
|
RepostCounter++;
|
||||||
if (RepostCounter == 0)
|
if (RepostCounter == 0)
|
||||||
{
|
{
|
||||||
try { msg = await Response().Embed(msg.Embeds.First().ToEmbedBuilder()).SendAsync(); }
|
try
|
||||||
|
{ msg = await Response().Embed(msg.Embeds.First().ToEmbedBuilder()).SendAsync(); }
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,19 +135,19 @@ public partial class Gambling
|
||||||
title = GetText(strs.connect4_draw);
|
title = GetText(strs.connect4_draw);
|
||||||
|
|
||||||
return msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
return msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(GetGameStateText(game))
|
.WithDescription(GetGameStateText(game))
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.Build());
|
.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Game_OnGameStateUpdated(Connect4Game game)
|
private async Task Game_OnGameStateUpdated(Connect4Game game)
|
||||||
{
|
{
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
|
||||||
.WithDescription(GetGameStateText(game))
|
.WithDescription(GetGameStateText(game))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
|
|
||||||
if (msg is null)
|
if (msg is null)
|
||||||
|
|
|
@ -13,7 +13,6 @@ using System.Text;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Common.TypeReaders;
|
using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
using EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
@ -31,8 +30,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
private readonly IPatronageService _ps;
|
private readonly IPatronageService _ps;
|
||||||
|
|
||||||
private IUserMessage rdMsg;
|
|
||||||
|
|
||||||
public Gambling(
|
public Gambling(
|
||||||
IGamblingService gs,
|
IGamblingService gs,
|
||||||
DbService db,
|
DbService db,
|
||||||
|
@ -105,34 +102,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
await Response().Embed(eb).SendAsync();
|
await Response().Embed(eb).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public async Task Economy()
|
|
||||||
{
|
|
||||||
var ec = await _service.GetEconomyAsync();
|
|
||||||
decimal onePercent = 0;
|
|
||||||
|
|
||||||
// This stops the top 1% from owning more than 100% of the money
|
|
||||||
if (ec.Cash > 0)
|
|
||||||
{
|
|
||||||
onePercent = ec.OnePercent / (ec.Cash - ec.Bot);
|
|
||||||
}
|
|
||||||
|
|
||||||
// [21:03] Bob Page: Kinda remids me of US economy
|
|
||||||
var embed = _sender.CreateEmbed()
|
|
||||||
.WithTitle(GetText(strs.economy_state))
|
|
||||||
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
|
|
||||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
|
||||||
.AddField(GetText(strs.currency_planted), N(ec.Planted))
|
|
||||||
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
|
|
||||||
.AddField(GetText(strs.bot_currency), N(ec.Bot))
|
|
||||||
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
|
|
||||||
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
|
|
||||||
.WithOkColor();
|
|
||||||
|
|
||||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
|
||||||
await Response().Embed(embed).SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
||||||
{
|
{
|
||||||
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
|
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
|
||||||
|
@ -602,117 +571,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task RollDuel(IUser u)
|
|
||||||
{
|
|
||||||
if (ctx.User.Id == u.Id)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//since the challenge is created by another user, we need to reverse the ids
|
|
||||||
//if it gets removed, means challenge is accepted
|
|
||||||
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game))
|
|
||||||
{
|
|
||||||
await game.StartGame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task RollDuel([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, IUser u)
|
|
||||||
{
|
|
||||||
if (ctx.User.Id == u.Id)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.roll_duel));
|
|
||||||
|
|
||||||
var description = string.Empty;
|
|
||||||
|
|
||||||
var game = new RollDuelGame(_cs, _client.CurrentUser.Id, ctx.User.Id, u.Id, amount);
|
|
||||||
//means challenge is just created
|
|
||||||
if (_service.Duels.TryGetValue((ctx.User.Id, u.Id), out var other))
|
|
||||||
{
|
|
||||||
if (other.Amount != amount)
|
|
||||||
{
|
|
||||||
await Response().Error(strs.roll_duel_already_challenged).SendAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await RollDuel(u);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_service.Duels.TryAdd((u.Id, ctx.User.Id), game))
|
|
||||||
{
|
|
||||||
game.OnGameTick += GameOnGameTick;
|
|
||||||
game.OnEnded += GameOnEnded;
|
|
||||||
|
|
||||||
await Response()
|
|
||||||
.Confirm(strs.roll_duel_challenge(Format.Bold(ctx.User.ToString()),
|
|
||||||
Format.Bold(u.ToString()),
|
|
||||||
Format.Bold(N(amount))))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task GameOnGameTick(RollDuelGame arg)
|
|
||||||
{
|
|
||||||
var rolls = arg.Rolls.Last();
|
|
||||||
description += $@"{Format.Bold(ctx.User.ToString())} rolled **{rolls.Item1}**
|
|
||||||
{Format.Bold(u.ToString())} rolled **{rolls.Item2}**
|
|
||||||
--
|
|
||||||
";
|
|
||||||
embed = embed.WithDescription(description);
|
|
||||||
|
|
||||||
if (rdMsg is null)
|
|
||||||
{
|
|
||||||
rdMsg = await Response().Embed(embed).SendAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await rdMsg.ModifyAsync(x => { x.Embed = embed.Build(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task GameOnEnded(RollDuelGame rdGame, RollDuelGame.Reason reason)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (reason == RollDuelGame.Reason.Normal)
|
|
||||||
{
|
|
||||||
var winner = rdGame.Winner == rdGame.P1 ? ctx.User : u;
|
|
||||||
description += $"\n**{winner}** Won {N((long)(rdGame.Amount * 2 * 0.98))}";
|
|
||||||
|
|
||||||
embed = embed.WithDescription(description);
|
|
||||||
|
|
||||||
await rdMsg.ModifyAsync(x => x.Embed = embed.Build());
|
|
||||||
}
|
|
||||||
else if (reason == RollDuelGame.Reason.Timeout)
|
|
||||||
{
|
|
||||||
await Response().Error(strs.roll_duel_timeout).SendAsync();
|
|
||||||
}
|
|
||||||
else if (reason == RollDuelGame.Reason.NoFunds)
|
|
||||||
{
|
|
||||||
await Response().Error(strs.roll_duel_no_funds).SendAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_service.Duels.TryRemove((u.Id, ctx.User.Id), out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task BetRoll([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
public async Task BetRoll([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Gambling.Common;
|
using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Connect4;
|
using EllieBot.Modules.Gambling.Common.Connect4;
|
||||||
|
@ -129,38 +128,6 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
|
|
||||||
private static readonly TypedKey<EconomyResult> _ecoKey = new("ellie:economy");
|
private static readonly TypedKey<EconomyResult> _ecoKey = new("ellie:economy");
|
||||||
|
|
||||||
public async Task<EconomyResult> GetEconomyAsync()
|
|
||||||
{
|
|
||||||
var data = await _cache.GetOrAddAsync(_ecoKey,
|
|
||||||
async () =>
|
|
||||||
{
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
var cash = uow.Set<DiscordUser>().GetTotalCurrency();
|
|
||||||
var onePercent = uow.Set<DiscordUser>().GetTopOnePercentCurrency(_client.CurrentUser.Id);
|
|
||||||
decimal planted = uow.Set<PlantedCurrency>().AsQueryable().Sum(x => x.Amount);
|
|
||||||
var waifus = uow.Set<WaifuInfo>().GetTotalValue();
|
|
||||||
var bot = await uow.Set<DiscordUser>().GetUserCurrencyAsync(_client.CurrentUser.Id);
|
|
||||||
decimal bank = await uow.GetTable<BankUser>()
|
|
||||||
.SumAsyncLinqToDB(x => x.Balance);
|
|
||||||
|
|
||||||
var result = new EconomyResult
|
|
||||||
{
|
|
||||||
Cash = cash,
|
|
||||||
Planted = planted,
|
|
||||||
Bot = bot,
|
|
||||||
Waifus = waifus,
|
|
||||||
OnePercent = onePercent,
|
|
||||||
Bank = bank
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
TimeSpan.FromMinutes(3));
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static readonly SemaphoreSlim _timelyLock = new(1, 1);
|
private static readonly SemaphoreSlim _timelyLock = new(1, 1);
|
||||||
|
|
||||||
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using SixLabors.Fonts;
|
using SixLabors.Fonts;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
|
@ -32,7 +31,6 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
|
|
||||||
public PlantPickService(
|
public PlantPickService(
|
||||||
DbService db,
|
DbService db,
|
||||||
CommandHandler cmd,
|
|
||||||
IBotStrings strings,
|
IBotStrings strings,
|
||||||
IImageCache images,
|
IImageCache images,
|
||||||
FontProvider fonts,
|
FontProvider fonts,
|
||||||
|
@ -108,7 +106,6 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
/// Get a random currency image stream, with an optional password sticked onto it.
|
/// Get a random currency image stream, with an optional password sticked onto it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pass">Optional password to add to top left corner.</param>
|
/// <param name="pass">Optional password to add to top left corner.</param>
|
||||||
/// <param name="extension">Extension of the file, defaults to png</param>
|
|
||||||
/// <returns>Stream of the currency image</returns>
|
/// <returns>Stream of the currency image</returns>
|
||||||
public async Task<(Stream, string)> GetRandomCurrencyImageAsync(string pass)
|
public async Task<(Stream, string)> GetRandomCurrencyImageAsync(string pass)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
using EllieBot.Common.TypeReaders;
|
|
||||||
using EllieBot.Modules.Gambling.Common;
|
|
||||||
using EllieBot.Modules.Gambling.Services;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
|
||||||
|
|
||||||
public partial class Gambling
|
|
||||||
{
|
|
||||||
public partial class CurrencyRaffleCommands : GamblingSubmodule<CurrencyRaffleService>
|
|
||||||
{
|
|
||||||
public enum Mixed { Mixed }
|
|
||||||
|
|
||||||
public CurrencyRaffleCommands(GamblingConfigService gamblingConfService)
|
|
||||||
: base(gamblingConfService)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
[Priority(0)]
|
|
||||||
public Task RaffleCur(Mixed _, [OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
|
||||||
=> RaffleCur(amount, true);
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
[Priority(1)]
|
|
||||||
public async Task RaffleCur([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, bool mixed = false)
|
|
||||||
{
|
|
||||||
if (!await CheckBetMandatory(amount))
|
|
||||||
return;
|
|
||||||
|
|
||||||
async Task OnEnded(IUser arg, long won)
|
|
||||||
{
|
|
||||||
await Response()
|
|
||||||
.Confirm(GetText(strs.rafflecur_ended(CurrencyName,
|
|
||||||
Format.Bold(arg.ToString()),
|
|
||||||
won + CurrencySign)))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
var res = await _service.JoinOrCreateGame(ctx.Channel.Id, ctx.User, amount, mixed, OnEnded);
|
|
||||||
|
|
||||||
if (res.Item1 is not null)
|
|
||||||
{
|
|
||||||
await Response()
|
|
||||||
.Confirm(GetText(strs.rafflecur(res.Item1.GameType.ToString())),
|
|
||||||
string.Join("\n", res.Item1.Users.Select(x => $"{x.DiscordUser} ({N(x.Amount)})")),
|
|
||||||
footer: GetText(strs.rafflecur_joined(ctx.User.ToString())))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (res.Item2 == CurrencyRaffleService.JoinErrorType.AlreadyJoinedOrInvalidAmount)
|
|
||||||
await Response().Error(strs.rafflecur_already_joined).SendAsync();
|
|
||||||
else if (res.Item2 == CurrencyRaffleService.JoinErrorType.NotEnoughCurrency)
|
|
||||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
namespace EllieBot.Modules.Gambling.Common;
|
|
||||||
|
|
||||||
public class CurrencyRaffleGame
|
|
||||||
{
|
|
||||||
public enum Type
|
|
||||||
{
|
|
||||||
Mixed,
|
|
||||||
Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<User> Users
|
|
||||||
=> _users;
|
|
||||||
|
|
||||||
public Type GameType { get; }
|
|
||||||
|
|
||||||
private readonly HashSet<User> _users = new();
|
|
||||||
|
|
||||||
public CurrencyRaffleGame(Type type)
|
|
||||||
=> GameType = type;
|
|
||||||
|
|
||||||
public bool AddUser(IUser usr, long amount)
|
|
||||||
{
|
|
||||||
// if game type is normal, and someone already joined the game
|
|
||||||
// (that's the user who created it)
|
|
||||||
if (GameType == Type.Normal && _users.Count > 0 && _users.First().Amount != amount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!_users.Add(new()
|
|
||||||
{
|
|
||||||
DiscordUser = usr,
|
|
||||||
Amount = amount
|
|
||||||
}))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public User GetWinner()
|
|
||||||
{
|
|
||||||
var rng = new EllieRandom();
|
|
||||||
if (GameType == Type.Mixed)
|
|
||||||
{
|
|
||||||
var num = rng.NextLong(0L, Users.Sum(x => x.Amount));
|
|
||||||
var sum = 0L;
|
|
||||||
foreach (var u in Users)
|
|
||||||
{
|
|
||||||
sum += u.Amount;
|
|
||||||
if (sum > num)
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var usrs = _users.ToArray();
|
|
||||||
return usrs[rng.Next(0, usrs.Length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public IUser DiscordUser { get; set; }
|
|
||||||
public long Amount { get; set; }
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
=> DiscordUser.GetHashCode();
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
=> obj is User u ? u.DiscordUser == DiscordUser : false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
using EllieBot.Modules.Gambling.Common;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
|
||||||
|
|
||||||
public class CurrencyRaffleService : IEService
|
|
||||||
{
|
|
||||||
public enum JoinErrorType
|
|
||||||
{
|
|
||||||
NotEnoughCurrency,
|
|
||||||
AlreadyJoinedOrInvalidAmount
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<ulong, CurrencyRaffleGame> Games { get; } = new();
|
|
||||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
|
|
||||||
public CurrencyRaffleService(ICurrencyService cs)
|
|
||||||
=> _cs = cs;
|
|
||||||
|
|
||||||
public async Task<(CurrencyRaffleGame, JoinErrorType?)> JoinOrCreateGame(
|
|
||||||
ulong channelId,
|
|
||||||
IUser user,
|
|
||||||
long amount,
|
|
||||||
bool mixed,
|
|
||||||
Func<IUser, long, Task> onEnded)
|
|
||||||
{
|
|
||||||
await _locker.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var newGame = false;
|
|
||||||
if (!Games.TryGetValue(channelId, out var crg))
|
|
||||||
{
|
|
||||||
newGame = true;
|
|
||||||
crg = new(mixed ? CurrencyRaffleGame.Type.Mixed : CurrencyRaffleGame.Type.Normal);
|
|
||||||
Games.Add(channelId, crg);
|
|
||||||
}
|
|
||||||
|
|
||||||
//remove money, and stop the game if this
|
|
||||||
// user created it and doesn't have the money
|
|
||||||
if (!await _cs.RemoveAsync(user.Id, amount, new("raffle", "join")))
|
|
||||||
{
|
|
||||||
if (newGame)
|
|
||||||
Games.Remove(channelId);
|
|
||||||
return (null, JoinErrorType.NotEnoughCurrency);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!crg.AddUser(user, amount))
|
|
||||||
{
|
|
||||||
await _cs.AddAsync(user.Id, amount, new("raffle", "refund"));
|
|
||||||
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newGame)
|
|
||||||
{
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(60000);
|
|
||||||
await _locker.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var winner = crg.GetWinner();
|
|
||||||
var won = crg.Users.Sum(x => x.Amount);
|
|
||||||
|
|
||||||
await _cs.AddAsync(winner.DiscordUser.Id, won, new("raffle", "win"));
|
|
||||||
Games.Remove(channelId, out _);
|
|
||||||
_ = onEnded(winner.DiscordUser, won);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
finally { _locker.Release(); }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (crg, null);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_locker.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Gambling.Common;
|
using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
|
|
@ -7,9 +7,7 @@ using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Drawing.Processing;
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using EllieBot.Modules.Gambling;
|
|
||||||
using EllieBot.Common.TypeReaders;
|
using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Modules.Utility;
|
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
|
@ -25,9 +23,6 @@ public partial class Gambling
|
||||||
[Group]
|
[Group]
|
||||||
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
||||||
{
|
{
|
||||||
private static decimal totalBet;
|
|
||||||
private static decimal totalPaidOut;
|
|
||||||
|
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
private readonly FontProvider _fonts;
|
private readonly FontProvider _fonts;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
@ -71,17 +66,19 @@ public partial class Gambling
|
||||||
|
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
var eb = _sender.CreateEmbed()
|
||||||
.WithAuthor(ctx.User)
|
.WithAuthor(ctx.User)
|
||||||
.WithDescription(Format.Bold(text))
|
.WithDescription(Format.Bold(text))
|
||||||
.WithImageUrl($"attachment://result.png")
|
.WithImageUrl($"attachment://result.png")
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var bb = new ButtonBuilder(emote: Emoji.Parse("🔁"), customId: "slot:again", label: "Pull Again");
|
var bb = new ButtonBuilder(emote: Emoji.Parse("🔁"), customId: "slot:again", label: "Pull Again");
|
||||||
var inter = _inter.Create(ctx.User.Id, bb, smc =>
|
var inter = _inter.Create(ctx.User.Id,
|
||||||
{
|
bb,
|
||||||
smc.DeferAsync();
|
smc =>
|
||||||
return Slot(amount);
|
{
|
||||||
});
|
smc.DeferAsync();
|
||||||
|
return Slot(amount);
|
||||||
|
});
|
||||||
|
|
||||||
var msg = await ctx.Channel.SendFileAsync(imgStream,
|
var msg = await ctx.Channel.SendFileAsync(imgStream,
|
||||||
"result.png",
|
"result.png",
|
||||||
|
@ -162,12 +159,6 @@ public partial class Gambling
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_slotStatsLock)
|
|
||||||
{
|
|
||||||
totalBet += amount;
|
|
||||||
totalPaidOut += result.Won;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Waifu;
|
using EllieBot.Modules.Gambling.Common.Waifu;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using TwitchLib.Api.Helix.Models.Teams;
|
using System.Globalization;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
@ -153,12 +153,12 @@ public partial class Gambling
|
||||||
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
|
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
|
||||||
else if (result == DivorceResult.NotYourWife)
|
else if (result == DivorceResult.NotYourWife)
|
||||||
await Response().Error(strs.waifu_not_yours).SendAsync();
|
await Response().Error(strs.waifu_not_yours).SendAsync();
|
||||||
else
|
else if (remaining is { } rem)
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Error(strs.waifu_recent_divorce(
|
.Error(strs.waifu_recent_divorce(
|
||||||
Format.Bold(((int)remaining?.TotalHours).ToString()),
|
Format.Bold(((int)rem.TotalHours).ToString()),
|
||||||
Format.Bold(remaining?.Minutes.ToString())))
|
Format.Bold(rem.Minutes.ToString())))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ public partial class Gambling
|
||||||
private string GetLbString(WaifuLbResult w)
|
private string GetLbString(WaifuLbResult w)
|
||||||
{
|
{
|
||||||
var claimer = "no one";
|
var claimer = "no one";
|
||||||
var status = string.Empty;
|
string status;
|
||||||
|
|
||||||
var waifuUsername = w.Username.TrimTo(20);
|
var waifuUsername = w.Username.TrimTo(20);
|
||||||
var claimerUsername = w.Claimer?.TrimTo(20);
|
var claimerUsername = w.Claimer?.TrimTo(20);
|
||||||
|
@ -377,7 +377,8 @@ public partial class Gambling
|
||||||
if (sucess)
|
if (sucess)
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.waifu_gift(Format.Bold($"{GetCountString(items)}{items.Item} {items.Item.ItemEmoji}"),
|
.Confirm(strs.waifu_gift(
|
||||||
|
Format.Bold($"{GetCountString(items)}{items.Item} {items.Item.ItemEmoji}"),
|
||||||
Format.Bold(waifu.ToString())))
|
Format.Bold(waifu.ToString())))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,9 @@ using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Gambling.Common;
|
using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Waifu;
|
using EllieBot.Modules.Gambling.Common.Waifu;
|
||||||
using SixLabors.ImageSharp;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Gambling;
|
|
||||||
using EllieBot.Modules.Gambling.Betdraw;
|
using EllieBot.Modules.Gambling.Betdraw;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using OneOf;
|
using OneOf;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Gambling;
|
|
||||||
using EllieBot.Modules.Gambling.Betdraw;
|
using EllieBot.Modules.Gambling.Betdraw;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
using NCalc;
|
using NCalc;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Games.Services;
|
using EllieBot.Modules.Games.Services;
|
||||||
using EllieBot.Db.Models;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games;
|
namespace EllieBot.Modules.Games;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using System.CodeDom;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
public sealed class ThinkResult
|
public sealed class ThinkResult
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
|
||||||
using EllieBot.Modules.Games.Common;
|
using EllieBot.Modules.Games.Common;
|
||||||
using EllieBot.Modules.Games.Common.Acrophobia;
|
using EllieBot.Modules.Games.Common.Acrophobia;
|
||||||
using EllieBot.Modules.Games.Common.Nunchi;
|
using EllieBot.Modules.Games.Common.Nunchi;
|
||||||
|
@ -8,11 +7,10 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games.Services;
|
namespace EllieBot.Modules.Games.Services;
|
||||||
|
|
||||||
public class GamesService : IEService, IReadyExecutor
|
public class GamesService : IEService
|
||||||
{
|
{
|
||||||
private const string TYPING_ARTICLES_PATH = "data/typing_articles3.json";
|
private const string TYPING_ARTICLES_PATH = "data/typing_articles3.json";
|
||||||
|
|
||||||
public ConcurrentDictionary<ulong, GirlRating> GirlRatings { get; } = new();
|
|
||||||
|
|
||||||
public IReadOnlyList<string> EightBallResponses
|
public IReadOnlyList<string> EightBallResponses
|
||||||
=> _gamesConfig.Data.EightBallResponses;
|
=> _gamesConfig.Data.EightBallResponses;
|
||||||
|
@ -25,7 +23,6 @@ public class GamesService : IEService, IReadyExecutor
|
||||||
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new();
|
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new();
|
||||||
public ConcurrentDictionary<ulong, NunchiGame> NunchiGames { get; } = new();
|
public ConcurrentDictionary<ulong, NunchiGame> NunchiGames { get; } = new();
|
||||||
|
|
||||||
public AsyncLazy<RatingTexts> Ratings { get; }
|
|
||||||
private readonly GamesConfigService _gamesConfig;
|
private readonly GamesConfigService _gamesConfig;
|
||||||
|
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
|
@ -41,7 +38,6 @@ public class GamesService : IEService, IReadyExecutor
|
||||||
SizeLimit = 500_000
|
SizeLimit = 500_000
|
||||||
});
|
});
|
||||||
|
|
||||||
Ratings = new(GetRatingTexts);
|
|
||||||
_rng = new EllieRandom();
|
_rng = new EllieRandom();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -55,22 +51,6 @@ public class GamesService : IEService, IReadyExecutor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
|
||||||
{
|
|
||||||
// reset rating once a day
|
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromDays(1));
|
|
||||||
while (await timer.WaitForNextTickAsync())
|
|
||||||
GirlRatings.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<RatingTexts> GetRatingTexts()
|
|
||||||
{
|
|
||||||
using var http = _httpFactory.CreateClient();
|
|
||||||
var text = await http.GetStringAsync(
|
|
||||||
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/rates.json");
|
|
||||||
return JsonConvert.DeserializeObject<RatingTexts>(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddTypingArticle(IUser user, string text)
|
public void AddTypingArticle(IUser user, string text)
|
||||||
{
|
{
|
||||||
TypingArticles.Add(new()
|
TypingArticles.Add(new()
|
||||||
|
@ -104,15 +84,4 @@ public class GamesService : IEService, IReadyExecutor
|
||||||
File.WriteAllText(TYPING_ARTICLES_PATH, JsonConvert.SerializeObject(articles));
|
File.WriteAllText(TYPING_ARTICLES_PATH, JsonConvert.SerializeObject(articles));
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RatingTexts
|
|
||||||
{
|
|
||||||
public string Nog { get; set; }
|
|
||||||
public string Tra { get; set; }
|
|
||||||
public string Fun { get; set; }
|
|
||||||
public string Uni { get; set; }
|
|
||||||
public string Wif { get; set; }
|
|
||||||
public string Dat { get; set; }
|
|
||||||
public string Dan { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
using SixLabors.ImageSharp;
|
|
||||||
using SixLabors.ImageSharp.Processing;
|
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games.Common;
|
|
||||||
|
|
||||||
public class GirlRating
|
|
||||||
{
|
|
||||||
public double Crazy { get; }
|
|
||||||
public double Hot { get; }
|
|
||||||
public int Roll { get; }
|
|
||||||
public string Advice { get; }
|
|
||||||
|
|
||||||
public AsyncLazy<Stream> Stream { get; }
|
|
||||||
private readonly IImageCache _images;
|
|
||||||
|
|
||||||
public GirlRating(
|
|
||||||
IImageCache images,
|
|
||||||
double crazy,
|
|
||||||
double hot,
|
|
||||||
int roll,
|
|
||||||
string advice)
|
|
||||||
{
|
|
||||||
_images = images;
|
|
||||||
Crazy = crazy;
|
|
||||||
Hot = hot;
|
|
||||||
Roll = roll;
|
|
||||||
Advice = advice; // convenient to have it here, even though atm there are only few different ones.
|
|
||||||
|
|
||||||
Stream = new(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bgBytes = await _images.GetRategirlBgAsync();
|
|
||||||
using var img = Image.Load(bgBytes);
|
|
||||||
const int minx = 35;
|
|
||||||
const int miny = 385;
|
|
||||||
const int length = 345;
|
|
||||||
|
|
||||||
var pointx = (int)(minx + (length * (Hot / 10)));
|
|
||||||
var pointy = (int)(miny - (length * ((Crazy - 4) / 6)));
|
|
||||||
|
|
||||||
var dotBytes = await _images.GetRategirlDotAsync();
|
|
||||||
using (var pointImg = Image.Load(dotBytes))
|
|
||||||
{
|
|
||||||
img.Mutate(x => x.DrawImage(pointImg, new(pointx - 10, pointy - 10), new GraphicsOptions()));
|
|
||||||
}
|
|
||||||
|
|
||||||
var imgStream = new MemoryStream();
|
|
||||||
img.SaveAsPng(imgStream);
|
|
||||||
return imgStream;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "Error getting RateGirl image");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -107,9 +107,9 @@ public partial class Games
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private EmbedBuilder? questionEmbed = null;
|
private EmbedBuilder? questionEmbed;
|
||||||
private IUserMessage? questionMessage = null;
|
private IUserMessage? questionMessage;
|
||||||
private bool showHowToQuit = false;
|
private bool showHowToQuit;
|
||||||
|
|
||||||
private void RegisterEvents(TriviaGame trivia)
|
private void RegisterEvents(TriviaGame trivia)
|
||||||
{
|
{
|
|
@ -11,12 +11,19 @@ public sealed class TriviaGame
|
||||||
private readonly IQuestionPool _questionPool;
|
private readonly IQuestionPool _questionPool;
|
||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
|
|
||||||
public event Func<TriviaGame, TriviaQuestion, Task> OnQuestion = static delegate { return Task.CompletedTask; };
|
public event Func<TriviaGame, TriviaQuestion, Task> OnQuestion = static delegate { return Task.CompletedTask; };
|
||||||
public event Func<TriviaGame, TriviaQuestion, Task> OnHint = static delegate { return Task.CompletedTask; };
|
public event Func<TriviaGame, TriviaQuestion, Task> OnHint = static delegate { return Task.CompletedTask; };
|
||||||
public event Func<TriviaGame, Task> OnStats = static delegate { return Task.CompletedTask; };
|
public event Func<TriviaGame, Task> OnStats = static delegate { return Task.CompletedTask; };
|
||||||
public event Func<TriviaGame, TriviaUser, TriviaQuestion, bool, Task> OnGuess = static delegate { return Task.CompletedTask; };
|
|
||||||
|
public event Func<TriviaGame, TriviaUser, TriviaQuestion, bool, Task> OnGuess = static delegate
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
public event Func<TriviaGame, TriviaQuestion, Task> OnTimeout = static delegate { return Task.CompletedTask; };
|
public event Func<TriviaGame, TriviaQuestion, Task> OnTimeout = static delegate { return Task.CompletedTask; };
|
||||||
public event Func<TriviaGame, Task> OnEnded = static delegate { return Task.CompletedTask; };
|
public event Func<TriviaGame, Task> OnEnded = static delegate { return Task.CompletedTask; };
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private bool _isStopped;
|
private bool _isStopped;
|
||||||
|
@ -24,7 +31,7 @@ public sealed class TriviaGame
|
||||||
public TriviaQuestion? CurrentQuestion { get; set; }
|
public TriviaQuestion? CurrentQuestion { get; set; }
|
||||||
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ulong, int> _users = new ();
|
private readonly ConcurrentDictionary<ulong, int> _users = new();
|
||||||
|
|
||||||
private readonly Channel<(TriviaUser User, string Input)> _inputs
|
private readonly Channel<(TriviaUser User, string Input)> _inputs
|
||||||
= Channel.CreateUnbounded<(TriviaUser, string)>(new UnboundedChannelOptions
|
= Channel.CreateUnbounded<(TriviaUser, string)>(new UnboundedChannelOptions
|
||||||
|
@ -41,8 +48,8 @@ public sealed class TriviaGame
|
||||||
_questionPool = _opts.IsPokemon
|
_questionPool = _opts.IsPokemon
|
||||||
? new PokemonQuestionPool(cache)
|
? new PokemonQuestionPool(cache)
|
||||||
: new DefaultQuestionPool(cache);
|
: new DefaultQuestionPool(cache);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAsync()
|
public async Task RunAsync()
|
||||||
{
|
{
|
||||||
await GameLoop();
|
await GameLoop();
|
||||||
|
@ -50,7 +57,8 @@ public sealed class TriviaGame
|
||||||
|
|
||||||
private async Task GameLoop()
|
private async Task GameLoop()
|
||||||
{
|
{
|
||||||
Task TimeOutFactory() => Task.Delay(_opts.QuestionTimer * 1000 / 2);
|
Task TimeOutFactory()
|
||||||
|
=> Task.Delay(_opts.QuestionTimer * 1000 / 2);
|
||||||
|
|
||||||
var errorCount = 0;
|
var errorCount = 0;
|
||||||
var inactivity = 0;
|
var inactivity = 0;
|
||||||
|
@ -91,7 +99,8 @@ public sealed class TriviaGame
|
||||||
{
|
{
|
||||||
// clear out all of the past guesses
|
// clear out all of the past guesses
|
||||||
while (_inputs.Reader.TryRead(out _))
|
while (_inputs.Reader.TryRead(out _))
|
||||||
;
|
{
|
||||||
|
}
|
||||||
|
|
||||||
await OnQuestion(this, question);
|
await OnQuestion(this, question);
|
||||||
}
|
}
|
||||||
|
@ -213,7 +222,7 @@ public sealed class TriviaGame
|
||||||
|
|
||||||
public async Task TriggerQuestionAsync()
|
public async Task TriggerQuestionAsync()
|
||||||
{
|
{
|
||||||
if(CurrentQuestion is TriviaQuestion q)
|
if (CurrentQuestion is TriviaQuestion q)
|
||||||
await OnQuestion(this, q);
|
await OnQuestion(this, q);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -509,6 +509,7 @@ public sealed partial class Help : EllieModule<HelpService>
|
||||||
|
|
||||||
// send the indented file to chat
|
// send the indented file to chat
|
||||||
await using var rDataStream = new MemoryStream(Encoding.ASCII.GetBytes(readableData));
|
await using var rDataStream = new MemoryStream(Encoding.ASCII.GetBytes(readableData));
|
||||||
|
await File.WriteAllTextAsync("data/commandlist.json", readableData);
|
||||||
await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen));
|
await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Music.Services;
|
using EllieBot.Modules.Music.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Music;
|
namespace EllieBot.Modules.Music;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Music.Services;
|
using EllieBot.Modules.Music.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using EllieBot.Db;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Db.Models;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Music.Services;
|
namespace EllieBot.Modules.Music.Services;
|
||||||
|
|
|
@ -61,8 +61,8 @@ public class RadioResolver : IRadioResolver
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var m = _m3URegex.Match(file);
|
var m = _m3URegex.Match(file);
|
||||||
var res = m.Groups["url"]?.ToString();
|
var res = m.Groups["url"].ToString();
|
||||||
return res?.Trim();
|
return res.Trim();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -76,8 +76,8 @@ public class RadioResolver : IRadioResolver
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var m = _asxRegex.Match(file);
|
var m = _asxRegex.Match(file);
|
||||||
var res = m.Groups["url"]?.ToString();
|
var res = m.Groups["url"].ToString();
|
||||||
return res?.Trim();
|
return res.Trim();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -91,8 +91,8 @@ public class RadioResolver : IRadioResolver
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var m = _xspfRegex.Match(file);
|
var m = _xspfRegex.Match(file);
|
||||||
var res = m.Groups["url"]?.ToString();
|
var res = m.Groups["url"].ToString();
|
||||||
return res?.Trim();
|
return res.Trim();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using StackExchange.Redis;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Patronage;
|
namespace EllieBot.Modules.Patronage;
|
||||||
|
|
||||||
|
@ -21,16 +19,11 @@ public sealed class PatronageService
|
||||||
public int Priority
|
public int Priority
|
||||||
=> int.MinValue;
|
=> int.MinValue;
|
||||||
|
|
||||||
private static readonly PatronTier[] _tiers = Enum.GetValues<PatronTier>();
|
|
||||||
|
|
||||||
private readonly PatronageConfig _pConf;
|
private readonly PatronageConfig _pConf;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly ISubscriptionHandler _subsHandler;
|
private readonly ISubscriptionHandler _subsHandler;
|
||||||
|
|
||||||
private static readonly TypedKey<long> _quotaKey
|
|
||||||
= new($"quota:last_hourly_reset");
|
|
||||||
|
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly IBotCredsProvider _creds;
|
private readonly IBotCredsProvider _creds;
|
||||||
private readonly IMessageSenderService _sender;
|
private readonly IMessageSenderService _sender;
|
||||||
|
@ -135,19 +128,19 @@ public sealed class PatronageService
|
||||||
// user is charged again for this month
|
// user is charged again for this month
|
||||||
// if his sub would end in teh future, extend it by one month.
|
// if his sub would end in teh future, extend it by one month.
|
||||||
// if it's not, just add 1 month to the last charge date
|
// if it's not, just add 1 month to the last charge date
|
||||||
var count = await ctx.GetTable<PatronUser>()
|
await ctx.GetTable<PatronUser>()
|
||||||
.Where(x => x.UniquePlatformUserId
|
.Where(x => x.UniquePlatformUserId
|
||||||
== subscriber.UniquePlatformUserId)
|
== subscriber.UniquePlatformUserId)
|
||||||
.UpdateAsync(old => new()
|
.UpdateAsync(old => new()
|
||||||
{
|
{
|
||||||
UserId = subscriber.UserId,
|
UserId = subscriber.UserId,
|
||||||
AmountCents = subscriber.Cents,
|
AmountCents = subscriber.Cents,
|
||||||
LastCharge = lastChargeUtc,
|
LastCharge = lastChargeUtc,
|
||||||
ValidThru = old.ValidThru >= todayDate
|
ValidThru = old.ValidThru >= todayDate
|
||||||
// ? Sql.DateAdd(Sql.DateParts.Month, 1, old.ValidThru).Value
|
// ? Sql.DateAdd(Sql.DateParts.Month, 1, old.ValidThru).Value
|
||||||
? old.ValidThru.AddMonths(1)
|
? old.ValidThru.AddMonths(1)
|
||||||
: dateInOneMonth,
|
: dateInOneMonth,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
dbPatron.UserId = subscriber.UserId;
|
dbPatron.UserId = subscriber.UserId;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Services;
|
namespace EllieBot.Modules.Permissions.Services;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.TypeReaders;
|
using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Permissions.Services;
|
using EllieBot.Modules.Permissions.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Permissions.Services;
|
using EllieBot.Modules.Permissions.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Services;
|
namespace EllieBot.Modules.Permissions.Services;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Common.TypeReaders;
|
using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Common.TypeReaders.Models;
|
using EllieBot.Common.TypeReaders.Models;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Permissions.Common;
|
using EllieBot.Modules.Permissions.Common;
|
||||||
using EllieBot.Modules.Permissions.Services;
|
using EllieBot.Modules.Permissions.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db;
|
|
||||||
using EllieBot.Modules.Permissions.Common;
|
using EllieBot.Modules.Permissions.Common;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
|
@ -10,133 +10,6 @@ public partial class Searches
|
||||||
[Group]
|
[Group]
|
||||||
public partial class AnimeSearchCommands : EllieModule<AnimeSearchService>
|
public partial class AnimeSearchCommands : EllieModule<AnimeSearchService>
|
||||||
{
|
{
|
||||||
// [EllieCommand, Aliases]
|
|
||||||
// public async Task Novel([Leftover] string query)
|
|
||||||
// {
|
|
||||||
// if (string.IsNullOrWhiteSpace(query))
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// var novelData = await _service.GetNovelData(query);
|
|
||||||
//
|
|
||||||
// if (novelData is null)
|
|
||||||
// {
|
|
||||||
// await Response().Error(strs.failed_finding_novel).SendAsync();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var embed = _sender.CreateEmbed()
|
|
||||||
// .WithOkColor()
|
|
||||||
// .WithDescription(novelData.Description.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
|
|
||||||
// .WithTitle(novelData.Title)
|
|
||||||
// .WithUrl(novelData.Link)
|
|
||||||
// .WithImageUrl(novelData.ImageUrl)
|
|
||||||
// .AddField(GetText(strs.authors), string.Join("\n", novelData.Authors), true)
|
|
||||||
// .AddField(GetText(strs.status), novelData.Status, true)
|
|
||||||
// .AddField(GetText(strs.genres), string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" }), true)
|
|
||||||
// .WithFooter($"{GetText(strs.score)} {novelData.Score}");
|
|
||||||
//
|
|
||||||
// await Response().Embed(embed).SendAsync();
|
|
||||||
// }
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[Priority(0)]
|
|
||||||
public async Task Mal([Leftover] string name)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var fullQueryLink = "https://myanimelist.net/profile/" + name;
|
|
||||||
|
|
||||||
var config = Configuration.Default.WithDefaultLoader();
|
|
||||||
using var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
|
||||||
var imageElem =
|
|
||||||
document.QuerySelector(
|
|
||||||
"body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img");
|
|
||||||
var imageUrl = ((IHtmlImageElement)imageElem)?.Source
|
|
||||||
?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png";
|
|
||||||
|
|
||||||
var stats = document
|
|
||||||
.QuerySelectorAll(
|
|
||||||
"body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span")
|
|
||||||
.Select(x => x.InnerHtml)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
|
|
||||||
|
|
||||||
var favAnime = GetText(strs.anime_no_fav);
|
|
||||||
if (favorites.Length > 0 && favorites[0].QuerySelector("p") is null)
|
|
||||||
{
|
|
||||||
favAnime = string.Join("\n",
|
|
||||||
favorites[0]
|
|
||||||
.QuerySelectorAll("ul > li > div.di-tc.va-t > a")
|
|
||||||
.Shuffle()
|
|
||||||
.Take(3)
|
|
||||||
.Select(x =>
|
|
||||||
{
|
|
||||||
var elem = (IHtmlAnchorElement)x;
|
|
||||||
return $"[{elem.InnerHtml}]({elem.Href})";
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li.clearfix")
|
|
||||||
.Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div")
|
|
||||||
.Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed()
|
|
||||||
.WithOkColor()
|
|
||||||
.WithTitle(GetText(strs.mal_profile(name)))
|
|
||||||
.AddField("💚 " + GetText(strs.watching), stats[0], true)
|
|
||||||
.AddField("💙 " + GetText(strs.completed), stats[1], true);
|
|
||||||
if (info.Count < 3)
|
|
||||||
embed.AddField("💛 " + GetText(strs.on_hold), stats[2], true);
|
|
||||||
embed.AddField("💔 " + GetText(strs.dropped), stats[3], true)
|
|
||||||
.AddField("⚪ " + GetText(strs.plan_to_watch), stats[4], true)
|
|
||||||
.AddField("🕐 " + daysAndMean[0][0], daysAndMean[0][1], true)
|
|
||||||
.AddField("📊 " + daysAndMean[1][0], daysAndMean[1][1], true)
|
|
||||||
.AddField(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1, info[0].Item2.TrimTo(20), true)
|
|
||||||
.AddField(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1, info[1].Item2.TrimTo(20), true);
|
|
||||||
if (info.Count > 2)
|
|
||||||
embed.AddField(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1, info[2].Item2.TrimTo(20), true);
|
|
||||||
|
|
||||||
embed.WithDescription($@"
|
|
||||||
** https://myanimelist.net/animelist/{name} **
|
|
||||||
|
|
||||||
**{GetText(strs.top_3_fav_anime)}**
|
|
||||||
{favAnime}")
|
|
||||||
.WithUrl(fullQueryLink)
|
|
||||||
.WithImageUrl(imageUrl);
|
|
||||||
|
|
||||||
await Response().Embed(embed).SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string MalInfoToEmoji(string info)
|
|
||||||
{
|
|
||||||
info = info.Trim().ToLowerInvariant();
|
|
||||||
switch (info)
|
|
||||||
{
|
|
||||||
case "gender":
|
|
||||||
return "🚁";
|
|
||||||
case "location":
|
|
||||||
return "🗺";
|
|
||||||
case "last online":
|
|
||||||
return "👥";
|
|
||||||
case "birthday":
|
|
||||||
return "📆";
|
|
||||||
default:
|
|
||||||
return "❔";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
[Priority(1)]
|
|
||||||
public Task Mal(IGuildUser usr)
|
|
||||||
=> Mal(usr.Username);
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task Anime([Leftover] string query)
|
public async Task Anime([Leftover] string query)
|
||||||
{
|
{
|
||||||
|
@ -152,19 +25,19 @@ public partial class Searches
|
||||||
}
|
}
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithDescription(animeData.Synopsis.Replace("<br>",
|
.WithDescription(animeData.Synopsis.Replace("<br>",
|
||||||
Environment.NewLine,
|
Environment.NewLine,
|
||||||
StringComparison.InvariantCulture))
|
StringComparison.InvariantCulture))
|
||||||
.WithTitle(animeData.TitleEnglish)
|
.WithTitle(animeData.TitleEnglish)
|
||||||
.WithUrl(animeData.Link)
|
.WithUrl(animeData.Link)
|
||||||
.WithImageUrl(animeData.ImageUrlLarge)
|
.WithImageUrl(animeData.ImageUrlLarge)
|
||||||
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
||||||
.AddField(GetText(strs.status), animeData.AiringStatus, true)
|
.AddField(GetText(strs.status), animeData.AiringStatus, true)
|
||||||
.AddField(GetText(strs.genres),
|
.AddField(GetText(strs.genres),
|
||||||
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : ["none"]),
|
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : ["none"]),
|
||||||
true)
|
true)
|
||||||
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
||||||
await Response().Embed(embed).SendAsync();
|
await Response().Embed(embed).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,19 +57,19 @@ public partial class Searches
|
||||||
}
|
}
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithDescription(mangaData.Synopsis.Replace("<br>",
|
.WithDescription(mangaData.Synopsis.Replace("<br>",
|
||||||
Environment.NewLine,
|
Environment.NewLine,
|
||||||
StringComparison.InvariantCulture))
|
StringComparison.InvariantCulture))
|
||||||
.WithTitle(mangaData.TitleEnglish)
|
.WithTitle(mangaData.TitleEnglish)
|
||||||
.WithUrl(mangaData.Link)
|
.WithUrl(mangaData.Link)
|
||||||
.WithImageUrl(mangaData.ImageUrlLge)
|
.WithImageUrl(mangaData.ImageUrlLge)
|
||||||
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
||||||
.AddField(GetText(strs.status), mangaData.PublishingStatus, true)
|
.AddField(GetText(strs.status), mangaData.PublishingStatus, true)
|
||||||
.AddField(GetText(strs.genres),
|
.AddField(GetText(strs.genres),
|
||||||
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : ["none"]),
|
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : ["none"]),
|
||||||
true)
|
true)
|
||||||
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
||||||
|
|
||||||
await Response().Embed(embed).SendAsync();
|
await Response().Embed(embed).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,18 +65,6 @@ public partial class Searches
|
||||||
var change = (stock.Price - stock.Close).ToString("N2", Culture);
|
var change = (stock.Price - stock.Close).ToString("N2", Culture);
|
||||||
var changePercent = (1 - (stock.Close / stock.Price)).ToString("P1", Culture);
|
var changePercent = (1 - (stock.Close / stock.Price)).ToString("P1", Culture);
|
||||||
|
|
||||||
var sign50 = stock.Change50d >= 0
|
|
||||||
? "\\🔼"
|
|
||||||
: "\\🔻";
|
|
||||||
|
|
||||||
var change50 = (stock.Change50d).ToString("P1", Culture);
|
|
||||||
|
|
||||||
var sign200 = stock.Change200d >= 0
|
|
||||||
? "\\🔼"
|
|
||||||
: "\\🔻";
|
|
||||||
|
|
||||||
var change200 = (stock.Change200d).ToString("P1", Culture);
|
|
||||||
|
|
||||||
var price = stock.Price.ToString("C2", localCulture);
|
var price = stock.Price.ToString("C2", localCulture);
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
var eb = _sender.CreateEmbed()
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue