diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..a4713c5 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "0.27.3", + "commands": [ + "dotnet-csharpier" + ] + } + } +} \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..9f6abec --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,91 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '27 4 * * 1' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: csharp + build-mode: autobuild + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 # avoid shallow clone so nbgv can do its work. + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + #- if: matrix.build-mode == 'manual' + # run: | + # echo 'If you are using a "manual" build mode for one or more of the' \ + # 'languages you are analyzing, replace this with the commands to build' \ + # 'your code, for example:' + # echo ' make bootstrap' + # echo ' make release' + # exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/publish-windows.yml b/.github/workflows/publish-windows.yml index 706be48..9262d16 100644 --- a/.github/workflows/publish-windows.yml +++ b/.github/workflows/publish-windows.yml @@ -67,7 +67,9 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - + - uses: dotnet/nbgv@master + with: + setAllVars: true # Install the .NET Core workload - name: Install .NET Core uses: actions/setup-dotnet@v3 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6cd137c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "[csharp]": { + "editor.defaultFormatter": "csharpier.csharpier-vscode" + } +} \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..2456241 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,19 @@ + + + latest + enable + true + latest + strict + false + + https://github.com/ArcadeArchie/ArcadePointsBot + git + ArcadeArchie + Copyright 2024 ArcadeArchie + + + + 8.0.0 + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..b5bbc34 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,60 @@ + + + true + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + \ No newline at end of file diff --git a/src/ArcadePointsBot/Application/Rewards/DisableRewardsCommandHandler.cs b/src/ArcadePointsBot/Application/Rewards/DisableRewardsCommandHandler.cs index bc53fb1..de18552 100644 --- a/src/ArcadePointsBot/Application/Rewards/DisableRewardsCommandHandler.cs +++ b/src/ArcadePointsBot/Application/Rewards/DisableRewardsCommandHandler.cs @@ -41,7 +41,8 @@ CancellationToken cancellationToken { x.IsEnabled = false; return x; - }).ToList(); + }) + .ToList(); await _rewardService.BulkUpdateRewards( toDisable, u => u.SetProperty(p => p.IsEnabled, false) diff --git a/src/ArcadePointsBot/ArcadePointsBot.csproj b/src/ArcadePointsBot/ArcadePointsBot.csproj index abae410..e827e5f 100644 --- a/src/ArcadePointsBot/ArcadePointsBot.csproj +++ b/src/ArcadePointsBot/ArcadePointsBot.csproj @@ -2,7 +2,6 @@ Exe net8.0 - enable true app.manifest true @@ -16,40 +15,38 @@ - - - - - - + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/ArcadePointsBot/Common/Abstractions/Messaging.cs b/src/ArcadePointsBot/Common/Abstractions/Messaging.cs index 0a72227..f443ec1 100644 --- a/src/ArcadePointsBot/Common/Abstractions/Messaging.cs +++ b/src/ArcadePointsBot/Common/Abstractions/Messaging.cs @@ -4,6 +4,7 @@ namespace ArcadePointsBot.Common.Abstractions.Messaging; internal interface ICommand : Mediator.ICommand where TResponse : Result; + internal interface IQuery : Mediator.IQuery where TResponse : Result; @@ -21,6 +22,7 @@ internal interface IQueryHandler : Mediator.IQueryHandler< /// /// The query type. /// The query response type. -internal interface ICommandHandler : Mediator.ICommandHandler +internal interface ICommandHandler + : Mediator.ICommandHandler where TCommand : ICommand - where TResponse : Result; \ No newline at end of file + where TResponse : Result; diff --git a/src/ArcadePointsBot/Common/Errors/TwitchAPI.cs b/src/ArcadePointsBot/Common/Errors/TwitchAPI.cs index d52ee8b..9197188 100644 --- a/src/ArcadePointsBot/Common/Errors/TwitchAPI.cs +++ b/src/ArcadePointsBot/Common/Errors/TwitchAPI.cs @@ -4,20 +4,25 @@ namespace ArcadePointsBot.Errors { public static class TwitchAPI { - public static Error BadCredentials => new( - "TTV.API.BadCreds", - "The Credentials are invalid, resetting them and restarting the bot is recommended"); - public static Error BadRewardTitle => new( - "TTV.API.BadTitle", - "The Reward Title was disallowed by twitch, try a differend one"); - public static Error DupeRewardTitle => new( - "TTV.API.DupeTitle", - "The Reward with this title already exists, try a differend one or delete the duplicate"); + public static Error BadCredentials => + new( + "TTV.API.BadCreds", + "The Credentials are invalid, resetting them and restarting the bot is recommended" + ); + public static Error BadRewardTitle => + new( + "TTV.API.BadTitle", + "The Reward Title was disallowed by twitch, try a differend one" + ); + public static Error DupeRewardTitle => + new( + "TTV.API.DupeTitle", + "The Reward with this title already exists, try a differend one or delete the duplicate" + ); } + public static class Database { - public static Error NotFound => new( - "DB.404", - "Entry not found in the database"); + public static Error NotFound => new("DB.404", "Entry not found in the database"); } } diff --git a/src/ArcadePointsBot/Common/Primitives/Result/Result.cs b/src/ArcadePointsBot/Common/Primitives/Result/Result.cs index 65a5774..cd77ea3 100644 --- a/src/ArcadePointsBot/Common/Primitives/Result/Result.cs +++ b/src/ArcadePointsBot/Common/Primitives/Result/Result.cs @@ -65,8 +65,7 @@ protected Result(bool isSuccess, Error error) /// The error in case the value is null. /// A new instance of with the specified value or an error. public static Result Create(TValue? value, Error error) - where TValue : class - => value is null ? Failure(error) : Success(value); + where TValue : class => value is null ? Failure(error) : Success(value); /// /// Returns a failure with the specified error. @@ -107,4 +106,4 @@ public static Result FirstFailureOrSuccess(params Result[] results) return Success(); } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Common/Primitives/Result/ResultExtensions.cs b/src/ArcadePointsBot/Common/Primitives/Result/ResultExtensions.cs index 377e25d..1dba4cb 100644 --- a/src/ArcadePointsBot/Common/Primitives/Result/ResultExtensions.cs +++ b/src/ArcadePointsBot/Common/Primitives/Result/ResultExtensions.cs @@ -53,8 +53,10 @@ public static Result Map(this Result result, Func /// The success result with the bound value if the current result is a success result, otherwise a failure result. /// - public static async Task Bind(this Result result, Func> func) => - result.IsSuccess ? await func(result.Value) : Result.Failure(result.Error); + public static async Task Bind( + this Result result, + Func> func + ) => result.IsSuccess ? await func(result.Value) : Result.Failure(result.Error); /// /// Binds to the result of the function and returns it. @@ -66,8 +68,10 @@ public static async Task Bind(this Result result, Func /// The success result with the bound value if the current result is a success result, otherwise a failure result. /// - public static async Task> Bind(this Result result, Func>> func) => - result.IsSuccess ? await func(result.Value) : Result.Failure(result.Error); + public static async Task> Bind( + this Result result, + Func>> func + ) => result.IsSuccess ? await func(result.Value) : Result.Failure(result.Error); /// /// Matches the success status of the result to the corresponding functions. @@ -79,7 +83,11 @@ public static async Task> Bind(this Result result, /// /// The result of the on-success function if the result is a success result, otherwise the result of the failure result. /// - public static async Task Match(this Task resultTask, Func onSuccess, Func onFailure) + public static async Task Match( + this Task resultTask, + Func onSuccess, + Func onFailure + ) { Result result = await resultTask; @@ -100,7 +108,8 @@ public static async Task Match(this Task resultTask, Func onSuc public static async Task Match( this Task> resultTask, Func onSuccess, - Func onFailure) + Func onFailure + ) { Result result = await resultTask; diff --git a/src/ArcadePointsBot/Common/Primitives/Result/ResultT.cs b/src/ArcadePointsBot/Common/Primitives/Result/ResultT.cs index 71a3e55..0d89427 100644 --- a/src/ArcadePointsBot/Common/Primitives/Result/ResultT.cs +++ b/src/ArcadePointsBot/Common/Primitives/Result/ResultT.cs @@ -21,17 +21,19 @@ public class Result : Result /// The flag indicating if the result is successful. /// The error. protected internal Result(TValue? value, bool isSuccess, Error error) - : base(isSuccess, error) - => _value = value; + : base(isSuccess, error) => _value = value; /// /// Gets the result value if the result is successful, otherwise throws an exception. /// /// The result value if the result is successful. /// when is true. - public TValue Value => IsSuccess - ? _value! - : throw new InvalidOperationException("The value of a failure result can not be accessed."); + public TValue Value => + IsSuccess + ? _value! + : throw new InvalidOperationException( + "The value of a failure result can not be accessed." + ); public static implicit operator Result(TValue value) => Success(value); } diff --git a/src/ArcadePointsBot/Common/Primitives/ServiceLifetimes.cs b/src/ArcadePointsBot/Common/Primitives/ServiceLifetimes.cs index 7aad7bc..5777937 100644 --- a/src/ArcadePointsBot/Common/Primitives/ServiceLifetimes.cs +++ b/src/ArcadePointsBot/Common/Primitives/ServiceLifetimes.cs @@ -14,4 +14,4 @@ public interface ITransient; /// /// Represents the scoped service lifetime. /// -public interface IScoped; \ No newline at end of file +public interface IScoped; diff --git a/src/ArcadePointsBot/Data/Abstractions/IUnitOfWork.cs b/src/ArcadePointsBot/Data/Abstractions/IUnitOfWork.cs index a9fd275..ff41475 100644 --- a/src/ArcadePointsBot/Data/Abstractions/IUnitOfWork.cs +++ b/src/ArcadePointsBot/Data/Abstractions/IUnitOfWork.cs @@ -1,25 +1,28 @@ -using Microsoft.EntityFrameworkCore.Storage; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Storage; namespace ArcadePointsBot.Data.Abstractions { public interface IUnitOfWork { - Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task SaveChangesAsync( + CancellationToken cancellationToken = default(CancellationToken) + ); - Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default); + Task SaveChangesAsync( + bool acceptAllChangesOnSuccess, + CancellationToken cancellationToken = default + ); int SaveChanges(); int SaveChanges(bool acceptAllChangesOnSuccess); - - IDbContextTransaction BeginTransaction(); Task BeginTransactionAsync(); void CommitTransaction(); diff --git a/src/ArcadePointsBot/Data/Abstractions/Repositories/IRepository.cs b/src/ArcadePointsBot/Data/Abstractions/Repositories/IRepository.cs index 4cc2ffe..b53dd93 100644 --- a/src/ArcadePointsBot/Data/Abstractions/Repositories/IRepository.cs +++ b/src/ArcadePointsBot/Data/Abstractions/Repositories/IRepository.cs @@ -1,15 +1,16 @@ -using ArcadePointsBot.Domain; -using Microsoft.EntityFrameworkCore.Query; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; +using ArcadePointsBot.Domain; +using Microsoft.EntityFrameworkCore.Query; namespace ArcadePointsBot.Data.Abstractions.Repositories; -public interface IEntityRepository : IReadRepository, IWriteRepository where T : IEntity +public interface IEntityRepository : IReadRepository, IWriteRepository + where T : IEntity { /// /// Finds an entity from data store. @@ -26,12 +27,14 @@ public interface IEntityRepository : IReadRepository, IWriteRepositor Task FindAsync(TId id); } -public interface IRepository where T : IEntity +public interface IRepository + where T : IEntity { IUnitOfWork UnitOfWork { get; } } -public interface IWriteRepository : IRepository where T : IEntity +public interface IWriteRepository : IRepository + where T : IEntity { /// /// Adds the entity to the data store @@ -82,7 +85,10 @@ public interface IWriteRepository : IRepository where T : IEntit /// The collection of entities to update void UpdateRange(IEnumerable entities); - public Task ExecuteUpdate(IEnumerable entities, Expression, SetPropertyCalls>> setPropertyCalls); + public Task ExecuteUpdate( + IEnumerable entities, + Expression, SetPropertyCalls>> setPropertyCalls + ); } /// @@ -112,7 +118,7 @@ public interface IReadRepository IQueryable GetAll(); /// - /// Retrieves all entities that satisfy the given condition from the data store as + /// Retrieves all entities that satisfy the given condition from the data store as /// /// Condition /// Query @@ -144,4 +150,4 @@ public interface IReadRepository /// Condition /// Entity Task GetSingleAsync(Expression> predicate); -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Data/Contexts/ApplicationDbContext.cs b/src/ArcadePointsBot/Data/Contexts/ApplicationDbContext.cs index fdfa018..465d1bb 100644 --- a/src/ArcadePointsBot/Data/Contexts/ApplicationDbContext.cs +++ b/src/ArcadePointsBot/Data/Contexts/ApplicationDbContext.cs @@ -1,13 +1,13 @@ -using ArcadePointsBot.Data.Abstractions; -using ArcadePointsBot.Domain.Rewards; -using ArcadePointsBot.ViewModels; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using ArcadePointsBot.Data.Abstractions; +using ArcadePointsBot.Domain.Rewards; +using ArcadePointsBot.ViewModels; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; namespace ArcadePointsBot.Data.Contexts; @@ -15,10 +15,9 @@ public class ApplicationDbContext : DbContext, IUnitOfWork { public DbSet Rewards { get; set; } public DbSet Actions { get; set; } - public ApplicationDbContext(DbContextOptions options) : base(options) - { - - } + + public ApplicationDbContext(DbContextOptions options) + : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -27,7 +26,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } public IDbContextTransaction BeginTransaction() => Database.BeginTransaction(); + public Task BeginTransactionAsync() => Database.BeginTransactionAsync(); + public void CommitTransaction() => Database.CommitTransaction(); + public Task CommitTransactionAsync() => Database.CommitTransactionAsync(); } diff --git a/src/ArcadePointsBot/Data/Migrations/20231022033828_InitialCreate.cs b/src/ArcadePointsBot/Data/Migrations/20231022033828_InitialCreate.cs index 4506458..2d7c71a 100644 --- a/src/ArcadePointsBot/Data/Migrations/20231022033828_InitialCreate.cs +++ b/src/ArcadePointsBot/Data/Migrations/20231022033828_InitialCreate.cs @@ -10,64 +10,71 @@ public partial class InitialCreate : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AlterDatabase() - .Annotation("MySql:CharSet", "utf8mb4"); + migrationBuilder.AlterDatabase().Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.CreateTable( - name: "Rewards", - columns: table => new - { - Id = table.Column(type: "varchar(255)", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Title = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Cost = table.Column(type: "int", nullable: false), - RequireInput = table.Column(type: "tinyint(1)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Rewards", x => x.Id); - }) + migrationBuilder + .CreateTable( + name: "Rewards", + columns: table => new + { + Id = table + .Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Title = table + .Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Cost = table.Column(type: "int", nullable: false), + RequireInput = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Rewards", x => x.Id); + } + ) .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.CreateTable( - name: "Actions", - columns: table => new - { - Id = table.Column(type: "varchar(255)", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - RewardId = table.Column(type: "varchar(255)", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Duration = table.Column(type: "int", nullable: true), - ActionType = table.Column(type: "int", nullable: false), - ActionKey = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Actions", x => x.Id); - table.ForeignKey( - name: "FK_Actions_Rewards_RewardId", - column: x => x.RewardId, - principalTable: "Rewards", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }) + migrationBuilder + .CreateTable( + name: "Actions", + columns: table => new + { + Id = table + .Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + RewardId = table + .Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Duration = table.Column(type: "int", nullable: true), + ActionType = table.Column(type: "int", nullable: false), + ActionKey = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Actions", x => x.Id); + table.ForeignKey( + name: "FK_Actions_Rewards_RewardId", + column: x => x.RewardId, + principalTable: "Rewards", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ) .Annotation("MySql:CharSet", "utf8mb4"); migrationBuilder.CreateIndex( name: "IX_Actions_RewardId", table: "Actions", - column: "RewardId"); + column: "RewardId" + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "Actions"); + migrationBuilder.DropTable(name: "Actions"); - migrationBuilder.DropTable( - name: "Rewards"); + migrationBuilder.DropTable(name: "Rewards"); } } } diff --git a/src/ArcadePointsBot/Data/Migrations/20231024011504_AddIndexCol.cs b/src/ArcadePointsBot/Data/Migrations/20231024011504_AddIndexCol.cs index d758ea3..8ba66d3 100644 --- a/src/ArcadePointsBot/Data/Migrations/20231024011504_AddIndexCol.cs +++ b/src/ArcadePointsBot/Data/Migrations/20231024011504_AddIndexCol.cs @@ -15,15 +15,14 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "Actions", type: "int", nullable: false, - defaultValue: 0); + defaultValue: 0 + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "Index", - table: "Actions"); + migrationBuilder.DropColumn(name: "Index", table: "Actions"); } } } diff --git a/src/ArcadePointsBot/Data/Migrations/20231024013830_AddEnabledCol.cs b/src/ArcadePointsBot/Data/Migrations/20231024013830_AddEnabledCol.cs index 4981ea0..3ad9e7a 100644 --- a/src/ArcadePointsBot/Data/Migrations/20231024013830_AddEnabledCol.cs +++ b/src/ArcadePointsBot/Data/Migrations/20231024013830_AddEnabledCol.cs @@ -15,15 +15,14 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "Rewards", type: "tinyint(1)", nullable: false, - defaultValue: false); + defaultValue: false + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "IsEnabled", - table: "Rewards"); + migrationBuilder.DropColumn(name: "IsEnabled", table: "Rewards"); } } } diff --git a/src/ArcadePointsBot/Data/Migrations/20231031040438_NewActionType.cs b/src/ArcadePointsBot/Data/Migrations/20231031040438_NewActionType.cs index 5b4333b..c0dff2c 100644 --- a/src/ArcadePointsBot/Data/Migrations/20231031040438_NewActionType.cs +++ b/src/ArcadePointsBot/Data/Migrations/20231031040438_NewActionType.cs @@ -16,7 +16,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "TEXT", nullable: false, oldClrType: typeof(string), - oldType: "longtext"); + oldType: "longtext" + ); migrationBuilder.AlterColumn( name: "RequireInput", @@ -24,7 +25,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "INTEGER", nullable: false, oldClrType: typeof(bool), - oldType: "tinyint(1)"); + oldType: "tinyint(1)" + ); migrationBuilder.AlterColumn( name: "IsEnabled", @@ -32,7 +34,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "INTEGER", nullable: false, oldClrType: typeof(bool), - oldType: "tinyint(1)"); + oldType: "tinyint(1)" + ); migrationBuilder.AlterColumn( name: "Cost", @@ -40,7 +43,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "INTEGER", nullable: false, oldClrType: typeof(int), - oldType: "int"); + oldType: "int" + ); migrationBuilder.AlterColumn( name: "Id", @@ -48,7 +52,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "TEXT", nullable: false, oldClrType: typeof(string), - oldType: "varchar(255)"); + oldType: "varchar(255)" + ); migrationBuilder.AlterColumn( name: "RewardId", @@ -56,7 +61,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "TEXT", nullable: false, oldClrType: typeof(string), - oldType: "varchar(255)"); + oldType: "varchar(255)" + ); migrationBuilder.AlterColumn( name: "Index", @@ -64,7 +70,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "INTEGER", nullable: false, oldClrType: typeof(int), - oldType: "int"); + oldType: "int" + ); migrationBuilder.AlterColumn( name: "Duration", @@ -73,7 +80,8 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: true, oldClrType: typeof(int), oldType: "int", - oldNullable: true); + oldNullable: true + ); migrationBuilder.AlterColumn( name: "ActionType", @@ -81,7 +89,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "INTEGER", nullable: false, oldClrType: typeof(int), - oldType: "int"); + oldType: "int" + ); migrationBuilder.AlterColumn( name: "ActionKey", @@ -89,7 +98,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "INTEGER", nullable: false, oldClrType: typeof(int), - oldType: "int"); + oldType: "int" + ); migrationBuilder.AlterColumn( name: "Id", @@ -97,7 +107,8 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "TEXT", nullable: false, oldClrType: typeof(string), - oldType: "varchar(255)"); + oldType: "varchar(255)" + ); migrationBuilder.CreateTable( name: "MouseRewardAction", @@ -118,20 +129,22 @@ protected override void Up(MigrationBuilder migrationBuilder) column: x => x.RewardId, principalTable: "Rewards", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + onDelete: ReferentialAction.Cascade + ); + } + ); migrationBuilder.CreateIndex( name: "IX_MouseRewardAction_RewardId", table: "MouseRewardAction", - column: "RewardId"); + column: "RewardId" + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "MouseRewardAction"); + migrationBuilder.DropTable(name: "MouseRewardAction"); migrationBuilder.AlterColumn( name: "Title", @@ -139,7 +152,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "longtext", nullable: false, oldClrType: typeof(string), - oldType: "TEXT"); + oldType: "TEXT" + ); migrationBuilder.AlterColumn( name: "RequireInput", @@ -147,7 +161,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "tinyint(1)", nullable: false, oldClrType: typeof(bool), - oldType: "INTEGER"); + oldType: "INTEGER" + ); migrationBuilder.AlterColumn( name: "IsEnabled", @@ -155,7 +170,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "tinyint(1)", nullable: false, oldClrType: typeof(bool), - oldType: "INTEGER"); + oldType: "INTEGER" + ); migrationBuilder.AlterColumn( name: "Cost", @@ -163,7 +179,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "int", nullable: false, oldClrType: typeof(int), - oldType: "INTEGER"); + oldType: "INTEGER" + ); migrationBuilder.AlterColumn( name: "Id", @@ -171,7 +188,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "varchar(255)", nullable: false, oldClrType: typeof(string), - oldType: "TEXT"); + oldType: "TEXT" + ); migrationBuilder.AlterColumn( name: "RewardId", @@ -179,7 +197,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "varchar(255)", nullable: false, oldClrType: typeof(string), - oldType: "TEXT"); + oldType: "TEXT" + ); migrationBuilder.AlterColumn( name: "Index", @@ -187,7 +206,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "int", nullable: false, oldClrType: typeof(int), - oldType: "INTEGER"); + oldType: "INTEGER" + ); migrationBuilder.AlterColumn( name: "Duration", @@ -196,7 +216,8 @@ protected override void Down(MigrationBuilder migrationBuilder) nullable: true, oldClrType: typeof(int), oldType: "INTEGER", - oldNullable: true); + oldNullable: true + ); migrationBuilder.AlterColumn( name: "ActionType", @@ -204,7 +225,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "int", nullable: false, oldClrType: typeof(int), - oldType: "INTEGER"); + oldType: "INTEGER" + ); migrationBuilder.AlterColumn( name: "ActionKey", @@ -212,7 +234,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "int", nullable: false, oldClrType: typeof(int), - oldType: "INTEGER"); + oldType: "INTEGER" + ); migrationBuilder.AlterColumn( name: "Id", @@ -220,7 +243,8 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "varchar(255)", nullable: false, oldClrType: typeof(string), - oldType: "TEXT"); + oldType: "TEXT" + ); } } } diff --git a/src/ArcadePointsBot/Data/Migrations/20231101142744_RewardCategory.cs b/src/ArcadePointsBot/Data/Migrations/20231101142744_RewardCategory.cs index ef4b8ce..6caa796 100644 --- a/src/ArcadePointsBot/Data/Migrations/20231101142744_RewardCategory.cs +++ b/src/ArcadePointsBot/Data/Migrations/20231101142744_RewardCategory.cs @@ -14,15 +14,14 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "Category", table: "Rewards", type: "TEXT", - nullable: true); + nullable: true + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "Category", - table: "Rewards"); + migrationBuilder.DropColumn(name: "Category", table: "Rewards"); } } } diff --git a/src/ArcadePointsBot/Data/Repositories/DataEntityRepository.cs b/src/ArcadePointsBot/Data/Repositories/DataEntityRepository.cs index f958ca2..ce03d83 100644 --- a/src/ArcadePointsBot/Data/Repositories/DataEntityRepository.cs +++ b/src/ArcadePointsBot/Data/Repositories/DataEntityRepository.cs @@ -1,20 +1,21 @@ -using ArcadePointsBot.Data.Abstractions; -using ArcadePointsBot.Data.Abstractions.Repositories; -using ArcadePointsBot.Data.Contexts; -using ArcadePointsBot.Domain; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Query; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; +using ArcadePointsBot.Data.Abstractions; +using ArcadePointsBot.Data.Abstractions.Repositories; +using ArcadePointsBot.Data.Contexts; +using ArcadePointsBot.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; using TwitchLib.Api.Helix.Models.Soundtrack; namespace ArcadePointsBot.Data.Repositories { - internal class DataEntityRepository : IEntityRepository where T : class, IEntity + internal class DataEntityRepository : IEntityRepository + where T : class, IEntity { protected readonly DbSet _entities; protected readonly ApplicationDbContext _context; @@ -112,8 +113,10 @@ public void UpdateRange(IEnumerable entities) } } - public Task ExecuteUpdate(IEnumerable entities, Expression, SetPropertyCalls>> setPropertyCalls) - => _entities.Where(x => entities.Contains(x)).ExecuteUpdateAsync(setPropertyCalls); + public Task ExecuteUpdate( + IEnumerable entities, + Expression, SetPropertyCalls>> setPropertyCalls + ) => _entities.Where(x => entities.Contains(x)).ExecuteUpdateAsync(setPropertyCalls); public T? Find(TId id) { diff --git a/src/ArcadePointsBot/Data/Repositories/RewardRepository.cs b/src/ArcadePointsBot/Data/Repositories/RewardRepository.cs index 755db18..1f5f2af 100644 --- a/src/ArcadePointsBot/Data/Repositories/RewardRepository.cs +++ b/src/ArcadePointsBot/Data/Repositories/RewardRepository.cs @@ -1,22 +1,26 @@ -using ArcadePointsBot.Data.Contexts; +using System.Threading.Tasks; +using ArcadePointsBot.Data.Contexts; using ArcadePointsBot.Domain.Rewards; using Microsoft.EntityFrameworkCore; -using System.Threading.Tasks; namespace ArcadePointsBot.Data.Repositories { internal class RewardRepository : DataEntityRepository, IRewardRepository { - public RewardRepository(ApplicationDbContext context) : base(context) - { - } + public RewardRepository(ApplicationDbContext context) + : base(context) { } - - public new TwitchReward? Find(string id) { + public new TwitchReward? Find(string id) + { return _entities.Find(id); } - public new Task FindAsync(string id) { - return _entities.Include(x => x.KeyboardActions).Include(x => x.MouseActions).FirstOrDefaultAsync(x => x.Id == id); + + public new Task FindAsync(string id) + { + return _entities + .Include(x => x.KeyboardActions) + .Include(x => x.MouseActions) + .FirstOrDefaultAsync(x => x.Id == id); } } } diff --git a/src/ArcadePointsBot/Domain/Rewards/IRewardRepository.cs b/src/ArcadePointsBot/Domain/Rewards/IRewardRepository.cs index c339350..3c8c457 100644 --- a/src/ArcadePointsBot/Domain/Rewards/IRewardRepository.cs +++ b/src/ArcadePointsBot/Domain/Rewards/IRewardRepository.cs @@ -2,7 +2,5 @@ namespace ArcadePointsBot.Domain.Rewards { - public interface IRewardRepository : IEntityRepository - { - } + public interface IRewardRepository : IEntityRepository { } } diff --git a/src/ArcadePointsBot/Domain/Rewards/KeyboardRewardAction.cs b/src/ArcadePointsBot/Domain/Rewards/KeyboardRewardAction.cs index fe48bdc..2c59ea9 100644 --- a/src/ArcadePointsBot/Domain/Rewards/KeyboardRewardAction.cs +++ b/src/ArcadePointsBot/Domain/Rewards/KeyboardRewardAction.cs @@ -1,18 +1,25 @@ -using Avalonia.Input; -using ArcadePointsBot.Util; -using ReactiveUI.Fody.Helpers; -using ReactiveUI; -using System; +using System; using System.Collections; +using ArcadePointsBot.Util; using ArcadePointsBot.ViewModels; +using Avalonia.Input; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; namespace ArcadePointsBot.Domain.Rewards; public class KeyboardRewardAction : RewardAction { - public KeyboardRewardAction() { } - public KeyboardRewardAction(string id, int index, TwitchReward reward, int? duration, KeyboardActionType actionType, Key actionKey) + + public KeyboardRewardAction( + string id, + int index, + TwitchReward reward, + int? duration, + KeyboardActionType actionType, + Key actionKey + ) { Id = id; Index = index; @@ -22,10 +29,20 @@ public KeyboardRewardAction(string id, int index, TwitchReward reward, int? dura ActionKey = actionKey; } - public static KeyboardRewardAction FromVMType(TwitchReward reward, RewardActionViewModel actionVM) + public static KeyboardRewardAction FromVMType( + TwitchReward reward, + RewardActionViewModel actionVM + ) { var actionType = (KeyboardActionType)actionVM.ActionKeyType!; var key = (Key)actionVM.ActionKey!; - return new(Guid.NewGuid().ToString(), actionVM.Index, reward, actionVM.Duration, actionType, key); + return new( + Guid.NewGuid().ToString(), + actionVM.Index, + reward, + actionVM.Duration, + actionType, + key + ); } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Domain/Rewards/MouseRewardAction.cs b/src/ArcadePointsBot/Domain/Rewards/MouseRewardAction.cs index 7d56761..a3bac65 100644 --- a/src/ArcadePointsBot/Domain/Rewards/MouseRewardAction.cs +++ b/src/ArcadePointsBot/Domain/Rewards/MouseRewardAction.cs @@ -1,13 +1,21 @@ -using Avalonia.Input; -using System; +using System; using ArcadePointsBot.ViewModels; +using Avalonia.Input; namespace ArcadePointsBot.Domain.Rewards; public class MouseRewardAction : RewardAction { public MouseRewardAction() { } - public MouseRewardAction(string id, int index, TwitchReward reward, int? duration, MouseActionType actionType, MouseButton actionKey) + + public MouseRewardAction( + string id, + int index, + TwitchReward reward, + int? duration, + MouseActionType actionType, + MouseButton actionKey + ) { Id = id; Index = index; @@ -21,6 +29,13 @@ public static MouseRewardAction FromVMType(TwitchReward reward, RewardActionView { var actionType = (MouseActionType)actionVM.ActionKeyType!; var key = (MouseButton)actionVM.ActionKey!; - return new(Guid.NewGuid().ToString(), actionVM.Index, reward, actionVM.Duration, actionType, key); + return new( + Guid.NewGuid().ToString(), + actionVM.Index, + reward, + actionVM.Duration, + actionType, + key + ); } } diff --git a/src/ArcadePointsBot/Domain/Rewards/RewardAction.cs b/src/ArcadePointsBot/Domain/Rewards/RewardAction.cs index ff608a8..f43de5a 100644 --- a/src/ArcadePointsBot/Domain/Rewards/RewardAction.cs +++ b/src/ArcadePointsBot/Domain/Rewards/RewardAction.cs @@ -2,6 +2,7 @@ using System.ComponentModel; namespace ArcadePointsBot.Domain.Rewards; + public abstract class RewardAction : IEntity { public string Id { get; set; } = null!; @@ -9,6 +10,7 @@ public abstract class RewardAction : IEntity public TwitchReward Reward { get; set; } = null!; public int? Duration { get; set; } } + public abstract class RewardAction : RewardAction where TType : struct, Enum where TKey : struct, Enum @@ -21,6 +23,7 @@ public enum ActionType { [Description("ActionType_Keyboard")] Keyboard, + [Description("ActionType_Mouse")] Mouse } @@ -29,19 +32,25 @@ public enum KeyboardActionType { [Description("KeyboardActionType_Tap")] Tap, + [Description("KeyboardActionType_Press")] Press, + [Description("KeyboardActionType_Release")] Release } + public enum MouseActionType { [Description("MouseActionType_Click")] Click, + [Description("MouseActionType_DoubleClick")] DoubleClick, + [Description("KeyboardActionType_Press")] Press, + [Description("KeyboardActionType_Release")] Release -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Domain/Rewards/TwitchReward.cs b/src/ArcadePointsBot/Domain/Rewards/TwitchReward.cs index a78f321..2630994 100644 --- a/src/ArcadePointsBot/Domain/Rewards/TwitchReward.cs +++ b/src/ArcadePointsBot/Domain/Rewards/TwitchReward.cs @@ -15,24 +15,27 @@ public class TwitchReward : IEntity, INotifyPropertyChanged private bool _isEnabled; public bool IsEnabled { - get => _isEnabled; set + get => _isEnabled; + set { _isEnabled = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsEnabled))); } } - public IList KeyboardActions { get; init; } = new List(); + public IList KeyboardActions { get; init; } = + new List(); public IList MouseActions { get; init; } = new List(); public event PropertyChangedEventHandler? PropertyChanged; - public static TwitchReward FromRemote(CustomReward remoteReward) => new TwitchReward - { - Id = remoteReward.Id, - Title = remoteReward.Title, - Cost = remoteReward.Cost, - RequireInput = remoteReward.IsUserInputRequired - }; + public static TwitchReward FromRemote(CustomReward remoteReward) => + new TwitchReward + { + Id = remoteReward.Id, + Title = remoteReward.Title, + Cost = remoteReward.Cost, + RequireInput = remoteReward.IsUserInputRequired + }; } } diff --git a/src/ArcadePointsBot/Domain/ValueObject.cs b/src/ArcadePointsBot/Domain/ValueObject.cs index 56d5c65..92b6279 100644 --- a/src/ArcadePointsBot/Domain/ValueObject.cs +++ b/src/ArcadePointsBot/Domain/ValueObject.cs @@ -63,7 +63,11 @@ public override bool Equals(object? obj) /// public override int GetHashCode() => - GetAtomicValues().Aggregate(default(int), (hashcode, value) => HashCode.Combine(hashcode, value.GetHashCode())); + GetAtomicValues() + .Aggregate( + default(int), + (hashcode, value) => HashCode.Combine(hashcode, value.GetHashCode()) + ); /// /// Gets the atomic values of the value object. diff --git a/src/ArcadePointsBot/GlobalRxExceptionHandler.cs b/src/ArcadePointsBot/GlobalRxExceptionHandler.cs index 084035c..7d60578 100644 --- a/src/ArcadePointsBot/GlobalRxExceptionHandler.cs +++ b/src/ArcadePointsBot/GlobalRxExceptionHandler.cs @@ -1,10 +1,10 @@ -using Avalonia.Controls.ApplicationLifetimes; +using System; +using System.Diagnostics; +using System.Reactive.Concurrency; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using Microsoft.Extensions.Logging; using ReactiveUI; -using System; -using System.Diagnostics; -using System.Reactive.Concurrency; namespace ArcadePointsBot { @@ -19,25 +19,38 @@ public GlobalRxExceptionHandler(ILogger logger) public void OnCompleted() { - if (Debugger.IsAttached) Debugger.Break(); - RxApp.MainThreadScheduler.Schedule(() => { throw new NotImplementedException(); }); + if (Debugger.IsAttached) + Debugger.Break(); + RxApp.MainThreadScheduler.Schedule(() => + { + throw new NotImplementedException(); + }); } public void OnError(Exception error) { - if (Debugger.IsAttached) Debugger.Break(); + if (Debugger.IsAttached) + Debugger.Break(); _logger.LogCritical(error, "Something went wrong in the UI"); - RxApp.MainThreadScheduler.Schedule(() => { throw error; }); + RxApp.MainThreadScheduler.Schedule(() => + { + throw error; + }); } public void OnNext(Exception value) { - if (Debugger.IsAttached) Debugger.Break(); + if (Debugger.IsAttached) + Debugger.Break(); _logger.LogCritical(value, "Something went wrong in the UI"); - if (App.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) + if ( + App.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime + ) Dispatcher.UIThread.Post(() => lifetime.Shutdown(value.HResult)); - RxApp.MainThreadScheduler.Schedule(() => { throw value; }); - + RxApp.MainThreadScheduler.Schedule(() => + { + throw value; + }); } } } diff --git a/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthConfig.cs b/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthConfig.cs index 9fd439f..cbf92a6 100644 --- a/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthConfig.cs +++ b/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthConfig.cs @@ -1,23 +1,24 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.CompilerServices; using System.Text.Json.Nodes; using System.Text.Json.Serialization; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; namespace ArcadePointsBot.Auth; + public class TwitchAuthConfig : INotifyPropertyChanged { private DateTimeOffset? accessTokenExpiration; private string? refreshToken; private string? identityToken; private string? accessToken; - + [Required] public string ClientId { get; init; } = string.Empty; - + [Required] public string ClientSecret { get; init; } = string.Empty; @@ -70,7 +71,8 @@ public DateTimeOffset? AccessTokenExpiration private void ParseIdToken() { - if (string.IsNullOrEmpty(IdentityToken)) return; + if (string.IsNullOrEmpty(IdentityToken)) + return; var parts = IdentityToken.Split('.'); byte[]? json = null; try @@ -90,20 +92,24 @@ private void ParseIdToken() #region INotifyPropertyChanged public event PropertyChangedEventHandler? PropertyChanged; - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null, object? value = null) + protected virtual void OnPropertyChanged( + [CallerMemberName] string? propertyName = null, + object? value = null + ) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgsEx(propertyName, value)); } - #endregion - + #endregion } + public class PropertyChangedEventArgsEx : PropertyChangedEventArgs { private readonly object? _value; public virtual object? Value => _value; - public PropertyChangedEventArgsEx(string? propertyName, object? value) : base(propertyName) + public PropertyChangedEventArgsEx(string? propertyName, object? value) + : base(propertyName) { _value = value; } diff --git a/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthenticationService.cs b/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthenticationService.cs index 43dbfb5..d2b0af8 100644 --- a/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthenticationService.cs +++ b/src/ArcadePointsBot/Infrastructure/Auth/TwitchAuthenticationService.cs @@ -1,42 +1,50 @@ -using IdentityModel.OidcClient; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System; +using System; using System.Net.Http; using System.Threading.Tasks; +using IdentityModel.OidcClient; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace ArcadePointsBot.Auth { internal class TwitchAuthenticationService : IAuthenticationService { - private const string SCOPES = "openid channel:read:redemptions user:read:chat user:bot channel:moderate channel:manage:redemptions"; + private const string SCOPES = + "openid channel:read:redemptions user:read:chat user:bot channel:moderate channel:manage:redemptions"; private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly OidcClient _oidcClient; public TwitchAuthConfig AuthConfig { get; } - public TwitchAuthenticationService(IOptions config, ILoggerFactory loggerFactory) + public TwitchAuthenticationService( + IOptions config, + ILoggerFactory loggerFactory + ) { AuthConfig = config.Value; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); - _oidcClient = new OidcClient(new OidcClientOptions - { - Authority = "https://id.twitch.tv/oauth2", - ClientId = AuthConfig.ClientId, - ClientSecret = AuthConfig.ClientSecret, - RedirectUri = "http://localhost:5000", - LoggerFactory = _loggerFactory, - Scope = SCOPES, - FilterClaims = false, - Browser = new SystemBrowser(5000), - }); + _oidcClient = new OidcClient( + new OidcClientOptions + { + Authority = "https://id.twitch.tv/oauth2", + ClientId = AuthConfig.ClientId, + ClientSecret = AuthConfig.ClientSecret, + RedirectUri = "http://localhost:5000", + LoggerFactory = _loggerFactory, + Scope = SCOPES, + FilterClaims = false, + Browser = new SystemBrowser(5000), + } + ); } public async Task RequestCredentials() { - if (string.IsNullOrEmpty(AuthConfig.AccessToken) || - AuthConfig.AccessTokenExpiration < DateTimeOffset.UtcNow) + if ( + string.IsNullOrEmpty(AuthConfig.AccessToken) + || AuthConfig.AccessTokenExpiration < DateTimeOffset.UtcNow + ) { _logger.LogInformation("Requesting Twitch credentials"); @@ -63,19 +71,25 @@ public async Task RequestCredentials() public async Task IsTokenValidAsync() { - if (string.IsNullOrEmpty(AuthConfig.AccessToken) || - AuthConfig.AccessTokenExpiration < DateTimeOffset.UtcNow) + if ( + string.IsNullOrEmpty(AuthConfig.AccessToken) + || AuthConfig.AccessTokenExpiration < DateTimeOffset.UtcNow + ) return false; using var http = new HttpClient(); - http.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth", AuthConfig.AccessToken); + http.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue( + "OAuth", + AuthConfig.AccessToken + ); var res = await http.GetAsync("https://id.twitch.tv/oauth2/validate"); return res.IsSuccessStatusCode; } public async Task EnsureValidTokenAsync() { - if (await IsTokenValidAsync()) return; + if (await IsTokenValidAsync()) + return; if (!string.IsNullOrEmpty(AuthConfig.RefreshToken)) await RefreshCredentials(); diff --git a/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationProvider.cs b/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationProvider.cs index 55c939b..364616f 100644 --- a/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationProvider.cs +++ b/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationProvider.cs @@ -1,6 +1,4 @@ -using ArcadePointsBot.Auth; -using Microsoft.Extensions.Configuration.Json; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,28 +6,34 @@ using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; +using ArcadePointsBot.Auth; +using Microsoft.Extensions.Configuration.Json; namespace ArcadePointsBot.Config { internal class WritableJsonConfigurationProvider : JsonConfigurationProvider { - public WritableJsonConfigurationProvider(JsonConfigurationSource source) : base(source) { } + public WritableJsonConfigurationProvider(JsonConfigurationSource source) + : base(source) { } public override void Set(string key, string? value) { base.Set(key, value); - if (Source.FileProvider == null) throw new InvalidOperationException("The FileProvider cannot be null"); - if (string.IsNullOrEmpty(Source.Path)) throw new InvalidOperationException("The Sourcepath cannot be null"); + if (Source.FileProvider == null) + throw new InvalidOperationException("The FileProvider cannot be null"); + if (string.IsNullOrEmpty(Source.Path)) + throw new InvalidOperationException("The Sourcepath cannot be null"); var fullPath = Source.FileProvider.GetFileInfo(Source.Path).PhysicalPath; var json = File.ReadAllText(fullPath!); var config = JsonNode.Parse(json); - if (config is null) throw new JsonException("Malformed app config"); + if (config is null) + throw new JsonException("Malformed app config"); var configObj = config.AsObject(); var keyParts = key.Split(':'); if (keyParts.Length > 1) { - if(configObj[keyParts[0]] is null) + if (configObj[keyParts[0]] is null) configObj[keyParts[0]] = new JsonObject(); configObj[keyParts[0]]![keyParts[1]] = value; } @@ -37,10 +41,10 @@ public override void Set(string key, string? value) { configObj[key] = value; } - File.WriteAllText(fullPath!, configObj.ToJsonString(new JsonSerializerOptions - { - WriteIndented = true - })); + File.WriteAllText( + fullPath!, + configObj.ToJsonString(new JsonSerializerOptions { WriteIndented = true }) + ); } } diff --git a/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationSource.cs b/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationSource.cs index d398844..2258390 100644 --- a/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationSource.cs +++ b/src/ArcadePointsBot/Infrastructure/Config/WritableJsonConfigurationSource.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; namespace ArcadePointsBot.Config { diff --git a/src/ArcadePointsBot/Infrastructure/Interop/Windows/NativeMethods.cs b/src/ArcadePointsBot/Infrastructure/Interop/Windows/NativeMethods.cs index f82397c..9bf1017 100644 --- a/src/ArcadePointsBot/Infrastructure/Interop/Windows/NativeMethods.cs +++ b/src/ArcadePointsBot/Infrastructure/Interop/Windows/NativeMethods.cs @@ -1,11 +1,11 @@ //https://gist.github.com/DrustZ/640912b9d5cb745a3a56971c9bd58ac7 -using Avalonia.Input; -using Avalonia.Win32.Input; using System; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Avalonia.Input; +using Avalonia.Win32.Input; namespace ArcadePointsBot.Interop.Windows; @@ -50,6 +50,7 @@ internal struct INPUTUNION { [FieldOffset(0)] internal MOUSEINPUT mouseInput; + [FieldOffset(0)] internal KEYBDINPUT keyboardInput; }; @@ -106,6 +107,7 @@ internal enum SendMouseInputFlags #endregion } + [SupportedOSPlatform("windows")] public static class Keyboard { @@ -153,7 +155,8 @@ private static void SendKeyboardInput(Key key, bool press) NativeMethods.INPUT ki = new NativeMethods.INPUT(); ki.type = NativeMethods.InputKeyboard; ki.union.keyboardInput.wVk = (short)KeyInterop.VirtualKeyFromKey(key); - ki.union.keyboardInput.wScan = (short)NativeMethods.MapVirtualKey(ki.union.keyboardInput.wVk, 0); + ki.union.keyboardInput.wScan = (short) + NativeMethods.MapVirtualKey(ki.union.keyboardInput.wVk, 0); int dwFlags = 0; @@ -189,27 +192,29 @@ private static void SendKeyboardInput(Key key, bool press) // CTRL keys on the right-hand side of the keyboard; the INS, DEL, HOME, END, PAGE UP, // PAGE DOWN, and arrow keys in the clusters to the left of the numeric keypad; the NUM LOCK // key; the BREAK (CTRL+PAUSE) key; the PRINT SCRN key; and the divide (/) and ENTER keys in - // the numeric keypad. The extended-key flag is set if the key is an extended key. + // the numeric keypad. The extended-key flag is set if the key is an extended key. // // - docs appear to be incorrect. Use of Spy++ indicates that break is not an extended key. // Also, menu key and windows keys also appear to be extended. - private static readonly Key[] ExtendedKeys = new Key[] { - Key.RightAlt, - Key.RightCtrl, - Key.NumLock, - Key.Insert, - Key.Delete, - Key.Home, - Key.End, - Key.Prior, - Key.Next, - Key.Up, - Key.Down, - Key.Left, - Key.Right, - Key.Apps, - Key.RWin, - Key.LWin }; + private static readonly Key[] ExtendedKeys = new Key[] + { + Key.RightAlt, + Key.RightCtrl, + Key.NumLock, + Key.Insert, + Key.Delete, + Key.Home, + Key.End, + Key.Prior, + Key.Next, + Key.Up, + Key.Down, + Key.Left, + Key.Right, + Key.Apps, + Key.RWin, + Key.LWin + }; // Note that there are no distinct values for the following keys: // numpad divide // numpad enter @@ -217,7 +222,6 @@ private static void SendKeyboardInput(Key key, bool press) #endregion } - [SupportedOSPlatform("windows")] public static class Mouse { @@ -226,11 +230,13 @@ public static void Click(MouseButton mouseButton) Down(mouseButton); Up(mouseButton); } + public static void DoubleClick(MouseButton mouseButton) { Click(mouseButton); Click(mouseButton); } + public static void Down(MouseButton mouseButton) { switch (mouseButton) @@ -245,15 +251,22 @@ public static void Down(MouseButton mouseButton) SendMouseButtonInput(NativeMethods.SendMouseInputFlags.MiddleDown); break; case MouseButton.XButton1: - SendMouseButtonInput(NativeMethods.SendMouseInputFlags.XDown, NativeMethods.XButton1); + SendMouseButtonInput( + NativeMethods.SendMouseInputFlags.XDown, + NativeMethods.XButton1 + ); break; case MouseButton.XButton2: - SendMouseButtonInput(NativeMethods.SendMouseInputFlags.XDown, NativeMethods.XButton2); + SendMouseButtonInput( + NativeMethods.SendMouseInputFlags.XDown, + NativeMethods.XButton2 + ); break; default: throw new InvalidOperationException("Unsupported MouseButton input."); } } + public static void Up(MouseButton mouseButton) { switch (mouseButton) @@ -287,7 +300,11 @@ private static void SendMouseButtonInput(NativeMethods.SendMouseInputFlags flags throw new Win32Exception(Marshal.GetLastWin32Error()); } } - private static NativeMethods.INPUT CreateMouseInput(NativeMethods.SendMouseInputFlags flags, int data = 0) + + private static NativeMethods.INPUT CreateMouseInput( + NativeMethods.SendMouseInputFlags flags, + int data = 0 + ) { int intflags = (int)flags; @@ -295,10 +312,7 @@ private static NativeMethods.INPUT CreateMouseInput(NativeMethods.SendMouseInput { intflags |= NativeMethods.MouseeventfVirtualdesk; } - NativeMethods.INPUT mi = new() - { - type = NativeMethods.InputMouse - }; + NativeMethods.INPUT mi = new() { type = NativeMethods.InputMouse }; mi.union.mouseInput.dx = 0; mi.union.mouseInput.dy = 0; mi.union.mouseInput.mouseData = data; @@ -307,4 +321,4 @@ private static NativeMethods.INPUT CreateMouseInput(NativeMethods.SendMouseInput mi.union.mouseInput.dwExtraInfo = new IntPtr(0); return mi; } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Infrastructure/SystemBrowser.cs b/src/ArcadePointsBot/Infrastructure/SystemBrowser.cs index d4ef11e..863302b 100644 --- a/src/ArcadePointsBot/Infrastructure/SystemBrowser.cs +++ b/src/ArcadePointsBot/Infrastructure/SystemBrowser.cs @@ -1,18 +1,19 @@ -using IdentityModel.OidcClient.Browser; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using System; +using System; using System.Diagnostics; -using System.Net.Sockets; +using System.IO; using System.Net; +using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.IO; +using IdentityModel.OidcClient.Browser; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; namespace ArcadePointsBot; + public class SystemBrowser : IBrowser { public int Port { get; } @@ -41,7 +42,10 @@ private int GetRandomUnusedPort() return port; } - public async Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task InvokeAsync( + BrowserOptions options, + CancellationToken cancellationToken = default(CancellationToken) + ) { using (var listener = new LoopbackHttpListener(Port, _path)) { @@ -52,18 +56,34 @@ private int GetRandomUnusedPort() var result = await listener.WaitForCallbackAsync(); if (string.IsNullOrWhiteSpace(result)) { - return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Empty response." }; + return new BrowserResult + { + ResultType = BrowserResultType.UnknownError, + Error = "Empty response." + }; } - return new BrowserResult { Response = result, ResultType = BrowserResultType.Success }; + return new BrowserResult + { + Response = result, + ResultType = BrowserResultType.Success + }; } catch (TaskCanceledException ex) { - return new BrowserResult { ResultType = BrowserResultType.Timeout, Error = ex.Message }; + return new BrowserResult + { + ResultType = BrowserResultType.Timeout, + Error = ex.Message + }; } catch (Exception ex) { - return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = ex.Message }; + return new BrowserResult + { + ResultType = BrowserResultType.UnknownError, + Error = ex.Message + }; } } } @@ -80,7 +100,9 @@ public static void OpenBrowser(string url) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { url = url.Replace("&", "^&"); - Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true }); + Process.Start( + new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true } + ); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { @@ -111,15 +133,12 @@ public class LoopbackHttpListener : IDisposable public LoopbackHttpListener(int port, string? path = null) { path ??= string.Empty; - if (path.StartsWith("/")) path = path[1..]; + if (path.StartsWith("/")) + path = path[1..]; _url = $"http://127.0.0.1:{port}/{path}"; - _host = new WebHostBuilder() - .UseKestrel() - .UseUrls(_url) - .Configure(Configure) - .Build(); + _host = new WebHostBuilder().UseKestrel().UseUrls(_url).Configure(Configure).Build(); _host.Start(); } @@ -142,7 +161,12 @@ void Configure(IApplicationBuilder app) } else if (ctx.Request.Method == "POST") { - if (!ctx.Request.ContentType!.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) + if ( + !ctx.Request.ContentType!.Equals( + "application/x-www-form-urlencoded", + StringComparison.OrdinalIgnoreCase + ) + ) { ctx.Response.StatusCode = 415; } diff --git a/src/ArcadePointsBot/Program.cs b/src/ArcadePointsBot/Program.cs index acb6482..42bb67a 100644 --- a/src/ArcadePointsBot/Program.cs +++ b/src/ArcadePointsBot/Program.cs @@ -1,27 +1,28 @@ -using Avalonia; -using Avalonia.ReactiveUI; -using System; +using System; using System.Globalization; +using Avalonia; +using Avalonia.ReactiveUI; namespace ArcadePointsBot { internal class Program { public static IServiceProvider ServiceProvider { get; private set; } = null!; + // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. [STAThread] - public static void Main(string[] args) => - BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + public static void Main(string[] args) => + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); // Avalonia configuration, don't remove; also used by visual designer. - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .UsePlatformDetect() - .WithInterFont() - .LogToTrace() - .UseReactiveUI(); + public static AppBuilder BuildAvaloniaApp() => + AppBuilder + .Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI(); } } diff --git a/src/ArcadePointsBot/Util/EnumUtils.cs b/src/ArcadePointsBot/Util/EnumUtils.cs index 7938fad..8a8899c 100644 --- a/src/ArcadePointsBot/Util/EnumUtils.cs +++ b/src/ArcadePointsBot/Util/EnumUtils.cs @@ -1,43 +1,57 @@ -using Avalonia; -using Avalonia.Data.Converters; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Reflection; +using Avalonia; +using Avalonia.Data.Converters; namespace ArcadePointsBot.Util; public static class EnumUtils { - public static IEnumerable GetValues() where T : Enum - => typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static).Select(x => x.GetValue(typeof(T))); + public static IEnumerable GetValues() + where T : Enum => + typeof(T) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Select(x => x.GetValue(typeof(T))); public static EnumDescription ToDescription(this Enum value) { string? description; string? help = null; - - var attributes = value.GetType().GetField(value.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), false); + + var attributes = value + .GetType() + .GetField(value.ToString()) + ?.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes?.Any() ?? false) { - description = Resources.Enums.ResourceManager.GetString((attributes.First() as DescriptionAttribute)!.Description, Resources.Enums.Culture); + description = Resources.Enums.ResourceManager.GetString( + (attributes.First() as DescriptionAttribute)!.Description, + Resources.Enums.Culture + ); } else { TextInfo ti = CultureInfo.CurrentCulture.TextInfo; description = ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " "))); } - - if(description!.IndexOf(';') is var index && index != -1) + + if (description!.IndexOf(';') is var index && index != -1) { help = description.Substring(index + 1); description = description.Substring(0, index); } - return new EnumDescription() { Value = value, Description = description, Help = help }; + return new EnumDescription() + { + Value = value, + Description = description, + Help = help + }; } } @@ -89,24 +103,32 @@ public class EnumDescriptionConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if(value is null) return null; + if (value is null) + return null; if (value.GetType().IsEnum) { return ((Enum)value).ToDescription(); } throw new ArgumentException("Convert:Value must be an enum."); } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture + ) { - if(value is null) return null; - if(value is EnumDescription enumDescription) + if (value is null) + return null; + if (value is EnumDescription enumDescription) { return enumDescription.Value; } throw new ArgumentException("ConvertBack:EnumDescription must be an enum."); } } + public class EnumDescriptionsConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) @@ -122,10 +144,15 @@ public class EnumDescriptionsConverter : IValueConverter } throw new ArgumentException("Convert:Value must be an enum."); } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture + ) { - if(value is IEnumerable enumDescriptions) + if (value is IEnumerable enumDescriptions) { return enumDescriptions.Select(x => x.Value); } @@ -137,12 +164,12 @@ public record EnumDescription { public object Value { get; set; } = null!; - public string Description { get; set; }= null!; - + public string Description { get; set; } = null!; + public string? Help { get; set; } - + public override string ToString() { return Description; } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Util/ResourceHostExtensions.cs b/src/ArcadePointsBot/Util/ResourceHostExtensions.cs index 0718062..ae26887 100644 --- a/src/ArcadePointsBot/Util/ResourceHostExtensions.cs +++ b/src/ArcadePointsBot/Util/ResourceHostExtensions.cs @@ -1,10 +1,10 @@ -using Avalonia.Controls; -using Microsoft.Extensions.DependencyInjection; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Controls; +using Microsoft.Extensions.DependencyInjection; namespace ArcadePointsBot.Util { @@ -14,13 +14,15 @@ internal static class ResourceHostExtensions // https://www.reddit.com/r/AvaloniaUI/comments/ssplp9/comment/hx0e3zi/?utm_source=share&utm_medium=web2x&context=3 public static IServiceProvider GetServiceProvider(this IResourceHost control) { - return (IServiceProvider?)control.FindResource(typeof(IServiceProvider)) ?? - throw new Exception("Expected service provider missing"); + return (IServiceProvider?)control.FindResource(typeof(IServiceProvider)) + ?? throw new Exception("Expected service provider missing"); } public static T CreateInstance(this IResourceHost control) { - return ActivatorUtilities.CreateInstance(control.GetServiceProvider().CreateScope().ServiceProvider); + return ActivatorUtilities.CreateInstance( + control.GetServiceProvider().CreateScope().ServiceProvider + ); } } } diff --git a/src/ArcadePointsBot/ViewLocator.cs b/src/ArcadePointsBot/ViewLocator.cs index 76c7825..cdbdbf4 100644 --- a/src/ArcadePointsBot/ViewLocator.cs +++ b/src/ArcadePointsBot/ViewLocator.cs @@ -1,23 +1,28 @@ -using Avalonia.Controls; -using Avalonia.Controls.Templates; -using ArcadePointsBot.ViewModels; -using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; +using ArcadePointsBot.ViewModels; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Microsoft.Extensions.DependencyInjection; namespace ArcadePointsBot { public static class ViewLocatorHelpers { - public static IServiceCollection AddView(this IServiceCollection services) + public static IServiceCollection AddView( + this IServiceCollection services + ) where TView : Control, new() where TViewModel : ViewModelBase { - services.AddSingleton(new ViewLocator.ViewLocationDescriptor(typeof(TViewModel), () => new TView())); + services.AddSingleton( + new ViewLocator.ViewLocationDescriptor(typeof(TViewModel), () => new TView()) + ); return services; } } + public class ViewLocator : IDataTemplate { private readonly Dictionary> _views; @@ -28,7 +33,10 @@ public ViewLocator(IEnumerable descriptors) } public Control Build(object? param) => _views[param!.GetType()](); - public bool Match(object? param) => param is not null && _views.ContainsKey(param.GetType()); + + public bool Match(object? param) => + param is not null && _views.ContainsKey(param.GetType()); + public record ViewLocationDescriptor(Type ViewModel, Func Factory); } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/ViewModels/CreateRewardWindowViewModel.cs b/src/ArcadePointsBot/ViewModels/CreateRewardWindowViewModel.cs index 87f2a14..e9e9186 100644 --- a/src/ArcadePointsBot/ViewModels/CreateRewardWindowViewModel.cs +++ b/src/ArcadePointsBot/ViewModels/CreateRewardWindowViewModel.cs @@ -1,4 +1,10 @@ -using ArcadePointsBot.Domain.Rewards; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using ArcadePointsBot.Domain.Rewards; using ArcadePointsBot.Services; using ArcadePointsBot.Util; using DynamicData; @@ -6,12 +12,6 @@ using Microsoft.Extensions.DependencyInjection; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; namespace ArcadePointsBot.ViewModels { @@ -22,6 +22,7 @@ public partial class CreateRewardWindowViewModel : ViewModelBase [Reactive] public string? Title { get; set; } + [Reactive] public string? Category { get; set; } @@ -45,18 +46,34 @@ public partial class CreateRewardWindowViewModel : ViewModelBase public CreateRewardWindowViewModel(IServiceProvider serviceProvider) { _serviceScope = serviceProvider.CreateScope(); - _pointRewardService = _serviceScope.ServiceProvider.GetRequiredService(); - Actions.ToObservableChangeSet(x => x).ToCollection().Select(x => x.Any()).ToPropertyEx(this, x => x.HasActions); - CreateTwitchRewardCommand = ReactiveCommand.CreateFromTask(CreateTwitchReward, + _pointRewardService = + _serviceScope.ServiceProvider.GetRequiredService(); + Actions + .ToObservableChangeSet(x => x) + .ToCollection() + .Select(x => x.Any()) + .ToPropertyEx(this, x => x.HasActions); + CreateTwitchRewardCommand = ReactiveCommand.CreateFromTask( + CreateTwitchReward, Observable.CombineLatest( IsBusyObservable, - this.WhenAnyValue(x => x.Title, x => x.Cost, x => x.HasActions, (title, cost, hasActions) => - !string.IsNullOrEmpty(title) && cost >= 10 && hasActions), - (isBusy, a) => - isBusy && a)); - AddActionCommand = ReactiveCommand.Create(() => Actions.Add(new RewardActionViewModel(Actions.Count))); + this.WhenAnyValue( + x => x.Title, + x => x.Cost, + x => x.HasActions, + (title, cost, hasActions) => + !string.IsNullOrEmpty(title) && cost >= 10 && hasActions + ), + (isBusy, a) => isBusy && a + ) + ); + AddActionCommand = ReactiveCommand.Create( + () => Actions.Add(new RewardActionViewModel(Actions.Count)) + ); DupeActionCommand = ReactiveCommand.Create(DuplicateAction); - RemoveActionCommand = ReactiveCommand.Create(action => Actions.Remove(action)); + RemoveActionCommand = ReactiveCommand.Create(action => + Actions.Remove(action) + ); } void DuplicateAction(RewardActionViewModel action) @@ -74,9 +91,14 @@ void DuplicateAction(RewardActionViewModel action) async Task CreateTwitchReward() { Errors.Clear(); - var result = await _pointRewardService - .CreateReward(Title!, Category, Cost, RequireInput, Actions); - if(result.IsSuccess) + var result = await _pointRewardService.CreateReward( + Title!, + Category, + Cost, + RequireInput, + Actions + ); + if (result.IsSuccess) return result.Value; Errors.Add(result.Error); return null; diff --git a/src/ArcadePointsBot/ViewModels/DesignData.cs b/src/ArcadePointsBot/ViewModels/DesignData.cs index ab1e317..c1f846e 100644 --- a/src/ArcadePointsBot/ViewModels/DesignData.cs +++ b/src/ArcadePointsBot/ViewModels/DesignData.cs @@ -1,25 +1,30 @@ -using Avalonia; +using System.Diagnostics; +using System.Threading; using ArcadePointsBot; using ArcadePointsBot.Services; using ArcadePointsBot.ViewModels; +using Avalonia; using Microsoft.Extensions.DependencyInjection; -using System.Diagnostics; -using System.Threading; namespace ArcadePointsBot; +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. /// /// Used by for the Designer as a source of generated view models /// public static class DesignData { - public static MainWindowViewModel MainWindowViewModel { get; } = - ((App)Avalonia.Application.Current!).GlobalHost!.Services.GetRequiredService(); - + public static MainWindowViewModel MainWindowViewModel { get; } = + ( + (App)Avalonia.Application.Current! + ).GlobalHost!.Services.GetRequiredService(); public static CreateRewardWindowViewModel CreateRewardWindowViewModel { get; } = - ((App)Avalonia.Application.Current!).GlobalHost!.Services.GetRequiredService(); + ( + (App)Avalonia.Application.Current! + ).GlobalHost!.Services.GetRequiredService(); - public static EditRewardViewModel EditRewardViewModel { get; } = new EditRewardViewModel(null, - new ArcadePointsBot.Domain.Rewards.TwitchReward()); + public static EditRewardViewModel EditRewardViewModel { get; } = + new EditRewardViewModel(null, new ArcadePointsBot.Domain.Rewards.TwitchReward()); } +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. diff --git a/src/ArcadePointsBot/ViewModels/EditRewardViewModel.cs b/src/ArcadePointsBot/ViewModels/EditRewardViewModel.cs index 4d1d0f7..1042ffb 100644 --- a/src/ArcadePointsBot/ViewModels/EditRewardViewModel.cs +++ b/src/ArcadePointsBot/ViewModels/EditRewardViewModel.cs @@ -1,4 +1,11 @@ -using ArcadePointsBot.Domain.Rewards; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using ArcadePointsBot.Domain.Rewards; using ArcadePointsBot.Services; using ArcadePointsBot.Util; using DynamicData; @@ -6,13 +13,6 @@ using Microsoft.Extensions.DependencyInjection; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; namespace ArcadePointsBot.ViewModels; @@ -20,8 +20,10 @@ public partial class EditRewardViewModel : ViewModelBase { private readonly TwitchPointRewardService _pointRewardService; private readonly TwitchReward _oldReward; + [Reactive] public string Title { get; set; } + [Reactive] public string? Category { get; set; } @@ -51,20 +53,40 @@ public EditRewardViewModel(TwitchPointRewardService pointRewardService, TwitchRe Cost = _oldReward.Cost; RequireInput = _oldReward.RequireInput; - Actions = new(new List() + Actions = new( + new List() .Concat(reward.KeyboardActions.Select(RewardActionViewModel.FromAction)) - .Concat(reward.MouseActions.Select(RewardActionViewModel.FromAction)).OrderBy(x => x.Index).ToList()); - Actions.ToObservableChangeSet(x => x).ToCollection().Select(x => x.Any()).ToPropertyEx(this, x => x.HasActions); + .Concat(reward.MouseActions.Select(RewardActionViewModel.FromAction)) + .OrderBy(x => x.Index) + .ToList() + ); + Actions + .ToObservableChangeSet(x => x) + .ToCollection() + .Select(x => x.Any()) + .ToPropertyEx(this, x => x.HasActions); - EditTwitchRewardCommand = ReactiveCommand.CreateFromTask(EditTwitchReward, + EditTwitchRewardCommand = ReactiveCommand.CreateFromTask( + EditTwitchReward, Observable.CombineLatest( IsBusyObservable, - this.WhenAnyValue(x => x.Title, x => x.Cost, x => x.HasActions, (title, cost, hasActions) => - !string.IsNullOrEmpty(title) && cost >= 10 && hasActions), - (isBusy, a) => isBusy && a)); - AddActionCommand = ReactiveCommand.Create(() => Actions.Add(new RewardActionViewModel(Actions.Count))); + this.WhenAnyValue( + x => x.Title, + x => x.Cost, + x => x.HasActions, + (title, cost, hasActions) => + !string.IsNullOrEmpty(title) && cost >= 10 && hasActions + ), + (isBusy, a) => isBusy && a + ) + ); + AddActionCommand = ReactiveCommand.Create( + () => Actions.Add(new RewardActionViewModel(Actions.Count)) + ); DupeActionCommand = ReactiveCommand.Create(DuplicateAction); - RemoveActionCommand = ReactiveCommand.Create(action => Actions.Remove(action)); + RemoveActionCommand = ReactiveCommand.Create(action => + Actions.Remove(action) + ); } void DuplicateAction(RewardActionViewModel action) @@ -92,4 +114,4 @@ void DuplicateAction(RewardActionViewModel action) Errors.Add(result.Error); return null; } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/ViewModels/KeyboardRewardActionVM.cs b/src/ArcadePointsBot/ViewModels/KeyboardRewardActionVM.cs index cbd8306..13fbdfe 100644 --- a/src/ArcadePointsBot/ViewModels/KeyboardRewardActionVM.cs +++ b/src/ArcadePointsBot/ViewModels/KeyboardRewardActionVM.cs @@ -1,9 +1,9 @@ -using ArcadePointsBot.Domain.Rewards; +using System.Collections; +using ArcadePointsBot.Domain.Rewards; using ArcadePointsBot.Util; -using ReactiveUI.Fody.Helpers; -using ReactiveUI; -using System.Collections; using Avalonia.Input; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; namespace ArcadePointsBot.ViewModels; @@ -12,14 +12,17 @@ public class KeyboardRewardActionVM : ReactiveObject public IEnumerable ActionValues { get; } = EnumUtils.GetValues(); public IEnumerable KeyValues { get; } = EnumUtils.GetValues(); - [Reactive] public int? Duration { get; set; } - [Reactive] public KeyboardActionType? ActionType { get; set; } - [Reactive] public Key? ActionKey { get; set; } + [Reactive] + public int? Duration { get; set; } + + [Reactive] + public KeyboardActionType? ActionType { get; set; } + + [Reactive] + public Key? ActionKey { get; set; } public int Index { get; set; } - public KeyboardRewardActionVM() - { - } + public KeyboardRewardActionVM() { } public KeyboardRewardActionVM(int index) { diff --git a/src/ArcadePointsBot/ViewModels/MainWindowViewModel.cs b/src/ArcadePointsBot/ViewModels/MainWindowViewModel.cs index afa70a8..01b5438 100644 --- a/src/ArcadePointsBot/ViewModels/MainWindowViewModel.cs +++ b/src/ArcadePointsBot/ViewModels/MainWindowViewModel.cs @@ -1,23 +1,23 @@ -using Avalonia.Collections; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Threading; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; using ArcadePointsBot.Auth; +using ArcadePointsBot.Domain.Rewards; using ArcadePointsBot.Services; using ArcadePointsBot.Views; +using Avalonia.Collections; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Threading; using DynamicData; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; -using ArcadePointsBot.Domain.Rewards; namespace ArcadePointsBot.ViewModels { @@ -47,36 +47,59 @@ public bool IsAuthed public MainWindowViewModel(IServiceProvider serviceProvider, TwitchWorker worker) { _serviceScope = serviceProvider.CreateScope(); - _authenticationService = _serviceScope.ServiceProvider.GetRequiredService(); - _rewardService = _serviceScope.ServiceProvider.GetRequiredService(); + _authenticationService = + _serviceScope.ServiceProvider.GetRequiredService(); + _rewardService = + _serviceScope.ServiceProvider.GetRequiredService(); StatusText = "Checking Auth status"; _worker = worker; _worker.PropertyChanged += WorkerPropertyChanged; - - - CreateRewardCommand = ReactiveCommand.CreateFromTask(CreateReward, - Observable.CombineLatest(IsBusyObservable, this.WhenAny(x => x.IsAuthed, x => x.Value), (isBusy, isAuthed) => isBusy && isAuthed)); - EditRewardCommand = ReactiveCommand.CreateFromTask(EditReward, - Observable.CombineLatest(IsBusyObservable, this.WhenAny(x => x.IsAuthed, x => x.Value), (isBusy, isAuthed) => isBusy && isAuthed)); - DeleteRewardCommand = ReactiveCommand.CreateFromTask(DeleteReward, IsBusyObservable); + CreateRewardCommand = ReactiveCommand.CreateFromTask( + CreateReward, + Observable.CombineLatest( + IsBusyObservable, + this.WhenAny(x => x.IsAuthed, x => x.Value), + (isBusy, isAuthed) => isBusy && isAuthed + ) + ); + EditRewardCommand = ReactiveCommand.CreateFromTask( + EditReward, + Observable.CombineLatest( + IsBusyObservable, + this.WhenAny(x => x.IsAuthed, x => x.Value), + (isBusy, isAuthed) => isBusy && isAuthed + ) + ); + DeleteRewardCommand = ReactiveCommand.CreateFromTask( + DeleteReward, + IsBusyObservable + ); } - private void WorkerPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + private void WorkerPropertyChanged( + object? sender, + System.ComponentModel.PropertyChangedEventArgs e + ) { - if (e.PropertyName != nameof(_worker.Status)) return; + if (e.PropertyName != nameof(_worker.Status)) + return; WorkerStatus = _worker.Status; } private async Task CreateReward() { IsBusy = true; - if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + if ( + Avalonia.Application.Current?.ApplicationLifetime + is IClassicDesktopStyleApplicationLifetime desktop + ) { var reward = await new CreateRewardWindow() { - DataContext = _serviceScope.ServiceProvider.GetRequiredService(), + DataContext = + _serviceScope.ServiceProvider.GetRequiredService(), }.ShowDialog(desktop.MainWindow!); if (reward != null) { @@ -89,11 +112,17 @@ private async Task CreateReward() private async Task EditReward(TwitchReward reward) { IsBusy = true; - if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + if ( + Avalonia.Application.Current?.ApplicationLifetime + is IClassicDesktopStyleApplicationLifetime desktop + ) { var res = await new EditRewardWindow() { - DataContext = new EditRewardViewModel(_rewardService, await _rewardService.GetReward(reward.Id)), + DataContext = new EditRewardViewModel( + _rewardService, + await _rewardService.GetReward(reward.Id) + ), }.ShowDialog(desktop.MainWindow!); if (res != null) _rewardList.Replace(reward, res); @@ -101,7 +130,6 @@ private async Task EditReward(TwitchReward reward) IsBusy = false; } - private async Task DeleteReward(TwitchReward reward) { IsBusy = true; @@ -138,7 +166,10 @@ public async Task Login() return; } - if (_authenticationService.AuthConfig.AccessTokenExpiration < System.DateTimeOffset.UtcNow) + if ( + _authenticationService.AuthConfig.AccessTokenExpiration + < System.DateTimeOffset.UtcNow + ) { StatusText = "Accestoken expired, refreshing token"; var result = await _authenticationService.RefreshCredentials(); @@ -161,11 +192,18 @@ public async void ChangeRewardEnabled(TwitchReward reward) internal async Task BulkDisable(List toDisableIds) { - if(toDisableIds.Count == 0) return; + if (toDisableIds.Count == 0) + return; await _rewardService.BulkUpdateRewards( _rewardList - .Where(x => toDisableIds.Contains(x.Id)) - .Select(x => { x.IsEnabled = false; return x; }), u => u.SetProperty(p => p.IsEnabled, false)); + .Where(x => toDisableIds.Contains(x.Id)) + .Select(x => + { + x.IsEnabled = false; + return x; + }), + u => u.SetProperty(p => p.IsEnabled, false) + ); //foreach (var id in toDisableIds) //{ // var reward = _rewardList.FirstOrDefault(x => x.Id == id); @@ -177,11 +215,18 @@ await _rewardService.BulkUpdateRewards( internal async Task BulkEnable(List toEnableIds) { - if(toEnableIds.Count == 0) return; + if (toEnableIds.Count == 0) + return; await _rewardService.BulkUpdateRewards( _rewardList - .Where(x => toEnableIds.Contains(x.Id)) - .Select(x => { x.IsEnabled = true; return x; }), u => u.SetProperty(p => p.IsEnabled, true)); + .Where(x => toEnableIds.Contains(x.Id)) + .Select(x => + { + x.IsEnabled = true; + return x; + }), + u => u.SetProperty(p => p.IsEnabled, true) + ); //foreach (var id in toEnableIds) //{ // var reward = _rewardList.FirstOrDefault(x => x.Id == id); @@ -195,9 +240,13 @@ internal async void StopWorker() { await _worker.StopAsync(default); } + internal void StartWorker() { - Task.Factory.StartNew(async () => await _worker.StartAsync(default), TaskCreationOptions.LongRunning); + Task.Factory.StartNew( + async () => await _worker.StartAsync(default), + TaskCreationOptions.LongRunning + ); } } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/ViewModels/MouseRewardActionVM.cs b/src/ArcadePointsBot/ViewModels/MouseRewardActionVM.cs index b61ff48..accd5c8 100644 --- a/src/ArcadePointsBot/ViewModels/MouseRewardActionVM.cs +++ b/src/ArcadePointsBot/ViewModels/MouseRewardActionVM.cs @@ -1,9 +1,9 @@ -using ArcadePointsBot.Domain.Rewards; +using System.Collections; +using ArcadePointsBot.Domain.Rewards; using ArcadePointsBot.Util; -using ReactiveUI.Fody.Helpers; -using ReactiveUI; -using System.Collections; using Avalonia.Input; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; namespace ArcadePointsBot.ViewModels; @@ -12,14 +12,17 @@ public class MouseRewardActionVM : ReactiveObject public IEnumerable ActionValues { get; } = EnumUtils.GetValues(); public IEnumerable KeyValues { get; } = EnumUtils.GetValues(); - [Reactive] public int? Duration { get; set; } - [Reactive] public MouseActionType? ActionType { get; set; } - [Reactive] public MouseButton? ActionKey { get; set; } + [Reactive] + public int? Duration { get; set; } + + [Reactive] + public MouseActionType? ActionType { get; set; } + + [Reactive] + public MouseButton? ActionKey { get; set; } public int Index { get; set; } - public MouseRewardActionVM() - { - } + public MouseRewardActionVM() { } public MouseRewardActionVM(int index) { diff --git a/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs b/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs index e0ffd17..3f66fda 100644 --- a/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs +++ b/src/ArcadePointsBot/ViewModels/RewardActionViewModel.cs @@ -1,27 +1,31 @@ -using ReactiveUI; -using ArcadePointsBot.Util; +using System; using System.Collections; +using ArcadePointsBot.Domain.Rewards; +using ArcadePointsBot.Util; using Avalonia.Input; +using ReactiveUI; using ReactiveUI.Fody.Helpers; -using System; -using ArcadePointsBot.Domain.Rewards; namespace ArcadePointsBot.ViewModels; public class RewardActionViewModel : ReactiveObject { public string? Id { get; init; } - [Reactive] public int? Duration { get; set; } - [Reactive] public ActionType? ActionType { get; set; } - [Reactive] public Enum? ActionKeyType { get; set; } - [Reactive] public Enum? ActionKey { get; set; } - public int Index { get; set; } + [Reactive] + public int? Duration { get; set; } - public RewardActionViewModel() - { + [Reactive] + public ActionType? ActionType { get; set; } - } + [Reactive] + public Enum? ActionKeyType { get; set; } + + [Reactive] + public Enum? ActionKey { get; set; } + public int Index { get; set; } + + public RewardActionViewModel() { } public RewardActionViewModel(int index) { diff --git a/src/ArcadePointsBot/ViewModels/ViewModelBase.cs b/src/ArcadePointsBot/ViewModels/ViewModelBase.cs index c3c511d..cddc797 100644 --- a/src/ArcadePointsBot/ViewModels/ViewModelBase.cs +++ b/src/ArcadePointsBot/ViewModels/ViewModelBase.cs @@ -1,13 +1,13 @@ -using ArcadePointsBot.Common.Primitives; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive.Linq; +using ArcadePointsBot.Common.Primitives; using Avalonia.Threading; using DynamicData; using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive.Linq; namespace ArcadePointsBot.ViewModels { @@ -19,7 +19,8 @@ public partial class ViewModelBase : ReactiveObject public string? StatusText { get => _statusText; - set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _statusText, value)); + set => + Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _statusText, value)); } private bool _isBusy; public bool IsBusy @@ -27,7 +28,7 @@ public bool IsBusy get => _isBusy; set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _isBusy, value)); } - + [ObservableAsProperty] public bool HasError { get; } @@ -51,4 +52,4 @@ protected ViewModelBase() public bool HasActions { get; } Actions.ToObservableChangeSet(x => x).ToCollection().Select(x => x.Any()).ToPropertyEx(this, x => x.HasActions); - */ \ No newline at end of file + */ diff --git a/src/ArcadePointsBot/Views/CreateRewardWindow.axaml.cs b/src/ArcadePointsBot/Views/CreateRewardWindow.axaml.cs index 69e822d..55283c4 100644 --- a/src/ArcadePointsBot/Views/CreateRewardWindow.axaml.cs +++ b/src/ArcadePointsBot/Views/CreateRewardWindow.axaml.cs @@ -1,7 +1,7 @@ -using Avalonia.ReactiveUI; +using System; using ArcadePointsBot.ViewModels; +using Avalonia.ReactiveUI; using ReactiveUI; -using System; namespace ArcadePointsBot; @@ -11,10 +11,14 @@ public CreateRewardWindow() { InitializeComponent(); - this.WhenActivated(d => d(ViewModel!.CreateTwitchRewardCommand.Subscribe(x => - { - if (x != null) - Close(x); - }))); + this.WhenActivated(d => + d( + ViewModel!.CreateTwitchRewardCommand.Subscribe(x => + { + if (x != null) + Close(x); + }) + ) + ); } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Views/EditRewardWindow.axaml.cs b/src/ArcadePointsBot/Views/EditRewardWindow.axaml.cs index c734e4a..8c33ff1 100644 --- a/src/ArcadePointsBot/Views/EditRewardWindow.axaml.cs +++ b/src/ArcadePointsBot/Views/EditRewardWindow.axaml.cs @@ -1,8 +1,8 @@ +using System; +using ArcadePointsBot.ViewModels; using Avalonia.Controls; using Avalonia.ReactiveUI; -using ArcadePointsBot.ViewModels; using ReactiveUI; -using System; namespace ArcadePointsBot.Views { @@ -11,12 +11,17 @@ public partial class EditRewardWindow : ReactiveWindow public EditRewardWindow() { InitializeComponent(); - if (Design.IsDesignMode) return; - this.WhenActivated(d => d(ViewModel!.EditTwitchRewardCommand.Subscribe(x => - { - if (x != null) - Close(x); - }))); + if (Design.IsDesignMode) + return; + this.WhenActivated(d => + d( + ViewModel!.EditTwitchRewardCommand.Subscribe(x => + { + if (x != null) + Close(x); + }) + ) + ); } } } diff --git a/src/ArcadePointsBot/Views/MainWindow.axaml.cs b/src/ArcadePointsBot/Views/MainWindow.axaml.cs index 7718ba5..b3beb99 100644 --- a/src/ArcadePointsBot/Views/MainWindow.axaml.cs +++ b/src/ArcadePointsBot/Views/MainWindow.axaml.cs @@ -1,13 +1,13 @@ +using System.Globalization; +using ArcadePointsBot.Domain.Rewards; +using ArcadePointsBot.ViewModels; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.ReactiveUI; using Avalonia.Threading; -using ArcadePointsBot.ViewModels; -using ReactiveUI; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; -using System.Globalization; -using ArcadePointsBot.Domain.Rewards; +using Microsoft.Extensions.DependencyInjection; +using ReactiveUI; namespace ArcadePointsBot.Views { @@ -19,10 +19,10 @@ public MainWindow() this.WhenActivated(disposables => { - Dispatcher.UIThread.Post(async () => { - if (Design.IsDesignMode) return; + if (Design.IsDesignMode) + return; await ViewModel!.Login(); await ViewModel!.LoadRewards(); }); @@ -37,18 +37,22 @@ void OnEnabledChanged(object sender, RoutedEventArgs e) ViewModel!.ChangeRewardEnabled(reward); } } + void ChangeWorkerStatus(object sender, RoutedEventArgs e) { if (ViewModel!.WorkerStatus == ArcadePointsBot.WorkerStatus.Running) ViewModel!.StopWorker(); - if (ViewModel!.WorkerStatus == ArcadePointsBot.WorkerStatus.Stopped || - ViewModel!.WorkerStatus == ArcadePointsBot.WorkerStatus.Errored ) + if ( + ViewModel!.WorkerStatus == ArcadePointsBot.WorkerStatus.Stopped + || ViewModel!.WorkerStatus == ArcadePointsBot.WorkerStatus.Errored + ) ViewModel!.StartWorker(); } + protected override void OnClosing(WindowClosingEventArgs e) { ViewModel?.StopWorker(); base.OnClosing(e); } } -} \ No newline at end of file +} diff --git a/src/ArcadePointsBot/Views/RewardActionView.axaml.cs b/src/ArcadePointsBot/Views/RewardActionView.axaml.cs index 61da71d..1eb145a 100644 --- a/src/ArcadePointsBot/Views/RewardActionView.axaml.cs +++ b/src/ArcadePointsBot/Views/RewardActionView.axaml.cs @@ -10,4 +10,4 @@ public RewardActionView() { InitializeComponent(); } -} \ No newline at end of file +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..add7a90 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/version.json b/version.json new file mode 100644 index 0000000..4bd5fc6 --- /dev/null +++ b/version.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.2.1-preview.{height}", + "publicReleaseRefSpec": [ + "^refs/heads/main$", + "^refs/heads/release/v\\d+\\.\\d+" + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + }, + "release": { + "branchName": "release/v{version}", + "firstUnstableTag": "preview" + } +} \ No newline at end of file