├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── CatCore.Azure ├── .gitignore ├── CatCore.Azure.csproj ├── Functions │ ├── KeepAliveFunction.cs │ └── Twitch │ │ ├── AuthorizationCodeToTokensFunction.cs │ │ └── RefreshTokensFunction.cs ├── Program.cs ├── Services │ └── Twitch │ │ └── TwitchAuthService.cs └── host.json ├── CatCore.Shared ├── CatCore.Shared.csproj └── Models │ └── Twitch │ └── OAuth │ └── AuthorizationResponse.cs ├── CatCore.sln ├── CatCore ├── BeatSaber.targets ├── CatCore.csproj ├── CatCoreInstance.cs ├── Constants.user.cs.example ├── ConstantsBase.cs ├── Exceptions │ ├── CatCoreNotInitializedException.cs │ └── NotAuthenticatedException.cs ├── Helpers │ ├── AsyncEventHandlerDefinitions.cs │ ├── Converters │ │ └── JsonStringEnumConverter.cs │ ├── DictionaryExtensions.cs │ ├── IHasWeakReference.cs │ ├── IrcExtensions.cs │ ├── JSON │ │ ├── BttvSerializerContext.cs │ │ ├── TwitchAuthSerializerContext.cs │ │ ├── TwitchHelixSerializerContext.cs │ │ └── TwitchPubSubSerializerContext.cs │ ├── SharedProxyProvider.cs │ ├── StatusCodeHelper.cs │ ├── Synchronization.cs │ ├── ThreadSafeRandomFactory.cs │ └── WeakActionToken.cs ├── ILRepack.targets ├── Logging │ ├── ActionableLogSink.cs │ ├── CustomLogLevel.cs │ └── SinkExtensions.cs ├── Models │ ├── Api │ │ ├── Requests │ │ │ ├── GlobalStateRequestDto.cs │ │ │ └── TwitchStateRequestDto.cs │ │ └── Responses │ │ │ ├── GlobalStateResponseDto.cs │ │ │ ├── TwitchChannelData.cs │ │ │ ├── TwitchChannelQueryData.cs │ │ │ └── TwitchStateResponseDto.cs │ ├── Config │ │ ├── ConfigRoot.cs │ │ ├── GlobalConfig.cs │ │ └── TwitchConfig.cs │ ├── Credentials │ │ ├── AuthenticationStatus.cs │ │ ├── ICredentials.cs │ │ └── TwitchCredentials.cs │ ├── EventArgs │ │ └── TwitchChannelsUpdatedEventArgs.cs │ ├── Shared │ │ ├── ChatResourceData.cs │ │ ├── Emoji.cs │ │ ├── IChatBadge.cs │ │ ├── IChatChannel.cs │ │ ├── IChatEmote.cs │ │ ├── IChatMessage.cs │ │ ├── IChatResourceData.cs │ │ ├── IChatUser.cs │ │ └── PlatformType.cs │ ├── ThirdParty │ │ └── Bttv │ │ │ ├── Base │ │ │ ├── EmoteBase.cs │ │ │ └── EmoteUserBase.cs │ │ │ ├── BttvChannelData.cs │ │ │ ├── BttvEmote.cs │ │ │ ├── BttvEmoteBase.cs │ │ │ ├── BttvEmoteUser.cs │ │ │ ├── BttvSharedEmote.cs │ │ │ └── Ffz │ │ │ ├── FfzEmote.cs │ │ │ ├── FfzEmoteUser.cs │ │ │ └── FfzImageSizes.cs │ └── Twitch │ │ ├── Helix │ │ ├── Requests │ │ │ ├── Bans │ │ │ │ └── BanUserRequestDto.cs │ │ │ ├── ChatSettingsRequestDto.cs │ │ │ ├── CreateStreamMarkerRequestDto.cs │ │ │ ├── LegacyRequestDataWrapper.cs │ │ │ ├── Polls │ │ │ │ ├── CreatePollRequestDto.cs │ │ │ │ ├── EndPollRequestDto.cs │ │ │ │ └── PollChoice.cs │ │ │ ├── Predictions │ │ │ │ ├── CreatePredictionsRequestDto.cs │ │ │ │ ├── EndPollRequestDto.cs │ │ │ │ └── Outcome.cs │ │ │ ├── SendChatAnnouncementColor.cs │ │ │ ├── SendChatAnnouncementRequestDto.cs │ │ │ └── UserChatColor.cs │ │ └── Responses │ │ │ ├── Badges │ │ │ ├── BadgeData.cs │ │ │ └── BadgeVersion.cs │ │ │ ├── Bans │ │ │ ├── BanUser.cs │ │ │ └── BannedUserInfo.cs │ │ │ ├── Bits │ │ │ └── Cheermotes │ │ │ │ ├── CheermoteGroupData.cs │ │ │ │ ├── CheermoteImageColorGroup.cs │ │ │ │ ├── CheermoteImageSizeGroup.cs │ │ │ │ ├── CheermoteImageTypesGroup.cs │ │ │ │ ├── CheermoteTier.cs │ │ │ │ └── CheermoteType.cs │ │ │ ├── ChannelData.cs │ │ │ ├── ChatSettings.cs │ │ │ ├── CreateStreamMarkerData.cs │ │ │ ├── Emotes │ │ │ ├── ChannelEmote.cs │ │ │ └── GlobalEmote.cs │ │ │ ├── Pagination.cs │ │ │ ├── Polls │ │ │ ├── PollChoice.cs │ │ │ └── PollData.cs │ │ │ ├── Predictions │ │ │ ├── Outcome.cs │ │ │ ├── PredictionData.cs │ │ │ └── Predictor.cs │ │ │ ├── ResponseBase.cs │ │ │ ├── ResponseBaseWithPagination.cs │ │ │ ├── ResponseBaseWithTemplate.cs │ │ │ ├── StartRaidData.cs │ │ │ ├── Stream.cs │ │ │ ├── UserChatColorData.cs │ │ │ └── UserData.cs │ │ ├── IRC │ │ ├── IrcCommands.cs │ │ ├── IrcMessageTags.cs │ │ ├── MessageSendingRateLimit.cs │ │ ├── TwitchGlobalUserState.cs │ │ ├── TwitchMessage.cs │ │ ├── TwitchRoomState.cs │ │ ├── TwitchUser.cs │ │ └── TwitchUserState.cs │ │ ├── Media │ │ ├── TwitchBadge.cs │ │ ├── TwitchCheermoteData.cs │ │ └── TwitchEmote.cs │ │ ├── OAuth │ │ └── ValidationResponse.cs │ │ ├── PubSub │ │ ├── PubSubMessageTypes.cs │ │ ├── PubSubTopics.cs │ │ ├── Requests │ │ │ ├── TopicNegotiationMessage.cs │ │ │ └── TopicNegotiationMessageData.cs │ │ └── Responses │ │ │ ├── ChannelPointsChannelV1 │ │ │ ├── GlobalCooldown.cs │ │ │ ├── MaxPerStream.cs │ │ │ ├── MaxPerUserPerStream.cs │ │ │ ├── Reward.cs │ │ │ ├── RewardRedeemedData.cs │ │ │ └── User.cs │ │ │ ├── Follow.cs │ │ │ ├── Polls │ │ │ ├── ContributorBase.cs │ │ │ ├── PollChoice.cs │ │ │ ├── PollData.cs │ │ │ ├── PollSettings.cs │ │ │ ├── PollSettingsEntry.cs │ │ │ ├── Tokens.cs │ │ │ ├── TopBitsContributor.cs │ │ │ ├── TopChannelPointsContributor.cs │ │ │ └── Votes.cs │ │ │ ├── Predictions │ │ │ ├── Badge.cs │ │ │ ├── Outcome.cs │ │ │ ├── PredictionData.cs │ │ │ ├── PredictorResult.cs │ │ │ ├── TopPredictor.cs │ │ │ └── User.cs │ │ │ └── VideoPlayback │ │ │ ├── Commercial.cs │ │ │ ├── StreamDown.cs │ │ │ ├── StreamUp.cs │ │ │ ├── VideoPlaybackBase.cs │ │ │ └── ViewCountUpdate.cs │ │ ├── Shared │ │ ├── DefaultImage.cs │ │ ├── PollStatus.cs │ │ └── PredictionStatus.cs │ │ └── TwitchChannel.cs ├── Resources │ └── index.html └── Services │ ├── Interfaces │ ├── IChatService.cs │ ├── IKittenApiService.cs │ ├── IKittenBrowserLauncherService.cs │ ├── IKittenPathProvider.cs │ ├── IKittenPlatformActiveStateManager.cs │ ├── IKittenPlatformServiceManagerBase.cs │ ├── IKittenSettingsService.cs │ ├── IKittenWebSocketProvider.cs │ ├── INeedAsyncInitialization.cs │ ├── INeedInitialization.cs │ └── IPlatformService.cs │ ├── KittenApiService.cs │ ├── KittenBrowserLauncherService.cs │ ├── KittenCredentialsProvider.cs │ ├── KittenPathProvider.cs │ ├── KittenPlatformActiveStateManager.cs │ ├── KittenPlatformServiceManagerBase.cs │ ├── KittenSettingsService.cs │ ├── KittenWebSocketProvider.cs │ ├── Multiplexer │ ├── ChatServiceMultiplexer.cs │ ├── ChatServiceMultiplexerManager.cs │ ├── MultiplexedChannel.cs │ ├── MultiplexedMessage.cs │ └── MultiplexedPlatformService.cs │ └── Twitch │ ├── Interfaces │ ├── ITwitchAuthService.cs │ ├── ITwitchChannelManagementService.cs │ ├── ITwitchHelixApiService.cs │ ├── ITwitchIrcService.cs │ ├── ITwitchPubSubServiceManager.cs │ ├── ITwitchRoomStateTrackerService.cs │ ├── ITwitchService.cs │ └── ITwitchUserStateTrackerService.cs │ ├── Media │ ├── BttvDataProvider.cs │ ├── TwitchBadgeDataProvider.cs │ ├── TwitchCheermoteDataProvider.cs │ ├── TwitchEmoteDetectionHelper.cs │ └── TwitchMediaDataProvider.cs │ ├── TwitchAuthService.cs │ ├── TwitchChannelManagementService.cs │ ├── TwitchHelixApiService.Calls.cs │ ├── TwitchHelixApiService.cs │ ├── TwitchHelixClientHandler.cs │ ├── TwitchIrcService.cs │ ├── TwitchPubSubServiceExperimentalAgent.cs │ ├── TwitchPubSubServiceManager.Events.cs │ ├── TwitchPubSubServiceManager.cs │ ├── TwitchRoomStateTrackerService.cs │ ├── TwitchService.cs │ ├── TwitchServiceManager.cs │ └── TwitchUserStateTrackerService.cs ├── CatCoreBenchmarkSandbox ├── Benchmarks │ ├── EmojiParser │ │ └── EmojiParserBenchmark.cs │ ├── Miscellaneous │ │ ├── RandomColorGenerationBenchmark.cs │ │ ├── StringEqualityBenchmark.cs │ │ └── StringStartsWithBenchmark.cs │ ├── TwitchIRCMessageDeconstruction │ │ ├── TwitchIrcMessageCompoundDeconstructionBenchmark.cs │ │ ├── TwitchIrcMessageHighLevelDeconstructionBenchmark.cs │ │ ├── TwitchIrcMessageTagsDeconstructionBenchmark.cs │ │ └── TwitchIrcMultiMessageCompoundDeconstructionBenchmark.cs │ └── TwitchPubSub │ │ └── TwitchPubSubNonceGenerationBenchmark.cs ├── CatCoreBenchmarkSandbox.csproj └── Program.cs ├── CatCoreStandaloneSandbox ├── CatCoreStandaloneSandbox.csproj ├── Models │ └── Messages │ │ ├── MessageBase.cs │ │ ├── PingMessage.cs │ │ ├── PongMessage.cs │ │ ├── ReconnectMessage.cs │ │ └── TopicMessage.cs └── Program.cs ├── CatCoreTester ├── CatCoreTester.csproj └── Program.cs ├── CatCoreTesterMod ├── CatCoreTesterMod.csproj ├── Directory.Build.props ├── Directory.Build.targets ├── Plugin.cs └── manifest.json ├── CatCoreTests ├── CatCoreTests.csproj ├── EmojiParserTests.cs └── IrcExtensionsTests.cs ├── LICENSE ├── NuGet.Config ├── README.md └── Useful files └── Unicode ├── Unicode13_1EmojiTest.txt └── Unicode14_0EmojiTest.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: erisapps 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: erisapps 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build CatCore 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | paths: 8 | - 'CatCore*/**' 9 | - '.github/workflows/main.yml' 10 | pull_request: 11 | branches: [ main ] 12 | paths: 13 | - 'CatCore*/**' 14 | - '.github/workflows/main.yml' 15 | 16 | jobs: 17 | catcore-shared: 18 | name: CatCore.Shared 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Setup dotnet 23 | uses: actions/setup-dotnet@v3 24 | with: 25 | dotnet-version: 7.0.x 26 | - name: Build CatCore.Shared 27 | id: Build 28 | run: dotnet build CatCore.Shared --configuration Release 29 | catcore: 30 | name: CatCore 31 | runs-on: ubuntu-latest 32 | needs: catcore-shared 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Setup dotnet 36 | uses: actions/setup-dotnet@v3 37 | with: 38 | dotnet-version: 7.0.x 39 | - name: Authenticate with GitHub Package Registry 40 | run: dotnet nuget update source "ErisApps GH Packages" --username ${{ github.repository_owner }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text 41 | - name: Removed .example suffix from Constants implementation 42 | run: find ~ -type f -name Constants.user.cs.example -execdir mv Constants.user.cs.example Constants.user.cs \; 43 | - name: Find and Replace - Insert CatCore Auth Server URI 44 | run: find ~ -type f -name Constants.user.cs -exec sed -i 's| {{ CATCORE_AUTH_SERVER_URI }} |${{ secrets.CATCORE_AUTH_SERVER_URI }}|' {} \; 45 | - name: Find and Replace - Insert Twitch Client Id 46 | run: find ~ -type f -name Constants.user.cs -exec sed -i 's| {{ TWITCH_CLIENT_ID }} |${{ secrets.TWITCH_CLIENT_ID }}|' {} \; 47 | - name: Build 48 | id: Build 49 | run: dotnet build CatCore --configuration Release 50 | - name: Echo Filename 51 | run: echo $BUILDTEXT \($ASSEMBLYNAME\) 52 | env: 53 | BUILDTEXT: Filename=${{ steps.Build.outputs.filename }} 54 | ASSEMBLYNAME: AssemblyName=${{ steps.Build.outputs.assemblyname }} 55 | - name: Upload Artifact 56 | uses: actions/upload-artifact@v1 57 | with: 58 | name: ${{ steps.Build.outputs.filename }} 59 | path: ${{ steps.Build.outputs.artifactpath }} 60 | catcore-azure: 61 | name: CatCore.Azure 62 | runs-on: ubuntu-latest 63 | needs: catcore-shared 64 | steps: 65 | - uses: actions/checkout@v3 66 | - name: Setup dotnet 67 | uses: actions/setup-dotnet@v3 68 | with: 69 | dotnet-version: 7.0.x 70 | - name: Build CatCore.Azure 71 | id: Build 72 | run: dotnet build CatCore.Azure --configuration Release -------------------------------------------------------------------------------- /CatCore.Azure/CatCore.Azure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | preview 6 | v4 7 | Exe 8 | <_FunctionsSkipCleanOutput>true 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | Never 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /CatCore.Azure/Functions/KeepAliveFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Functions.Worker; 2 | 3 | namespace CatCore.Azure.Functions 4 | { 5 | public static class KeepAliveFunction 6 | { 7 | [Function("KeepAliveFunction")] 8 | public static void Run([TimerTrigger("0 */10 * * * *")] TimerInfo timerInfo, FunctionContext context) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /CatCore.Azure/Functions/Twitch/AuthorizationCodeToTokensFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using System.Web; 4 | using CatCore.Azure.Services.Twitch; 5 | using Microsoft.Azure.Functions.Worker; 6 | using Microsoft.Azure.Functions.Worker.Http; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace CatCore.Azure.Functions.Twitch 11 | { 12 | public static class AuthorizationCodeToTokensFunction 13 | { 14 | [Function("AuthorizationCodeToTokensFunction")] 15 | public static async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "twitch/authorize")] 16 | HttpRequestData req, 17 | FunctionContext executionContext) 18 | { 19 | var logger = executionContext.GetLogger(nameof(AuthorizationCodeToTokensFunction)); 20 | 21 | var queryParametersCollection = HttpUtility.ParseQueryString(req.Url.Query); 22 | var authorizationCode = queryParametersCollection["code"]; 23 | var redirectUrl = queryParametersCollection["redirect_uri"]; 24 | if (string.IsNullOrWhiteSpace(authorizationCode) || string.IsNullOrWhiteSpace(redirectUrl)) 25 | { 26 | logger.LogInformation("API Call is missing one of the required query parameters"); 27 | return req.CreateResponse(HttpStatusCode.BadRequest); 28 | } 29 | 30 | var twitchAuthService = executionContext.InstanceServices.GetService()!; 31 | await using var authorizationResponseStream = await twitchAuthService.GetTokensByAuthorizationCode(authorizationCode, redirectUrl).ConfigureAwait(false); 32 | 33 | HttpResponseData response; 34 | if (authorizationResponseStream != null) 35 | { 36 | response = req.CreateResponse(HttpStatusCode.OK); 37 | await authorizationResponseStream.CopyToAsync(response.Body).ConfigureAwait(false); 38 | } 39 | else 40 | { 41 | logger.LogInformation("Couldn't trade authorization code for credentials from Twitch Auth server"); 42 | response = req.CreateResponse(HttpStatusCode.Unauthorized); 43 | } 44 | 45 | return response; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /CatCore.Azure/Functions/Twitch/RefreshTokensFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using System.Web; 4 | using CatCore.Azure.Services.Twitch; 5 | using Microsoft.Azure.Functions.Worker; 6 | using Microsoft.Azure.Functions.Worker.Http; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace CatCore.Azure.Functions.Twitch 11 | { 12 | public class RefreshTokensFunction 13 | { 14 | [Function("RefreshTokensFunction")] 15 | public async Task Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "twitch/refresh")] 16 | HttpRequestData req, 17 | FunctionContext executionContext) 18 | { 19 | var logger = executionContext.GetLogger(nameof(RefreshTokensFunction)); 20 | 21 | var queryParametersCollection = HttpUtility.ParseQueryString(req.Url.Query); 22 | var refreshToken = queryParametersCollection["refresh_token"]; 23 | if (string.IsNullOrWhiteSpace(refreshToken)) 24 | { 25 | logger.LogWarning("API Call is missing one of the required query parameters"); 26 | return req.CreateResponse(HttpStatusCode.BadRequest); 27 | } 28 | 29 | var twitchAuthService = executionContext.InstanceServices.GetService()!; 30 | await using var authorizationResponseStream = await twitchAuthService.RefreshTokens(refreshToken).ConfigureAwait(false); 31 | 32 | HttpResponseData response; 33 | if (authorizationResponseStream != null) 34 | { 35 | response = req.CreateResponse(HttpStatusCode.OK); 36 | await authorizationResponseStream.CopyToAsync(response.Body).ConfigureAwait(false); 37 | } 38 | else 39 | { 40 | logger.LogInformation("Couldn't refresh existing credentials through Twitch Auth server"); 41 | response = req.CreateResponse(HttpStatusCode.Unauthorized); 42 | } 43 | 44 | return response; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /CatCore.Azure/Program.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Azure.Services.Twitch; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace CatCore.Azure 6 | { 7 | public class Program 8 | { 9 | public static void Main() 10 | { 11 | var host = new HostBuilder() 12 | .ConfigureFunctionsWorkerDefaults() 13 | .ConfigureServices(builder => 14 | { 15 | builder.AddHttpClient(); 16 | builder.AddSingleton(); 17 | }) 18 | .Build(); 19 | 20 | host.Run(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /CatCore.Azure/Services/Twitch/TwitchAuthService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace CatCore.Azure.Services.Twitch 8 | { 9 | internal class TwitchAuthService 10 | { 11 | private const string TWITCH_AUTH_BASEURL = "https://id.twitch.tv/oauth2/"; 12 | 13 | private readonly HttpClient _authClient; 14 | 15 | public TwitchAuthService(IHttpClientFactory httpClientFactory) 16 | { 17 | _authClient = httpClientFactory.CreateClient(); 18 | _authClient.DefaultRequestVersion = HttpVersion.Version20; 19 | _authClient.BaseAddress = new Uri(TWITCH_AUTH_BASEURL, UriKind.Absolute); 20 | _authClient.DefaultRequestHeaders.UserAgent.TryParseAdd($"{nameof(CatCore)}/1.0.0"); 21 | } 22 | 23 | public Task GetTokensByAuthorizationCode(string authorizationCode, string redirectUrl) 24 | { 25 | return PostWithoutBodyExpectStreamInternal($"{TWITCH_AUTH_BASEURL}token" + 26 | $"?client_id={Environment.GetEnvironmentVariable("Twitch_CatCore_ClientId")}" + 27 | $"&client_secret={Environment.GetEnvironmentVariable("Twitch_CatCore_ClientSecret")}" + 28 | $"&code={authorizationCode}" + 29 | "&grant_type=authorization_code" + 30 | $"&redirect_uri={redirectUrl}"); 31 | } 32 | 33 | public Task RefreshTokens(string refreshToken) 34 | { 35 | if (string.IsNullOrWhiteSpace(refreshToken)) 36 | { 37 | return Task.FromResult(null); 38 | } 39 | 40 | return PostWithoutBodyExpectStreamInternal($"{TWITCH_AUTH_BASEURL}token" + 41 | $"?client_id={Environment.GetEnvironmentVariable("Twitch_CatCore_ClientId")}" + 42 | $"&client_secret={Environment.GetEnvironmentVariable("Twitch_CatCore_ClientSecret")}" + 43 | "&grant_type=refresh_token" + 44 | $"&refresh_token={refreshToken}"); 45 | } 46 | 47 | private async Task PostWithoutBodyExpectStreamInternal(string requestUri) 48 | { 49 | using var request = new HttpRequestMessage(HttpMethod.Post, requestUri); 50 | using var responseMessage = await _authClient 51 | .SendAsync(request, HttpCompletionOption.ResponseHeadersRead) 52 | .ConfigureAwait(false); 53 | 54 | if (!responseMessage.IsSuccessStatusCode) 55 | { 56 | return null; 57 | } 58 | 59 | var memoryStream = new MemoryStream(); 60 | await responseMessage.Content.CopyToAsync(memoryStream).ConfigureAwait(false); 61 | memoryStream.Seek(0, SeekOrigin.Begin); 62 | 63 | return memoryStream; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /CatCore.Azure/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /CatCore.Shared/CatCore.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 9 6 | enable 7 | full 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CatCore.Shared/Models/Twitch/OAuth/AuthorizationResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Shared.Models.Twitch.OAuth 5 | { 6 | public readonly struct AuthorizationResponse 7 | { 8 | [JsonPropertyName("access_token")] 9 | public string AccessToken { get; } 10 | 11 | [JsonPropertyName("refresh_token")] 12 | public string RefreshToken { get; } 13 | 14 | [JsonPropertyName("token_type")] 15 | public string TokenType { get; } 16 | 17 | [JsonPropertyName("scope")] 18 | public string[] Scope { get; } 19 | 20 | [JsonPropertyName("expires_in")] 21 | public int ExpiresInRaw { get; } 22 | 23 | public DateTimeOffset ExpiresIn { get; } 24 | 25 | [JsonConstructor] 26 | public AuthorizationResponse(string accessToken, string refreshToken, string tokenType, int expiresInRaw, string[] scope) 27 | { 28 | AccessToken = accessToken; 29 | RefreshToken = refreshToken; 30 | TokenType = tokenType; 31 | Scope = scope; 32 | ExpiresInRaw = expiresInRaw; 33 | ExpiresIn = DateTimeOffset.Now.AddSeconds(expiresInRaw); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /CatCore/CatCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | library 6 | 9 7 | enable 8 | true 9 | true 10 | Debug;Develop;Release 11 | true 12 | CS1591 13 | 14 | 15 | 16 | full 17 | 18 | 19 | 20 | pdbonly 21 | 22 | 23 | 24 | CatCore 25 | CatCore is a .NET Standard 2.0 library which provides a shared connection to mods and other applications for Twitch (and other future streaming platforms). 26 | Eris 27 | 1.1.0 28 | Copyright © Eris 2023 29 | https://github.com/ErisApps/CatCore 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Resources\Unicode14_0EmojiTest.txt 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /CatCore/Constants.user.cs.example: -------------------------------------------------------------------------------- 1 | namespace CatCore 2 | { 3 | internal sealed partial class Constants 4 | { 5 | // Implement/Add the missing properties in this file. 6 | private const string CATCORE_AUTH_SERVER_URI = " {{ CATCORE_AUTH_SERVER_URI }} "; 7 | 8 | private const string TWITCH_CLIENT_ID = " {{ TWITCH_CLIENT_ID }} "; 9 | 10 | internal override string CatCoreAuthServerUri => CATCORE_AUTH_SERVER_URI; 11 | 12 | internal override string TwitchClientId => TWITCH_CLIENT_ID; 13 | } 14 | } -------------------------------------------------------------------------------- /CatCore/ConstantsBase.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore 2 | { 3 | internal sealed partial class Constants : ConstantsBase 4 | { 5 | } 6 | 7 | internal abstract class ConstantsBase 8 | { 9 | internal static string InternalApiServerUri => "http://localhost:8338/"; 10 | 11 | internal abstract string CatCoreAuthServerUri { get; } 12 | 13 | internal abstract string TwitchClientId { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Exceptions/CatCoreNotInitializedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CatCore.Exceptions 4 | { 5 | public class CatCoreNotInitializedException : Exception 6 | { 7 | public override string Message => $"{nameof(CatCore)} not initialized. Make sure to call {nameof(CatCoreInstance)}.{nameof(CatCoreInstance.Create)}() to initialize ChatCore!"; 8 | } 9 | } -------------------------------------------------------------------------------- /CatCore/Exceptions/NotAuthenticatedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CatCore.Models.Shared; 3 | 4 | namespace CatCore.Exceptions 5 | { 6 | public abstract class NotAuthenticatedException : Exception 7 | { 8 | private readonly PlatformType _platform; 9 | public override string Message => $"Non valid credentials are present for platform {_platform:G}, make sure the user is logged in or try again later."; 10 | 11 | protected NotAuthenticatedException(PlatformType platform) 12 | { 13 | _platform = platform; 14 | } 15 | } 16 | 17 | public class TwitchNotAuthenticatedException : NotAuthenticatedException 18 | { 19 | public TwitchNotAuthenticatedException() : base(PlatformType.Twitch) 20 | { 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /CatCore/Helpers/AsyncEventHandlerDefinitions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace CatCore.Helpers 4 | { 5 | internal static class AsyncEventHandlerDefinitions 6 | { 7 | public delegate Task AsyncEventHandler(); 8 | public delegate Task AsyncEventHandler(T1 t1); 9 | public delegate Task AsyncEventHandler(T1 t1, T2 t2); 10 | } 11 | } -------------------------------------------------------------------------------- /CatCore/Helpers/Converters/JsonStringEnumConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace CatCore.Helpers.Converters 9 | { 10 | /// 11 | /// Based upon https://github.com/dotnet/runtime/issues/31081#issuecomment-848697673 12 | /// 13 | internal sealed class JsonStringEnumConverter : JsonConverter where TEnum : struct, Enum 14 | { 15 | private readonly Dictionary _enumToString = new(); 16 | private readonly Dictionary _stringToEnum = new(); 17 | 18 | public JsonStringEnumConverter() 19 | { 20 | var type = typeof(TEnum); 21 | var values = Enum.GetValues(typeof(TEnum)).Cast().ToList(); 22 | 23 | foreach (var value in values) 24 | { 25 | var enumMember = type.GetMember(value.ToString())[0]; 26 | var attr = enumMember.GetCustomAttributes(typeof(EnumMemberAttribute), false) 27 | .Cast() 28 | .FirstOrDefault(); 29 | 30 | _stringToEnum.Add(value.ToString(), value); 31 | 32 | if (attr?.Value != null) 33 | { 34 | _enumToString.Add(value, attr.Value); 35 | _stringToEnum.Add(attr.Value, value); 36 | } 37 | else 38 | { 39 | _enumToString.Add(value, value.ToString()); 40 | } 41 | } 42 | } 43 | 44 | public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 45 | { 46 | var stringValue = reader.GetString(); 47 | 48 | if (stringValue != null && _stringToEnum.TryGetValue(stringValue, out var enumValue)) 49 | { 50 | return enumValue; 51 | } 52 | 53 | return default; 54 | } 55 | 56 | public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) 57 | { 58 | writer.WriteStringValue(_enumToString[value]); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /CatCore/Helpers/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace CatCore.Helpers 5 | { 6 | internal static class DictionaryExtensions 7 | { 8 | public static string ToPrettyString(this IDictionary dict) 9 | { 10 | var str = new StringBuilder(); 11 | str.Append("{ "); 12 | foreach (var pair in dict) 13 | { 14 | str.Append($"{pair.Key}={pair.Value}; "); 15 | } 16 | str.Append('}'); 17 | return str.ToString(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Helpers/IHasWeakReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CatCore.Helpers 4 | { 5 | public interface IHasWeakReference 6 | { 7 | WeakReference WeakReference { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /CatCore/Helpers/JSON/BttvSerializerContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | using CatCore.Models.ThirdParty.Bttv; 4 | using CatCore.Models.ThirdParty.Bttv.Ffz; 5 | 6 | namespace CatCore.Helpers.JSON 7 | { 8 | [JsonSerializable(typeof(IReadOnlyList))] 9 | [JsonSerializable(typeof(BttvChannelData))] 10 | [JsonSerializable(typeof(IReadOnlyList))] 11 | internal partial class BttvSerializerContext : JsonSerializerContext 12 | { 13 | } 14 | } -------------------------------------------------------------------------------- /CatCore/Helpers/JSON/TwitchAuthSerializerContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Models.Twitch.OAuth; 3 | using CatCore.Shared.Models.Twitch.OAuth; 4 | 5 | namespace CatCore.Helpers.JSON 6 | { 7 | [JsonSerializable(typeof(ValidationResponse))] 8 | [JsonSerializable(typeof(AuthorizationResponse))] 9 | internal partial class TwitchAuthSerializerContext : JsonSerializerContext 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /CatCore/Helpers/JSON/TwitchHelixSerializerContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using HelixRequests = CatCore.Models.Twitch.Helix.Requests; 3 | using HelixResponses = CatCore.Models.Twitch.Helix.Responses; 4 | 5 | namespace CatCore.Helpers.JSON 6 | { 7 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 8 | [JsonSerializable(typeof(HelixRequests.CreateStreamMarkerRequestDto))] 9 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 10 | [JsonSerializable(typeof(HelixResponses.ResponseBaseWithPagination))] 11 | [JsonSerializable(typeof(HelixRequests.Polls.PollChoice), TypeInfoPropertyName = "RequestPollChoice")] 12 | [JsonSerializable(typeof(HelixResponses.Polls.PollChoice), TypeInfoPropertyName = "ResponsePollChoice")] 13 | [JsonSerializable(typeof(HelixResponses.ResponseBaseWithPagination))] 14 | [JsonSerializable(typeof(HelixRequests.Polls.CreatePollRequestDto))] 15 | [JsonSerializable(typeof(HelixRequests.Polls.EndPollRequestDto))] 16 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 17 | [JsonSerializable(typeof(HelixRequests.Predictions.Outcome), TypeInfoPropertyName = "RequestOutcome")] 18 | [JsonSerializable(typeof(HelixResponses.Predictions.Outcome), TypeInfoPropertyName = "ResponseOutcome")] 19 | [JsonSerializable(typeof(HelixResponses.ResponseBaseWithPagination))] 20 | [JsonSerializable(typeof(HelixRequests.Predictions.CreatePredictionsRequestDto))] 21 | [JsonSerializable(typeof(HelixRequests.Predictions.EndPredictionRequestDto))] 22 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 23 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 24 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 25 | [JsonSerializable(typeof(HelixResponses.ResponseBaseWithPagination))] 26 | [JsonSerializable(typeof(HelixResponses.ResponseBaseWithTemplate))] 27 | [JsonSerializable(typeof(HelixResponses.ResponseBaseWithTemplate))] 28 | [JsonSerializable(typeof(HelixRequests.ChatSettingsRequestDto))] 29 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 30 | [JsonSerializable(typeof(HelixResponses.ResponseBaseWithPagination))] 31 | [JsonSerializable(typeof(HelixRequests.LegacyRequestDataWrapper))] 32 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 33 | [JsonSerializable(typeof(HelixRequests.SendChatAnnouncementRequestDto))] 34 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 35 | [JsonSerializable(typeof(HelixResponses.ResponseBase))] 36 | internal partial class TwitchHelixSerializerContext : JsonSerializerContext 37 | { 38 | } 39 | } -------------------------------------------------------------------------------- /CatCore/Helpers/JSON/TwitchPubSubSerializerContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Models.Twitch.PubSub.Requests; 3 | using CatCore.Models.Twitch.PubSub.Responses; 4 | using CatCore.Models.Twitch.PubSub.Responses.ChannelPointsChannelV1; 5 | using CatCore.Models.Twitch.PubSub.Responses.Polls; 6 | using CatCore.Models.Twitch.PubSub.Responses.Predictions; 7 | 8 | namespace CatCore.Helpers.JSON 9 | { 10 | [JsonSerializable(typeof(TopicNegotiationMessage))] 11 | [JsonSerializable(typeof(Follow))] 12 | [JsonSerializable(typeof(PollData))] 13 | [JsonSerializable(typeof(PredictionData))] 14 | [JsonSerializable(typeof(Models.Twitch.PubSub.Responses.Predictions.User), TypeInfoPropertyName = "PredictionUser")] 15 | [JsonSerializable(typeof(RewardRedeemedData))] 16 | [JsonSerializable(typeof(Models.Twitch.PubSub.Responses.ChannelPointsChannelV1.User), TypeInfoPropertyName = "RewardRedeemedUser")] 17 | internal partial class TwitchPubSubSerializerContext : JsonSerializerContext 18 | { 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Helpers/SharedProxyProvider.cs: -------------------------------------------------------------------------------- 1 | #if !RELEASE 2 | namespace CatCore.Helpers 3 | { 4 | internal static class SharedProxyProvider 5 | { 6 | public static readonly System.Net.WebProxy? PROXY = null; 7 | } 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /CatCore/Helpers/StatusCodeHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace CatCore.Helpers 4 | { 5 | internal static class StatusCodeHelper 6 | { 7 | public const HttpStatusCode TOO_MANY_REQUESTS = (HttpStatusCode) 429; 8 | } 9 | } -------------------------------------------------------------------------------- /CatCore/Helpers/ThreadSafeRandomFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace CatCore.Helpers 5 | { 6 | /// 7 | /// Convenience class for dealing with randomness. 8 | /// 9 | /// 10 | /// Adapted from https://codeblog.jonskeet.uk/2009/11/04/revisiting-randomness/ 11 | /// 12 | internal sealed class ThreadSafeRandomFactory 13 | { 14 | /// 15 | /// Random number generator used to generate seeds, 16 | /// which are then used to create new random number 17 | /// generators on a per-thread basis. 18 | /// 19 | private readonly Random _globalRandom; 20 | private readonly SemaphoreSlim _globalLock; 21 | 22 | /// 23 | /// ThreadLocal random number generator 24 | /// 25 | private readonly ThreadLocal _threadRandom; 26 | 27 | public ThreadSafeRandomFactory() 28 | { 29 | _globalLock = new SemaphoreSlim(1, 1); 30 | _globalRandom = new Random(); 31 | 32 | _threadRandom = new ThreadLocal(CreateNewRandom); 33 | } 34 | 35 | /// 36 | /// Creates a new instance of Random. The seed is derived from a global (static) instance of Random, rather than time. 37 | /// 38 | public Random CreateNewRandom() 39 | { 40 | using var _ = Synchronization.Lock(_globalLock); 41 | return new Random(_globalRandom.Next()); 42 | } 43 | 44 | /// 45 | /// Returns an instance of Random which can be used freely within the current thread. 46 | /// 47 | public Random Instance => _threadRandom.Value; 48 | } 49 | } -------------------------------------------------------------------------------- /CatCore/Helpers/WeakActionToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CatCore.Helpers 4 | { 5 | internal static class WeakActionToken 6 | { 7 | public static IDisposable Create(TTarget target, Action? action) where TTarget : class 8 | { 9 | return new WeakActionTokenInternal(target, action); 10 | } 11 | 12 | public static WeakReference WeakReferenceForItem(object item) 13 | { 14 | return item is IHasWeakReference hasWeak ? hasWeak.WeakReference : CreateWeakReference(item); 15 | } 16 | 17 | private static WeakReference GetWeakReference(object target) 18 | { 19 | return target as WeakReference ?? WeakReferenceForItem(target); 20 | } 21 | 22 | private static WeakReference CreateWeakReference(object o) 23 | { 24 | return new WeakReference(o, false); 25 | } 26 | 27 | private sealed class WeakActionTokenInternal : IDisposable where TTarget : class 28 | { 29 | private object _target; 30 | private object? _action; 31 | 32 | public WeakActionTokenInternal(TTarget target, Action? action = null) 33 | { 34 | _target = GetWeakReference(target); 35 | _action = action; 36 | } 37 | 38 | public void Dispose() 39 | { 40 | if (_action == null) 41 | { 42 | return; 43 | } 44 | 45 | object action; 46 | lock (_target) 47 | { 48 | if (_action == null) 49 | { 50 | return; 51 | } 52 | 53 | action = _action; 54 | _action = null!; 55 | } 56 | 57 | if (_target is WeakReference weakReference) 58 | { 59 | var target = (TTarget)weakReference.Target; 60 | ((Action)action).Invoke(target); 61 | } 62 | 63 | _target = null!; 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /CatCore/ILRepack.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | all 6 | runtime; build; native; contentfiles; analyzers; buildtransitive 7 | 8 | 9 | 10 | 13 | 14 | 15 | $(ProjectDir)$(OutputPath) 16 | Merged\ 17 | $(ILRepackOutputDir)$(AssemblyName).dll 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | <_ReferenceDirsDupl Include="@(ReferencePath->'%(RelativeDir)')" /> 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | <_ILRCmdArg Include="-union"/> 61 | <_ILRCmdArg Include="-parallel"/> 62 | <_ILRCmdArg Include="-internalize"/> 63 | <_ILRCmdArg Include="-renameInternalized"/> 64 | <_ILRCmdArg Include="-lib:%(_ReferenceDirs.Identity)" Condition="'$(OS)' != 'Windows_NT'" /> 65 | <_ILRCmdArg Include="-xmldocs"/> 66 | <_ILRCmdArg Include="-out:$(ILRepackOutput)"/> 67 | <_ILRCmdArg Include="%(RepackInputAssemblies.Identity)"/> 68 | 69 | 70 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /CatCore/Logging/ActionableLogSink.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Core; 3 | using Serilog.Events; 4 | 5 | namespace CatCore.Logging 6 | { 7 | internal sealed class ActionableLogSink : ILogEventSink 8 | { 9 | private readonly Action _logEventHandler; 10 | 11 | public ActionableLogSink(Action logEventHandler) 12 | { 13 | _logEventHandler = logEventHandler; 14 | } 15 | 16 | public void Emit(LogEvent logEvent) 17 | { 18 | _logEventHandler(logEvent); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Logging/CustomLogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Logging 2 | { 3 | public enum CustomLogLevel 4 | { 5 | Trace = 0, 6 | Debug = 1, 7 | Information = 2, 8 | Warning = 3, 9 | Error = 4, 10 | Critical = 5 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CatCore/Logging/SinkExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Configuration; 4 | using Serilog.Core; 5 | using Serilog.Events; 6 | 7 | namespace CatCore.Logging 8 | { 9 | internal static class SinkExtensions 10 | { 11 | /// 12 | /// Writes log events to the EventHandler defined in . 13 | /// 14 | /// Logger sink configuration. 15 | /// The action that will handle the event 16 | /// The minimum level for 17 | /// events passed through the sink. Ignored when is specified. 18 | /// A switch allowing the pass-through minimum level 19 | /// to be changed at runtime. 20 | /// Configuration object allowing method chaining. 21 | internal static LoggerConfiguration Actionable( 22 | this LoggerSinkConfiguration sinkConfiguration, 23 | Action logEventHandler, 24 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 25 | LoggingLevelSwitch? levelSwitch = null) 26 | { 27 | if (logEventHandler == null) 28 | { 29 | throw new ArgumentNullException(nameof(logEventHandler)); 30 | } 31 | 32 | return sinkConfiguration.Sink(new ActionableLogSink(logEventHandler), restrictedToMinimumLevel, levelSwitch); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /CatCore/Models/Api/Requests/GlobalStateRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Api.Requests 4 | { 5 | internal readonly struct GlobalStateRequestDto 6 | { 7 | public bool LaunchInternalApiOnStartup { get; } 8 | public bool LaunchWebPortalOnStartup { get; } 9 | public bool ParseEmojis { get; } 10 | 11 | [JsonConstructor] 12 | public GlobalStateRequestDto(bool launchInternalApiOnStartup, bool launchWebPortalOnStartup, bool parseEmojis) 13 | { 14 | LaunchInternalApiOnStartup = launchInternalApiOnStartup; 15 | LaunchWebPortalOnStartup = launchWebPortalOnStartup; 16 | ParseEmojis = parseEmojis; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /CatCore/Models/Api/Requests/TwitchStateRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Api.Requests 5 | { 6 | internal readonly struct TwitchStateRequestDto 7 | { 8 | public bool SelfEnabled { get; } 9 | 10 | /// 11 | /// Key being the actual userId and the value being the loginname 12 | /// 13 | public Dictionary AdditionalChannelsData { get; } 14 | 15 | public bool ParseBttvEmotes { get; } 16 | public bool ParseFfzEmotes { get; } 17 | public bool ParseTwitchEmotes { get; } 18 | public bool ParseCheermotes { get; } 19 | 20 | [JsonConstructor] 21 | public TwitchStateRequestDto(bool selfEnabled, Dictionary additionalChannelsData, bool parseBttvEmotes, bool parseFfzEmotes, bool parseTwitchEmotes, bool parseCheermotes) 22 | { 23 | SelfEnabled = selfEnabled; 24 | AdditionalChannelsData = additionalChannelsData; 25 | ParseBttvEmotes = parseBttvEmotes; 26 | ParseFfzEmotes = parseFfzEmotes; 27 | ParseTwitchEmotes = parseTwitchEmotes; 28 | ParseCheermotes = parseCheermotes; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /CatCore/Models/Api/Responses/GlobalStateResponseDto.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Config; 2 | 3 | namespace CatCore.Models.Api.Responses 4 | { 5 | internal readonly struct GlobalStateResponseDto 6 | { 7 | public bool LaunchInternalApiOnStartup { get; } 8 | public bool LaunchWebPortalOnStartup { get; } 9 | public bool ParseEmojis { get; } 10 | 11 | public GlobalStateResponseDto(GlobalConfig globalConfig) 12 | { 13 | LaunchInternalApiOnStartup = globalConfig.LaunchInternalApiOnStartup; 14 | LaunchWebPortalOnStartup = globalConfig.LaunchWebPortalOnStartup; 15 | ParseEmojis = globalConfig.HandleEmojis; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /CatCore/Models/Api/Responses/TwitchChannelData.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Api.Responses 2 | { 3 | internal readonly struct TwitchChannelData 4 | { 5 | public string ThumbnailUrl { get; } 6 | public string DisplayName { get; } 7 | public string LoginName { get; } 8 | public string ChannelId { get; } 9 | public bool IsSelf { get; } 10 | 11 | public TwitchChannelData(string thumbnailUrl, string displayName, string loginName, string channelId, bool isSelf) 12 | { 13 | ThumbnailUrl = thumbnailUrl; 14 | DisplayName = displayName; 15 | LoginName = loginName; 16 | ChannelId = channelId; 17 | IsSelf = isSelf; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Api/Responses/TwitchChannelQueryData.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Twitch.Helix.Responses; 2 | 3 | namespace CatCore.Models.Api.Responses 4 | { 5 | internal readonly struct TwitchChannelQueryData 6 | { 7 | public string ThumbnailUrl { get; } 8 | public string DisplayName { get; } 9 | public string LoginName { get; } 10 | public string ChannelId { get; } 11 | 12 | public TwitchChannelQueryData(ChannelData channelData) 13 | { 14 | ThumbnailUrl = channelData.ThumbnailUrl; 15 | DisplayName = channelData.DisplayName; 16 | LoginName = channelData.BroadcasterLogin; 17 | ChannelId = channelData.ChannelId; 18 | } 19 | 20 | public TwitchChannelQueryData(UserData userData) 21 | { 22 | ThumbnailUrl = userData.ProfileImageUrl; 23 | DisplayName = userData.DisplayName; 24 | LoginName = userData.LoginName; 25 | ChannelId = userData.UserId; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Api/Responses/TwitchStateResponseDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using CatCore.Models.Config; 4 | using CatCore.Models.Twitch.Helix.Responses; 5 | using CatCore.Models.Twitch.OAuth; 6 | 7 | namespace CatCore.Models.Api.Responses 8 | { 9 | internal readonly struct TwitchStateResponseDto 10 | { 11 | public bool LoggedIn { get; } 12 | public bool OwnChannelEnabled { get; } 13 | 14 | public List ChannelData { get; } 15 | 16 | public bool ParseBttvEmotes { get; } 17 | public bool ParseFfzEmotes { get; } 18 | public bool ParseTwitchEmotes { get; } 19 | public bool ParseCheermotes { get; } 20 | 21 | public TwitchStateResponseDto(bool isValid, ValidationResponse? loggedInUser, IEnumerable? channelData, TwitchConfig twitchConfig) 22 | { 23 | LoggedIn = isValid; 24 | 25 | OwnChannelEnabled = twitchConfig.OwnChannelEnabled; 26 | ChannelData = channelData? 27 | .Select(x => new TwitchChannelData(x.ProfileImageUrl, x.DisplayName, x.LoginName, x.UserId, x.LoginName == loggedInUser?.LoginName)) 28 | .OrderByDescending(channel => channel.IsSelf) 29 | .ThenBy(channel => channel.DisplayName) 30 | .ToList() ?? new List(); 31 | 32 | ParseBttvEmotes = twitchConfig.ParseBttvEmotes; 33 | ParseFfzEmotes = twitchConfig.ParseFfzEmotes; 34 | ParseTwitchEmotes = twitchConfig.ParseTwitchEmotes; 35 | ParseCheermotes = twitchConfig.ParseCheermotes; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /CatCore/Models/Config/ConfigRoot.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Config 2 | { 3 | internal sealed class ConfigRoot 4 | { 5 | public GlobalConfig GlobalConfig { get; set; } 6 | public TwitchConfig TwitchConfig { get; set; } 7 | 8 | public ConfigRoot() 9 | { 10 | GlobalConfig = new GlobalConfig(); 11 | TwitchConfig = new TwitchConfig(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /CatCore/Models/Config/GlobalConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Config 2 | { 3 | internal sealed class GlobalConfig 4 | { 5 | public bool LaunchInternalApiOnStartup { get; set; } = true; 6 | public bool LaunchWebPortalOnStartup { get; set; } = true; 7 | 8 | public bool HandleEmojis { get; set; } = true; 9 | } 10 | } -------------------------------------------------------------------------------- /CatCore/Models/Config/TwitchConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CatCore.Models.Config 4 | { 5 | internal sealed class TwitchConfig 6 | { 7 | public bool OwnChannelEnabled { get; set; } = true; 8 | 9 | public Dictionary AdditionalChannelsData { get; set; } = new Dictionary(); 10 | 11 | public bool ParseBttvEmotes { get; set; } = true; 12 | public bool ParseFfzEmotes { get; set; } = true; 13 | public bool ParseTwitchEmotes { get; set; } = true; 14 | public bool ParseCheermotes { get; set; } = true; 15 | } 16 | } -------------------------------------------------------------------------------- /CatCore/Models/Credentials/AuthenticationStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Credentials 2 | { 3 | public enum AuthenticationStatus 4 | { 5 | Unknown, 6 | Initializing, 7 | Unauthorized, 8 | Authenticated 9 | } 10 | } -------------------------------------------------------------------------------- /CatCore/Models/Credentials/ICredentials.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Credentials 2 | { 3 | internal interface ICredentials 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /CatCore/Models/Credentials/TwitchCredentials.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | using CatCore.Shared.Models.Twitch.OAuth; 4 | 5 | namespace CatCore.Models.Credentials 6 | { 7 | internal sealed class TwitchCredentials : ICredentials, IEquatable 8 | { 9 | public string? AccessToken { get; } 10 | public string? RefreshToken { get; } 11 | public DateTimeOffset? ValidUntil { get; } 12 | 13 | public TwitchCredentials() 14 | { 15 | } 16 | 17 | [JsonConstructor] 18 | public TwitchCredentials(string? accessToken, string? refreshToken, DateTimeOffset? validUntil) 19 | { 20 | AccessToken = accessToken; 21 | RefreshToken = refreshToken; 22 | ValidUntil = validUntil; 23 | } 24 | 25 | public TwitchCredentials(AuthorizationResponse authorizationResponse) 26 | { 27 | AccessToken = authorizationResponse.AccessToken; 28 | RefreshToken = authorizationResponse.RefreshToken; 29 | ValidUntil = authorizationResponse.ExpiresIn; 30 | } 31 | 32 | public static TwitchCredentials Empty() => new(); 33 | 34 | public bool Equals(TwitchCredentials? other) 35 | { 36 | if (ReferenceEquals(null, other)) 37 | { 38 | return false; 39 | } 40 | 41 | if (ReferenceEquals(this, other)) 42 | { 43 | return true; 44 | } 45 | 46 | return AccessToken == other.AccessToken && RefreshToken == other.RefreshToken; 47 | } 48 | 49 | public override bool Equals(object? obj) 50 | { 51 | return ReferenceEquals(this, obj) || obj is TwitchCredentials other && Equals(other); 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | unchecked 57 | { 58 | var hashCode = (AccessToken != null ? AccessToken.GetHashCode() : 0); 59 | hashCode = (hashCode * 397) ^ (RefreshToken != null ? RefreshToken.GetHashCode() : 0); 60 | return hashCode; 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /CatCore/Models/EventArgs/TwitchChannelsUpdatedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace CatCore.Models.EventArgs 5 | { 6 | public class TwitchChannelsUpdatedEventArgs : System.EventArgs 7 | { 8 | public TwitchChannelsUpdatedEventArgs(IDictionary enabledChannels, IDictionary disabledChannels) 9 | { 10 | EnabledChannels = new ReadOnlyDictionary(enabledChannels); 11 | DisabledChannels = new ReadOnlyDictionary(disabledChannels); 12 | } 13 | 14 | public readonly ReadOnlyDictionary EnabledChannels; 15 | public readonly ReadOnlyDictionary DisabledChannels; 16 | } 17 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/ChatResourceData.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Shared 2 | { 3 | public sealed class ChatResourceData : IChatResourceData 4 | { 5 | public string Id { get; } 6 | public string Name { get; } 7 | public string Url { get; } 8 | public bool IsAnimated { get; } 9 | public string Type { get; } 10 | 11 | public ChatResourceData(string id, string name, string uri, bool isAnimated, string type) 12 | { 13 | Id = id; 14 | Name = name; 15 | Url = uri; 16 | IsAnimated = isAnimated; 17 | Type = type; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/Emoji.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Shared 2 | { 3 | public class Emoji : IChatEmote 4 | { 5 | public string Id { get; } 6 | public string Name { get; } 7 | public int StartIndex { get; } 8 | public int EndIndex { get; } 9 | public string Url { get; } 10 | public bool Animated => false; 11 | 12 | public Emoji(string id, string name, int startIndex, int endIndex, string url) 13 | { 14 | Id = "Emoji_" + id; 15 | Name = name; 16 | StartIndex = startIndex; 17 | EndIndex = endIndex; 18 | Url = url; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/IChatBadge.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Shared 2 | { 3 | public interface IChatBadge 4 | { 5 | public string Id { get; } 6 | public string Name { get; } 7 | public string Uri { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/IChatChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CatCore.Models.Shared 4 | { 5 | public interface IChatChannel : ICloneable 6 | where TChannel : IChatChannel 7 | where TMessage : IChatMessage 8 | { 9 | /// 10 | /// The id of the channel 11 | /// 12 | string Id { get; } 13 | 14 | /// 15 | /// The name of the channel 16 | /// 17 | string Name { get; } 18 | 19 | /// 20 | /// Sends a message to channel that this instance represents 21 | /// 22 | /// The actual message that will be send to said channel 23 | void SendMessage(string message); 24 | } 25 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/IChatEmote.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Shared 2 | { 3 | public interface IChatEmote 4 | { 5 | string Id { get; } 6 | string Name { get; } 7 | int StartIndex { get; } 8 | int EndIndex { get; } 9 | string Url { get; } 10 | bool Animated { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/IChatMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace CatCore.Models.Shared 4 | { 5 | public interface IChatMessage 6 | where TMessage : IChatMessage 7 | where TChannel : IChatChannel 8 | { 9 | /// 10 | /// The internal identifier for this chat message 11 | /// 12 | string Id { get; } 13 | 14 | /// 15 | /// True if this message was generated by the underlying chat service, and not by another user. 16 | /// 17 | bool IsSystemMessage { get; } 18 | 19 | /// 20 | /// True if the message is a /me message, or whatever the equivalent is on the current chat service. 21 | /// 22 | bool IsActionMessage { get; } 23 | 24 | /// 25 | /// True if the logged in user was mentioned/tagged/pinged in this message. 26 | /// 27 | bool IsMentioned { get; } 28 | 29 | /// 30 | /// The exact message the user sent into the chat, *with* any custom formatting applied by the chat service. 31 | /// 32 | string Message { get; } 33 | 34 | /// 35 | /// The user who sent this message 36 | /// 37 | IChatUser Sender { get; } 38 | 39 | /// 40 | /// The channel this message was sent in 41 | /// 42 | TChannel Channel { get; } 43 | 44 | /// 45 | /// The emotes that are contained in this message. 46 | /// 47 | ReadOnlyCollection Emotes { get; } 48 | 49 | /// 50 | /// All the raw metadata associated with this message. This contains platform-specific data for devs who want to access any extra data that may not have been parsed. 51 | /// 52 | ReadOnlyDictionary? Metadata { get; } 53 | } 54 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/IChatResourceData.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Shared 2 | { 3 | public interface IChatResourceData 4 | { 5 | string Id { get; } 6 | string Name { get; } 7 | string Url { get; } 8 | bool IsAnimated { get; } 9 | string Type { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/IChatUser.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Shared 2 | { 3 | public interface IChatUser 4 | { 5 | string Id { get; } 6 | string UserName { get; } 7 | string DisplayName { get; } 8 | string Color { get; } 9 | bool IsBroadcaster { get; } 10 | bool IsModerator { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /CatCore/Models/Shared/PlatformType.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Shared 2 | { 3 | public enum PlatformType 4 | { 5 | Twitch 6 | } 7 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/Base/EmoteBase.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.ThirdParty.Bttv.Base 4 | { 5 | public abstract class EmoteBase 6 | { 7 | [JsonPropertyName("code")] 8 | public string Code { get; } 9 | 10 | [JsonPropertyName("imageType")] 11 | public string ImageType { get; } 12 | 13 | [JsonConstructor] 14 | public EmoteBase(string code, string imageType) 15 | { 16 | Code = code; 17 | ImageType = imageType; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/Base/EmoteUserBase.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.ThirdParty.Bttv.Base 4 | { 5 | public abstract class EmoteUserBase 6 | { 7 | [JsonPropertyName("name")] 8 | public string Name { get; } 9 | 10 | [JsonPropertyName("displayName")] 11 | public string DisplayName { get; } 12 | 13 | [JsonConstructor] 14 | public EmoteUserBase(string name, string displayName) 15 | { 16 | Name = name; 17 | DisplayName = displayName; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/BttvChannelData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.ThirdParty.Bttv 5 | { 6 | public readonly struct BttvChannelData 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; } 10 | 11 | // TODO: Investigate what this would imply... 12 | /*[JsonPropertyName("bots")] 13 | public IReadOnlyList Bots { get; }*/ 14 | 15 | [JsonPropertyName("avatar")] 16 | public string Avatar { get; } 17 | 18 | [JsonPropertyName("channelEmotes")] 19 | public IReadOnlyList ChannelEmotes { get; } 20 | 21 | [JsonPropertyName("sharedEmotes")] 22 | public IReadOnlyList SharedEmotes { get; } 23 | 24 | [JsonConstructor] 25 | public BttvChannelData(string id, string avatar, IReadOnlyList channelEmotes, IReadOnlyList sharedEmotes) 26 | { 27 | Id = id; 28 | Avatar = avatar; 29 | ChannelEmotes = channelEmotes; 30 | SharedEmotes = sharedEmotes; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/BttvEmote.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.ThirdParty.Bttv 4 | { 5 | public sealed class BttvEmote : BttvEmoteBase 6 | { 7 | [JsonPropertyName("userId")] 8 | public string UserId { get; } 9 | 10 | [JsonConstructor] 11 | public BttvEmote(string id, string code, string imageType, string userId) : base(id, code, imageType) 12 | { 13 | UserId = userId; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/BttvEmoteBase.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Models.ThirdParty.Bttv.Base; 3 | 4 | namespace CatCore.Models.ThirdParty.Bttv 5 | { 6 | public abstract class BttvEmoteBase : EmoteBase 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; } 10 | 11 | [JsonConstructor] 12 | protected BttvEmoteBase(string id, string code, string imageType) : base(code, imageType) 13 | { 14 | Id = id; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/BttvEmoteUser.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Models.ThirdParty.Bttv.Base; 3 | 4 | namespace CatCore.Models.ThirdParty.Bttv 5 | { 6 | public sealed class BttvEmoteUser : EmoteUserBase 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; } 10 | 11 | [JsonPropertyName("providerId")] 12 | public string ProviderId { get; } 13 | 14 | [JsonConstructor] 15 | public BttvEmoteUser(string id, string name, string displayName, string providerId) : base(name, displayName) 16 | { 17 | Id = id; 18 | ProviderId = providerId; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/BttvSharedEmote.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.ThirdParty.Bttv 4 | { 5 | public sealed class BttvSharedEmote : BttvEmoteBase 6 | { 7 | [JsonPropertyName("user")] 8 | public BttvEmoteUser User { get; } 9 | 10 | [JsonConstructor] 11 | public BttvSharedEmote(string id, string code, string imageType, BttvEmoteUser user) : base(id, code, imageType) 12 | { 13 | User = user; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/Ffz/FfzEmote.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Models.ThirdParty.Bttv.Base; 3 | 4 | namespace CatCore.Models.ThirdParty.Bttv.Ffz 5 | { 6 | public sealed class FfzEmote : EmoteBase 7 | { 8 | [JsonPropertyName("id")] 9 | public uint Id { get; } 10 | 11 | [JsonPropertyName("user")] 12 | public FfzEmoteUser User { get; } 13 | 14 | [JsonPropertyName("images")] 15 | public FfzImageSizes Images { get; } 16 | 17 | [JsonConstructor] 18 | public FfzEmote(uint id, string code, string imageType, FfzEmoteUser user, FfzImageSizes images) : base(code, imageType) 19 | { 20 | Id = id; 21 | User = user; 22 | Images = images; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/Ffz/FfzEmoteUser.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Models.ThirdParty.Bttv.Base; 3 | 4 | namespace CatCore.Models.ThirdParty.Bttv.Ffz 5 | { 6 | public sealed class FfzEmoteUser : EmoteUserBase 7 | { 8 | [JsonPropertyName("id")] 9 | public uint Id { get; } 10 | 11 | [JsonConstructor] 12 | public FfzEmoteUser(uint id, string name, string displayName) : base(name, displayName) 13 | { 14 | Id = id; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CatCore/Models/ThirdParty/Bttv/Ffz/FfzImageSizes.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.ThirdParty.Bttv.Ffz 4 | { 5 | public readonly struct FfzImageSizes 6 | { 7 | [JsonPropertyName("1x")] 8 | public string? Url1X { get; } 9 | 10 | [JsonPropertyName("2x")] 11 | public string? Url2X { get; } 12 | 13 | [JsonPropertyName("4x")] 14 | public string? Url4X { get; } 15 | 16 | [JsonIgnore] 17 | public string? PreferredUrl => Url4X ?? Url2X ?? Url1X; 18 | 19 | [JsonConstructor] 20 | public FfzImageSizes(string url1X, string url2X, string url4X) 21 | { 22 | Url1X = url1X; 23 | Url2X = url2X; 24 | Url4X = url4X; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/Bans/BanUserRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Requests.Bans 4 | { 5 | internal readonly struct BanUserRequestDto 6 | { 7 | [JsonPropertyName("user_id")] 8 | public string UserId { get; } 9 | 10 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 11 | [JsonPropertyName("duration")] 12 | public uint? Duration { get; } 13 | 14 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 15 | [JsonPropertyName("reason")] 16 | public string? Reason { get; } 17 | 18 | public BanUserRequestDto(string userId, uint? duration, string? reason) 19 | { 20 | UserId = userId; 21 | Duration = duration; 22 | Reason = reason; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/ChatSettingsRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Requests 4 | { 5 | internal readonly struct ChatSettingsRequestDto 6 | { 7 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 8 | [JsonPropertyName("emote_mode")] 9 | public bool? EmoteMode { get; } 10 | 11 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 12 | [JsonPropertyName("follower_mode")] 13 | public bool? FollowerMode { get; } 14 | 15 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 16 | [JsonPropertyName("follower_mode_duration")] 17 | public uint? FollowerModeDurationMinutes { get; } 18 | 19 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 20 | [JsonPropertyName("non_moderator_chat_delay")] 21 | public bool? NonModeratorChatDelay { get; } 22 | 23 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 24 | [JsonPropertyName("non_moderator_chat_delay_duration")] 25 | public uint? NonModeratorChatDelayDurationSeconds { get; } 26 | 27 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 28 | [JsonPropertyName("slow_mode")] 29 | public bool? SlowMode { get; } 30 | 31 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 32 | [JsonPropertyName("slow_mode_wait_time")] 33 | public uint? SlowModeWaitTimeSeconds { get; } 34 | 35 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 36 | [JsonPropertyName("subscriber_mode")] 37 | public bool? SubscriberMode { get; } 38 | 39 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 40 | [JsonPropertyName("unique_chat_mode")] 41 | public bool? UniqueChatMode { get; } 42 | 43 | public ChatSettingsRequestDto(bool? emoteMode, bool? followerMode, uint? followerModeDurationMinutes, bool? nonModeratorChatDelay, uint? nonModeratorChatDelayDurationSeconds, bool? slowMode, 44 | uint? slowModeWaitTimeSeconds, bool? subscriberMode, bool? uniqueChatMode) 45 | { 46 | EmoteMode = emoteMode; 47 | FollowerMode = followerMode; 48 | FollowerModeDurationMinutes = followerModeDurationMinutes; 49 | NonModeratorChatDelay = nonModeratorChatDelay; 50 | NonModeratorChatDelayDurationSeconds = nonModeratorChatDelayDurationSeconds; 51 | SlowMode = slowMode; 52 | SlowModeWaitTimeSeconds = slowModeWaitTimeSeconds; 53 | SubscriberMode = subscriberMode; 54 | UniqueChatMode = uniqueChatMode; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/CreateStreamMarkerRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Requests 4 | { 5 | internal readonly struct CreateStreamMarkerRequestDto 6 | { 7 | [JsonPropertyName("user_id")] 8 | public string UserId { get; } 9 | 10 | [JsonPropertyName("description")] 11 | public string? Description { get; } 12 | 13 | internal CreateStreamMarkerRequestDto(string userId, string? description) 14 | { 15 | UserId = userId; 16 | Description = description; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/LegacyRequestDataWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Requests 4 | { 5 | internal class LegacyRequestDataWrapper 6 | { 7 | [JsonPropertyName("data")] 8 | public T Data { get; } 9 | 10 | public LegacyRequestDataWrapper(T data) 11 | { 12 | Data = data; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/Polls/CreatePollRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Requests.Polls 5 | { 6 | internal readonly struct CreatePollRequestDto 7 | { 8 | [JsonPropertyName("broadcaster_id")] 9 | public string BroadcasterId { get; } 10 | 11 | [JsonPropertyName("title")] 12 | public string Title { get; } 13 | 14 | [JsonPropertyName("choices")] 15 | public List Choices { get; } 16 | 17 | [JsonPropertyName("duration")] 18 | public uint Duration { get; } 19 | 20 | [JsonPropertyName("bits_voting_enabled")] 21 | public bool? BitsVotingEnabled { get; } 22 | 23 | [JsonPropertyName("bits_per_vote")] 24 | public uint? BitsPerVote { get; } 25 | 26 | [JsonPropertyName("channel_points_voting_enabled")] 27 | public bool? ChannelPointsVotingEnabled { get; } 28 | 29 | [JsonPropertyName("channel_points_per_vote")] 30 | public uint? ChannelPointsPerVote { get; } 31 | 32 | public CreatePollRequestDto(string broadcasterId, string title, List choices, uint duration, bool? bitsVotingEnabled = null, uint? bitsPerVote = null, 33 | bool? channelPointsVotingEnabled = null, uint? channelPointsPerVote = null) 34 | { 35 | BroadcasterId = broadcasterId; 36 | Title = title; 37 | Choices = choices; 38 | Duration = duration; 39 | BitsVotingEnabled = bitsVotingEnabled; 40 | BitsPerVote = bitsPerVote; 41 | ChannelPointsVotingEnabled = channelPointsVotingEnabled; 42 | ChannelPointsPerVote = channelPointsPerVote; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/Polls/EndPollRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Helpers.Converters; 3 | using CatCore.Models.Twitch.Shared; 4 | 5 | namespace CatCore.Models.Twitch.Helix.Requests.Polls 6 | { 7 | internal readonly struct EndPollRequestDto 8 | { 9 | [JsonPropertyName("broadcaster_id")] 10 | public string BroadcasterId { get; } 11 | 12 | [JsonPropertyName("id")] 13 | public string PollId { get; } 14 | 15 | [JsonPropertyName("status")] 16 | [JsonConverter(typeof(JsonStringEnumConverter))] 17 | public PollStatus Status { get; } 18 | 19 | public EndPollRequestDto(string broadcasterId, string pollId, PollStatus status) 20 | { 21 | BroadcasterId = broadcasterId; 22 | PollId = pollId; 23 | Status = status; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/Polls/PollChoice.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Requests.Polls 4 | { 5 | internal readonly struct PollChoice 6 | { 7 | [JsonPropertyName("title")] 8 | public string Title { get; } 9 | 10 | public PollChoice(string title) 11 | { 12 | Title = title; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/Predictions/CreatePredictionsRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Requests.Predictions 5 | { 6 | internal readonly struct CreatePredictionsRequestDto 7 | { 8 | [JsonPropertyName("broadcaster_id")] 9 | public string BroadcasterId { get; } 10 | 11 | [JsonPropertyName("title")] 12 | public string Title { get; } 13 | 14 | [JsonPropertyName("outcomes")] 15 | public List Choices { get; } 16 | 17 | [JsonPropertyName("prediction_window")] 18 | public uint Duration { get; } 19 | 20 | public CreatePredictionsRequestDto(string broadcasterId, string title, List choices, uint duration) 21 | { 22 | BroadcasterId = broadcasterId; 23 | Title = title; 24 | Choices = choices; 25 | Duration = duration; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/Predictions/EndPollRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CatCore.Helpers.Converters; 3 | using CatCore.Models.Twitch.Shared; 4 | 5 | namespace CatCore.Models.Twitch.Helix.Requests.Predictions 6 | { 7 | internal readonly struct EndPredictionRequestDto 8 | { 9 | [JsonPropertyName("broadcaster_id")] 10 | public string BroadcasterId { get; } 11 | 12 | [JsonPropertyName("id")] 13 | public string PollId { get; } 14 | 15 | [JsonPropertyName("status")] 16 | [JsonConverter(typeof(JsonStringEnumConverter))] 17 | public PredictionStatus Status { get; } 18 | 19 | [JsonPropertyName("winning_outcome_id")] 20 | public string? WinningOutcomeId { get; } 21 | 22 | public EndPredictionRequestDto(string broadcasterId, string pollId, PredictionStatus status, string? winningOutcomeId) 23 | { 24 | BroadcasterId = broadcasterId; 25 | PollId = pollId; 26 | Status = status; 27 | WinningOutcomeId = winningOutcomeId; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/Predictions/Outcome.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Requests.Predictions 4 | { 5 | internal readonly struct Outcome 6 | { 7 | [JsonPropertyName("title")] 8 | public string Title { get; } 9 | 10 | public Outcome(string title) 11 | { 12 | Title = title; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/SendChatAnnouncementColor.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Twitch.Helix.Requests 2 | { 3 | public enum SendChatAnnouncementColor 4 | { 5 | Primary, 6 | Blue, 7 | Green, 8 | Orange, 9 | Purple 10 | } 11 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/SendChatAnnouncementRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Requests 5 | { 6 | internal readonly struct SendChatAnnouncementRequestDto 7 | { 8 | [JsonPropertyName("message")] 9 | public string Message { get; } 10 | 11 | [JsonPropertyName("color")] 12 | public string Color { get; } 13 | 14 | public SendChatAnnouncementRequestDto(string message, SendChatAnnouncementColor color) 15 | { 16 | Message = message; 17 | Color = color switch 18 | { 19 | SendChatAnnouncementColor.Primary => "primary", 20 | SendChatAnnouncementColor.Blue => "blue", 21 | SendChatAnnouncementColor.Green => "green", 22 | SendChatAnnouncementColor.Orange => "orange", 23 | SendChatAnnouncementColor.Purple => "purple", 24 | _ => throw new ArgumentOutOfRangeException(nameof(color), color, "An invalid color was provided.") 25 | }; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Requests/UserChatColor.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Twitch.Helix.Requests 2 | { 3 | public enum UserChatColor 4 | { 5 | Blue, 6 | BlueViolet, 7 | CadetBlue, 8 | Chocolate, 9 | Coral, 10 | DodgerBlue, 11 | Firebrick, 12 | GoldenRod, 13 | Green, 14 | HotPink, 15 | OrangeRed, 16 | Red, 17 | SeaGreen, 18 | SpringGreen, 19 | YellowGreen 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Badges/BadgeData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses.Badges 5 | { 6 | public readonly struct BadgeData 7 | { 8 | [JsonPropertyName("set_id")] 9 | public string SetId { get; } 10 | 11 | [JsonPropertyName("versions")] 12 | public IReadOnlyList Versions { get; } 13 | 14 | [JsonConstructor] 15 | public BadgeData(string setId, IReadOnlyList versions) 16 | { 17 | SetId = setId; 18 | Versions = versions; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Badges/BadgeVersion.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Badges 4 | { 5 | public readonly struct BadgeVersion 6 | { 7 | [JsonPropertyName("id")] 8 | public string Id { get; } 9 | 10 | [JsonPropertyName("image_url_1x")] 11 | public string ImageUrl1X { get; } 12 | 13 | [JsonPropertyName("image_url_2x")] 14 | public string ImageUrl2X { get; } 15 | 16 | [JsonPropertyName("image_url_4x")] 17 | public string ImageUrl4X { get; } 18 | 19 | [JsonConstructor] 20 | public BadgeVersion(string id, string imageUrl1X, string imageUrl2X, string imageUrl4X) 21 | { 22 | Id = id; 23 | ImageUrl1X = imageUrl1X; 24 | ImageUrl2X = imageUrl2X; 25 | ImageUrl4X = imageUrl4X; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bans/BanUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses.Bans 5 | { 6 | public readonly struct BanUser 7 | { 8 | [JsonPropertyName("broadcaster_id")] 9 | public string BroadcasterId { get; } 10 | 11 | [JsonPropertyName("moderator_id")] 12 | public string ModeratorId { get; } 13 | 14 | [JsonPropertyName("user_id")] 15 | public string UserId { get; } 16 | 17 | [JsonPropertyName("created_at")] 18 | public DateTimeOffset CreatedAt { get; } 19 | 20 | [JsonPropertyName("end_time")] 21 | public DateTimeOffset? EndTime { get; } 22 | 23 | [JsonConstructor] 24 | public BanUser(string broadcasterId, string moderatorId, string userId, DateTimeOffset createdAt, DateTimeOffset? endTime) 25 | { 26 | BroadcasterId = broadcasterId; 27 | ModeratorId = moderatorId; 28 | UserId = userId; 29 | CreatedAt = createdAt; 30 | EndTime = endTime; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bans/BannedUserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses.Bans 5 | { 6 | public readonly struct BannedUserInfo 7 | { 8 | [JsonPropertyName("user_id")] 9 | public string UserId { get; } 10 | 11 | [JsonPropertyName("user_login")] 12 | public string UserLogin { get; } 13 | 14 | [JsonPropertyName("user_name")] 15 | public string UserName { get; } 16 | 17 | [JsonPropertyName("expires_at")] 18 | public string? ExpiresAtRaw { get; } 19 | 20 | public DateTimeOffset? ExpiresAt => DateTimeOffset.TryParse(ExpiresAtRaw, out var parsedValue) ? parsedValue : null; 21 | 22 | [JsonPropertyName("created_at")] 23 | public DateTimeOffset CreatedAt { get; } 24 | 25 | [JsonPropertyName("reason")] 26 | public string Reason { get; } 27 | 28 | [JsonPropertyName("moderator_id")] 29 | public string ModeratorId { get; } 30 | 31 | [JsonPropertyName("moderator_login")] 32 | public string ModeratorLogin { get; } 33 | 34 | [JsonPropertyName("moderator_name")] 35 | public string ModeratorName { get; } 36 | 37 | [JsonConstructor] 38 | public BannedUserInfo(string userId, string userLogin, string userName, string? expiresAtRaw, DateTimeOffset createdAt, string reason, string moderatorId, string moderatorLogin, 39 | string moderatorName 40 | ) 41 | { 42 | UserId = userId; 43 | UserLogin = userLogin; 44 | UserName = userName; 45 | ExpiresAtRaw = expiresAtRaw; 46 | CreatedAt = createdAt; 47 | Reason = reason; 48 | ModeratorId = moderatorId; 49 | ModeratorLogin = moderatorLogin; 50 | ModeratorName = moderatorName; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bits/Cheermotes/CheermoteGroupData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | using CatCore.Helpers.Converters; 5 | 6 | namespace CatCore.Models.Twitch.Helix.Responses.Bits.Cheermotes 7 | { 8 | public readonly struct CheermoteGroupData 9 | { 10 | [JsonPropertyName("prefix")] 11 | public string Prefix { get; } 12 | 13 | [JsonPropertyName("tiers")] 14 | public IReadOnlyList Tiers { get; } 15 | 16 | [JsonPropertyName("type")] 17 | [JsonConverter(typeof(JsonStringEnumConverter))] 18 | public CheermoteType Type { get; } 19 | 20 | [JsonPropertyName("order")] 21 | public int Order { get; } 22 | 23 | [JsonPropertyName("last_updated")] 24 | public DateTimeOffset LastUpdated { get; } 25 | 26 | [JsonPropertyName("is_charitable")] 27 | public bool IsCharitable { get; } 28 | 29 | [JsonConstructor] 30 | public CheermoteGroupData(string prefix, IReadOnlyList tiers, CheermoteType type, int order, DateTimeOffset lastUpdated, bool isCharitable) 31 | { 32 | Prefix = prefix; 33 | Tiers = tiers; 34 | Type = type; 35 | Order = order; 36 | LastUpdated = lastUpdated; 37 | IsCharitable = isCharitable; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bits/Cheermotes/CheermoteImageColorGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Bits.Cheermotes 4 | { 5 | public readonly struct CheermoteImageColorGroup 6 | { 7 | [JsonPropertyName("light")] 8 | public CheermoteImageTypesGroup Light { get; } 9 | 10 | [JsonPropertyName("dark")] 11 | public CheermoteImageTypesGroup Dark { get; } 12 | 13 | [JsonConstructor] 14 | public CheermoteImageColorGroup(CheermoteImageTypesGroup light, CheermoteImageTypesGroup dark) 15 | { 16 | Light = light; 17 | Dark = dark; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bits/Cheermotes/CheermoteImageSizeGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Bits.Cheermotes 4 | { 5 | public readonly struct CheermoteImageSizeGroup 6 | { 7 | [JsonPropertyName("1")] 8 | public string Size1 { get; } 9 | 10 | [JsonPropertyName("1.5")] 11 | public string Size15 { get; } 12 | 13 | [JsonPropertyName("2")] 14 | public string Size2 { get; } 15 | 16 | [JsonPropertyName("3")] 17 | public string Size3 { get; } 18 | 19 | [JsonPropertyName("4")] 20 | public string Size4 { get; } 21 | 22 | [JsonConstructor] 23 | public CheermoteImageSizeGroup(string size1, string size15, string size2, string size3, string size4) 24 | { 25 | Size1 = size1; 26 | Size15 = size15; 27 | Size2 = size2; 28 | Size3 = size3; 29 | Size4 = size4; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bits/Cheermotes/CheermoteImageTypesGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Bits.Cheermotes 4 | { 5 | public readonly struct CheermoteImageTypesGroup 6 | { 7 | [JsonPropertyName("static")] 8 | public CheermoteImageSizeGroup Static { get; } 9 | 10 | [JsonPropertyName("animated")] 11 | public CheermoteImageSizeGroup Animated { get; } 12 | 13 | [JsonConstructor] 14 | public CheermoteImageTypesGroup(CheermoteImageSizeGroup @static, CheermoteImageSizeGroup animated) 15 | { 16 | Static = @static; 17 | Animated = animated; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bits/Cheermotes/CheermoteTier.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Bits.Cheermotes 4 | { 5 | public readonly struct CheermoteTier 6 | { 7 | [JsonPropertyName("id")] 8 | public string Id { get; } 9 | 10 | [JsonPropertyName("min_bits")] 11 | public uint MinBits { get; } 12 | 13 | [JsonPropertyName("color")] 14 | public string Color { get; } 15 | 16 | [JsonPropertyName("images")] 17 | public CheermoteImageColorGroup Images { get; } 18 | 19 | [JsonPropertyName("can_cheer")] 20 | public bool CanCheer { get; } 21 | 22 | [JsonPropertyName("show_in_bits_card")] 23 | public bool ShowInBitsCard { get; } 24 | 25 | [JsonConstructor] 26 | public CheermoteTier(string id, uint minBits, string color, CheermoteImageColorGroup images, bool canCheer, bool showInBitsCard) 27 | { 28 | Id = id; 29 | MinBits = minBits; 30 | Color = color; 31 | Images = images; 32 | CanCheer = canCheer; 33 | ShowInBitsCard = showInBitsCard; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Bits/Cheermotes/CheermoteType.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Bits.Cheermotes 4 | { 5 | public enum CheermoteType 6 | { 7 | [EnumMember(Value = "global_first_party")] 8 | GlobalFirstParty, 9 | 10 | [EnumMember(Value = "global_third_party")] 11 | GlobalThirdParty, 12 | 13 | [EnumMember(Value = "channel_custom")] 14 | ChannelCustom, 15 | 16 | [EnumMember(Value = "display_only")] 17 | DisplayOnly, 18 | 19 | [EnumMember(Value = "sponsored")] 20 | Sponsored 21 | } 22 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/ChannelData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace CatCore.Models.Twitch.Helix.Responses 6 | { 7 | public readonly struct ChannelData 8 | { 9 | [JsonPropertyName("broadcaster_login")] 10 | public string BroadcasterLogin { get; } 11 | 12 | [JsonPropertyName("broadcaster_language")] 13 | public string BroadcasterLanguage { get; } 14 | 15 | [JsonPropertyName("id")] 16 | public string ChannelId { get; } 17 | 18 | [JsonPropertyName("display_name")] 19 | public string DisplayName { get; } 20 | 21 | [JsonPropertyName("game_id")] 22 | public string GameId { get; } 23 | 24 | [JsonPropertyName("title")] 25 | public string Title { get; } 26 | 27 | [JsonPropertyName("thumbnail_url")] 28 | public string ThumbnailUrl { get; } 29 | 30 | [JsonPropertyName("is_live")] 31 | public bool IsLive { get; } 32 | 33 | [JsonPropertyName("started_at")] 34 | public string StartedAtRaw { get; } 35 | 36 | [JsonIgnore] 37 | public DateTimeOffset? StartedAt => DateTimeOffset.TryParse(StartedAtRaw, out var parsedValue) ? parsedValue : null; 38 | 39 | [JsonPropertyName("tag_ids")] 40 | [Obsolete("TagIds is deprecated by Twitch and will only contain an empty list. Use the Tags property instead which can contain custom tags. Will be removed in the next major version of CatCore")] 41 | public List TagIds { get; } 42 | 43 | [JsonPropertyName("tags")] 44 | public List Tags { get; } 45 | 46 | [JsonConstructor] 47 | public ChannelData(string broadcasterLogin, string broadcasterLanguage, string channelId, string displayName, string gameId, string title, string thumbnailUrl, bool isLive, 48 | string startedAtRaw, List tags) 49 | { 50 | BroadcasterLogin = broadcasterLogin; 51 | BroadcasterLanguage = broadcasterLanguage; 52 | ChannelId = channelId; 53 | DisplayName = displayName; 54 | GameId = gameId; 55 | Title = title; 56 | ThumbnailUrl = thumbnailUrl; 57 | IsLive = isLive; 58 | StartedAtRaw = startedAtRaw; 59 | Tags = tags; 60 | #pragma warning disable CS0618 // Type or member is obsolete 61 | TagIds = new List(0); 62 | #pragma warning restore CS0618 // Type or member is obsolete 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/ChatSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses 4 | { 5 | public readonly struct ChatSettings 6 | { 7 | [JsonPropertyName("broadcaster_id")] 8 | public string BroadcasterId { get; } 9 | 10 | [JsonPropertyName("emote_mode")] 11 | public bool EmoteMode { get; } 12 | 13 | [JsonPropertyName("follower_mode")] 14 | public bool FollowerMode { get; } 15 | 16 | [JsonPropertyName("follower_mode_duration")] 17 | public uint? FollowerModeDurationMinutes { get; } 18 | 19 | [JsonPropertyName("moderator_id")] 20 | public string? ModeratorId { get; } 21 | 22 | [JsonPropertyName("non_moderator_chat_delay")] 23 | public bool NonModeratorChatDelay { get; } 24 | 25 | [JsonPropertyName("non_moderator_chat_delay_duration")] 26 | public uint? NonModeratorChatDelayDurationSeconds { get; } 27 | 28 | [JsonPropertyName("slow_mode")] 29 | public bool SlowMode { get; } 30 | 31 | [JsonPropertyName("slow_mode_wait_time")] 32 | public uint? SlowModeWaitTimeSeconds { get; } 33 | 34 | [JsonPropertyName("subscriber_mode")] 35 | public bool SubscriberMode { get; } 36 | 37 | [JsonPropertyName("unique_chat_mode")] 38 | public bool UniqueChatMode { get; } 39 | 40 | [JsonConstructor] 41 | public ChatSettings(string broadcasterId, bool emoteMode, bool followerMode, uint? followerModeDurationMinutes, string moderatorId, bool nonModeratorChatDelay, 42 | uint? nonModeratorChatDelayDurationSeconds, bool slowMode, uint? slowModeWaitTimeSeconds, bool subscriberMode, bool uniqueChatMode) 43 | { 44 | BroadcasterId = broadcasterId; 45 | EmoteMode = emoteMode; 46 | SlowMode = slowMode; 47 | SlowModeWaitTimeSeconds = slowModeWaitTimeSeconds; 48 | ModeratorId = moderatorId; 49 | NonModeratorChatDelay = nonModeratorChatDelay; 50 | NonModeratorChatDelayDurationSeconds = nonModeratorChatDelayDurationSeconds; 51 | FollowerMode = followerMode; 52 | FollowerModeDurationMinutes = followerModeDurationMinutes; 53 | SubscriberMode = subscriberMode; 54 | UniqueChatMode = uniqueChatMode; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/CreateStreamMarkerData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses 5 | { 6 | public readonly struct CreateStreamMarkerData 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; } 10 | 11 | [JsonPropertyName("description")] 12 | public string? Description { get; } 13 | 14 | [JsonPropertyName("position_seconds")] 15 | public uint PositionSeconds { get; } 16 | 17 | [JsonPropertyName("created_at")] 18 | public DateTimeOffset CreatedAt { get; } 19 | 20 | [JsonConstructor] 21 | public CreateStreamMarkerData(string id, string? description, uint positionSeconds, DateTimeOffset createdAt) 22 | { 23 | Id = id; 24 | Description = description; 25 | PositionSeconds = positionSeconds; 26 | CreatedAt = createdAt; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Emotes/ChannelEmote.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | using CatCore.Models.Twitch.Shared; 4 | 5 | namespace CatCore.Models.Twitch.Helix.Responses.Emotes 6 | { 7 | public class ChannelEmote : GlobalEmote 8 | { 9 | [JsonPropertyName("tier")] 10 | public string Tier { get; } 11 | 12 | [JsonPropertyName("emote_type")] 13 | public string EmoteType { get; } 14 | 15 | [JsonPropertyName("emote_set_id")] 16 | public string EmoteSetId { get; } 17 | 18 | [JsonConstructor] 19 | public ChannelEmote(string id, string name, DefaultImage images, IReadOnlyList format, IReadOnlyList scale, IReadOnlyList themeMode, string tier, string emoteType, 20 | string emoteSetId) 21 | : base(id, name, images, format, scale, themeMode) 22 | { 23 | Tier = tier; 24 | EmoteType = emoteType; 25 | EmoteSetId = emoteSetId; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Emotes/GlobalEmote.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | using CatCore.Models.Twitch.Shared; 4 | 5 | namespace CatCore.Models.Twitch.Helix.Responses.Emotes 6 | { 7 | public class GlobalEmote 8 | { 9 | [JsonPropertyName("id")] 10 | public string Id { get; } 11 | 12 | [JsonPropertyName("name")] 13 | public string Name { get; } 14 | 15 | [JsonPropertyName("images")] 16 | public DefaultImage Images { get; } 17 | 18 | [JsonPropertyName("format")] 19 | public IReadOnlyList Format { get; } 20 | 21 | [JsonPropertyName("scale")] 22 | public IReadOnlyList Scale { get; } 23 | 24 | [JsonPropertyName("theme_mode")] 25 | public IReadOnlyList ThemeMode { get; } 26 | 27 | [JsonConstructor] 28 | public GlobalEmote(string id, string name, DefaultImage images, IReadOnlyList format, IReadOnlyList scale, IReadOnlyList themeMode) 29 | { 30 | Id = id; 31 | Name = name; 32 | Images = images; 33 | Format = format; 34 | Scale = scale; 35 | ThemeMode = themeMode; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Pagination.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses 4 | { 5 | public readonly struct Pagination 6 | { 7 | [JsonPropertyName("cursor")] 8 | public string Cursor { get; } 9 | 10 | [JsonConstructor] 11 | public Pagination(string cursor) 12 | { 13 | Cursor = cursor; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Polls/PollChoice.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Polls 4 | { 5 | public readonly struct PollChoice 6 | { 7 | [JsonPropertyName("id")] 8 | public string Id { get; } 9 | 10 | [JsonPropertyName("title")] 11 | public string Title { get; } 12 | 13 | [JsonPropertyName("votes")] 14 | public uint Votes { get; } 15 | 16 | [JsonPropertyName("channel_points_votes")] 17 | public uint ChannelPointsVotes { get; } 18 | 19 | [JsonPropertyName("bits_votes")] 20 | public uint BitsVotes { get; } 21 | 22 | [JsonConstructor] 23 | public PollChoice(string id, string title, uint votes, uint channelPointsVotes, uint bitsVotes) 24 | { 25 | Id = id; 26 | Title = title; 27 | Votes = votes; 28 | ChannelPointsVotes = channelPointsVotes; 29 | BitsVotes = bitsVotes; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Polls/PollData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | using CatCore.Helpers.Converters; 5 | using CatCore.Models.Twitch.Shared; 6 | 7 | namespace CatCore.Models.Twitch.Helix.Responses.Polls 8 | { 9 | public readonly struct PollData 10 | { 11 | [JsonPropertyName("id")] 12 | public string Id { get; } 13 | 14 | [JsonPropertyName("broadcaster_id")] 15 | public string BroadcasterId { get; } 16 | 17 | [JsonPropertyName("broadcaster_name")] 18 | public string BroadcasterName { get; } 19 | 20 | [JsonPropertyName("broadcaster_login")] 21 | public string BroadcasterLogin { get; } 22 | 23 | [JsonPropertyName("title")] 24 | public string Title { get; } 25 | 26 | [JsonPropertyName("choices")] 27 | public IReadOnlyList Choices { get; } 28 | 29 | [JsonPropertyName("bits_voting_enabled")] 30 | public bool BitsVotingEnabled { get; } 31 | 32 | [JsonPropertyName("bits_per_vote")] 33 | public uint BitsPerVote { get; } 34 | 35 | [JsonPropertyName("channel_points_voting_enabled")] 36 | public bool ChannelPointsVotingEnabled { get; } 37 | 38 | [JsonPropertyName("channel_points_per_vote")] 39 | public uint ChannelPointsPerVote { get; } 40 | 41 | [JsonPropertyName("status")] 42 | [JsonConverter(typeof(JsonStringEnumConverter))] 43 | public PollStatus Status { get; } 44 | 45 | [JsonPropertyName("duration")] 46 | public uint Duration { get; } 47 | 48 | [JsonPropertyName("started_at")] 49 | public DateTimeOffset StartedAt { get; } 50 | 51 | [JsonPropertyName("ended_at")] 52 | public DateTimeOffset? EndedAt { get; } 53 | 54 | [JsonConstructor] 55 | public PollData(string id, string broadcasterId, string broadcasterName, string broadcasterLogin, string title, IReadOnlyList choices, bool bitsVotingEnabled, uint bitsPerVote, 56 | bool channelPointsVotingEnabled, uint channelPointsPerVote, PollStatus status, uint duration, DateTimeOffset startedAt, DateTimeOffset? endedAt) 57 | { 58 | Id = id; 59 | BroadcasterId = broadcasterId; 60 | BroadcasterName = broadcasterName; 61 | BroadcasterLogin = broadcasterLogin; 62 | Title = title; 63 | Choices = choices; 64 | BitsVotingEnabled = bitsVotingEnabled; 65 | BitsPerVote = bitsPerVote; 66 | ChannelPointsVotingEnabled = channelPointsVotingEnabled; 67 | ChannelPointsPerVote = channelPointsPerVote; 68 | Status = status; 69 | Duration = duration; 70 | StartedAt = startedAt; 71 | EndedAt = endedAt; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Predictions/Outcome.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses.Predictions 5 | { 6 | public readonly struct Outcome 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; } 10 | 11 | [JsonPropertyName("title")] 12 | public string Title { get; } 13 | 14 | [JsonPropertyName("users")] 15 | public uint Users { get; } 16 | 17 | [JsonPropertyName("channel_points")] 18 | public uint ChannelPoints { get; } 19 | 20 | [JsonPropertyName("top_predictors")] 21 | public IReadOnlyList TopPredictors { get; } 22 | 23 | [JsonPropertyName("color")] 24 | public string Color { get; } 25 | 26 | [JsonConstructor] 27 | public Outcome(string id, string title, uint users, uint channelPoints, IReadOnlyList topPredictors, string color) 28 | { 29 | Id = id; 30 | Title = title; 31 | Users = users; 32 | ChannelPoints = channelPoints; 33 | TopPredictors = topPredictors; 34 | Color = color; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Predictions/PredictionData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | using CatCore.Helpers.Converters; 5 | using CatCore.Models.Twitch.Shared; 6 | 7 | namespace CatCore.Models.Twitch.Helix.Responses.Predictions 8 | { 9 | public readonly struct PredictionData 10 | { 11 | [JsonPropertyName("id")] 12 | public string Id { get; } 13 | 14 | [JsonPropertyName("broadcaster_id")] 15 | public string BroadcasterId { get; } 16 | 17 | [JsonPropertyName("broadcaster_name")] 18 | public string BroadcasterName { get; } 19 | 20 | [JsonPropertyName("broadcaster_login")] 21 | public string BroadcasterLogin { get; } 22 | 23 | [JsonPropertyName("title")] 24 | public string Title { get; } 25 | 26 | [JsonPropertyName("winning_outcome_id")] 27 | public string WinningOutcomeId { get; } 28 | 29 | [JsonPropertyName("outcomes")] 30 | public IReadOnlyList Outcomes { get; } 31 | 32 | [JsonPropertyName("prediction_window")] 33 | public uint Duration { get; } 34 | 35 | [JsonPropertyName("status")] 36 | [JsonConverter(typeof(JsonStringEnumConverter))] 37 | public PredictionStatus Status { get; } 38 | 39 | [JsonPropertyName("created_at")] 40 | public DateTimeOffset CreatedAt { get; } 41 | 42 | [JsonPropertyName("ended_at")] 43 | public DateTimeOffset? EndedAt { get; } 44 | 45 | [JsonPropertyName("locked_at")] 46 | public DateTimeOffset? LockedAt { get; } 47 | 48 | [JsonConstructor] 49 | public PredictionData(string id, string broadcasterId, string broadcasterName, string broadcasterLogin, string title, string winningOutcomeId, IReadOnlyList outcomes, uint duration, 50 | PredictionStatus status, DateTimeOffset createdAt, DateTimeOffset? endedAt, DateTimeOffset? lockedAt) 51 | { 52 | Id = id; 53 | BroadcasterId = broadcasterId; 54 | BroadcasterName = broadcasterName; 55 | BroadcasterLogin = broadcasterLogin; 56 | Title = title; 57 | WinningOutcomeId = winningOutcomeId; 58 | Outcomes = outcomes; 59 | Duration = duration; 60 | Status = status; 61 | CreatedAt = createdAt; 62 | EndedAt = endedAt; 63 | LockedAt = lockedAt; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Predictions/Predictor.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses.Predictions 4 | { 5 | public readonly struct Predictor 6 | { 7 | [JsonPropertyName("user_id")] 8 | public string UserId { get; } 9 | 10 | [JsonPropertyName("user_login")] 11 | public string LoginName { get; } 12 | 13 | [JsonPropertyName("user_name")] 14 | public string DisplayName { get; } 15 | 16 | [JsonPropertyName("channel_points_used")] 17 | public uint ChannelPointsUsed { get; } 18 | 19 | [JsonPropertyName("channel_points_won")] 20 | public uint ChannelPointsWon { get; } 21 | 22 | [JsonConstructor] 23 | public Predictor(string userId, string loginName, string displayName, uint channelPointsUsed, uint channelPointsWon) 24 | { 25 | UserId = userId; 26 | LoginName = loginName; 27 | DisplayName = displayName; 28 | ChannelPointsUsed = channelPointsUsed; 29 | ChannelPointsWon = channelPointsWon; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/ResponseBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses 5 | { 6 | public readonly struct ResponseBase 7 | { 8 | [JsonPropertyName("data")] 9 | public List Data { get; } 10 | 11 | [JsonConstructor] 12 | public ResponseBase(List data) 13 | { 14 | Data = data; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/ResponseBaseWithPagination.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses 5 | { 6 | public readonly struct ResponseBaseWithPagination 7 | { 8 | [JsonPropertyName("data")] 9 | public List Data { get; } 10 | 11 | [JsonPropertyName("pagination")] 12 | public Pagination Pagination { get; } 13 | 14 | [JsonConstructor] 15 | public ResponseBaseWithPagination(List data, Pagination pagination) 16 | { 17 | Data = data; 18 | Pagination = pagination; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/ResponseBaseWithTemplate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses 5 | { 6 | public readonly struct ResponseBaseWithTemplate 7 | { 8 | [JsonPropertyName("data")] 9 | public List Data { get; } 10 | 11 | [JsonPropertyName("template")] 12 | public string Template { get; } 13 | 14 | [JsonConstructor] 15 | public ResponseBaseWithTemplate(List data, string template) 16 | { 17 | Data = data; 18 | Template = template; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/StartRaidData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses 5 | { 6 | public readonly struct StartRaidData 7 | { 8 | [JsonPropertyName("created_at")] 9 | public DateTimeOffset CreatedAt { get; } 10 | 11 | [JsonPropertyName("is_mature")] 12 | public bool IsMature { get; } 13 | 14 | [JsonConstructor] 15 | public StartRaidData(DateTimeOffset createdAt, bool isMature) 16 | { 17 | CreatedAt = createdAt; 18 | IsMature = isMature; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/Stream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace CatCore.Models.Twitch.Helix.Responses 6 | { 7 | public readonly struct Stream 8 | { 9 | [JsonPropertyName("id")] 10 | public string Id { get; } 11 | 12 | [JsonPropertyName("user_id")] 13 | public string UserId { get; } 14 | 15 | [JsonPropertyName("user_login")] 16 | public string LoginName { get; } 17 | 18 | [JsonPropertyName("user_name")] 19 | public string DisplayName { get; } 20 | 21 | [JsonPropertyName("game_id")] 22 | public string GameId { get; } 23 | 24 | [JsonPropertyName("game_name")] 25 | public string GameName { get; } 26 | 27 | [JsonPropertyName("type")] 28 | public string Type { get; } 29 | 30 | [JsonPropertyName("title")] 31 | public string Title { get; } 32 | 33 | [JsonPropertyName("viewer_count")] 34 | public uint ViewerCount { get; } 35 | 36 | [JsonPropertyName("started_at")] 37 | public DateTimeOffset StartedAt { get; } 38 | 39 | [JsonPropertyName("language")] 40 | public string Language { get; } 41 | 42 | [JsonPropertyName("thumbnail_url")] 43 | public string ThumbnailUrl { get; } 44 | 45 | [JsonPropertyName("tag_ids")] 46 | [Obsolete("TagIds is deprecated by Twitch and will only contain an empty list. Use the Tags property instead which can contain custom tags. Will be removed in the next major version of CatCore")] 47 | public IReadOnlyList TagIds { get; } 48 | 49 | [JsonPropertyName("tags")] 50 | public IReadOnlyList Tags { get; } 51 | 52 | [JsonPropertyName("is_mature")] 53 | public bool IsMature { get; } 54 | 55 | [JsonConstructor] 56 | public Stream(string id, string userId, string loginName, string displayName, string gameId, string gameName, string type, string title, uint viewerCount, DateTimeOffset startedAt, 57 | string language, string thumbnailUrl, IReadOnlyList tags, bool isMature) 58 | { 59 | Id = id; 60 | UserId = userId; 61 | LoginName = loginName; 62 | DisplayName = displayName; 63 | GameId = gameId; 64 | GameName = gameName; 65 | Type = type; 66 | Title = title; 67 | ViewerCount = viewerCount; 68 | StartedAt = startedAt; 69 | Language = language; 70 | ThumbnailUrl = thumbnailUrl; 71 | #pragma warning disable CS0618 72 | TagIds = new List(0); 73 | #pragma warning restore CS0618 74 | Tags = tags; 75 | IsMature = isMature; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/UserChatColorData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Helix.Responses 4 | { 5 | public readonly struct UserChatColorData 6 | { 7 | [JsonPropertyName("user_id")] 8 | public string UserId { get; } 9 | 10 | [JsonPropertyName("user_name")] 11 | public string UserName { get; } 12 | 13 | [JsonPropertyName("user_login")] 14 | public string UserLogin { get; } 15 | 16 | [JsonPropertyName("color")] 17 | public string Color { get; } 18 | 19 | [JsonConstructor] 20 | public UserChatColorData(string userId, string userName, string userLogin, string color) 21 | { 22 | UserId = userId; 23 | UserName = userName; 24 | UserLogin = userLogin; 25 | Color = color; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Helix/Responses/UserData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.Helix.Responses 5 | { 6 | public readonly struct UserData 7 | { 8 | [JsonPropertyName("id")] 9 | public string UserId { get; } 10 | 11 | [JsonPropertyName("login")] 12 | public string LoginName { get; } 13 | 14 | [JsonPropertyName("display_name")] 15 | public string DisplayName { get; } 16 | 17 | [JsonPropertyName("description")] 18 | public string Description { get; } 19 | 20 | [JsonPropertyName("profile_image_url")] 21 | public string ProfileImageUrl { get; } 22 | 23 | [JsonPropertyName("offline_image_url")] 24 | public string OfflineImageUrl { get; } 25 | 26 | // User’s type: "staff", "admin", "global_mod", or "" 27 | [JsonPropertyName("type")] 28 | public string Type { get; } 29 | 30 | // User’s broadcaster type: "partner", "affiliate", or "" 31 | [JsonPropertyName("broadcaster_type")] 32 | public string BroadcasterType { get; } 33 | 34 | [JsonPropertyName("created_at")] 35 | public DateTimeOffset CreatedAt { get; } 36 | 37 | [JsonPropertyName("view_count")] 38 | public uint ViewCount { get; } 39 | 40 | /// 41 | /// Returned if the request includes the user:read:email scope. 42 | /// 43 | [JsonPropertyName("email")] 44 | public string Email { get; } 45 | 46 | [JsonConstructor] 47 | public UserData(string userId, string loginName, string displayName, string description, string profileImageUrl, string offlineImageUrl, string type, string broadcasterType, 48 | DateTimeOffset createdAt, uint viewCount, string email) 49 | { 50 | UserId = userId; 51 | LoginName = loginName; 52 | DisplayName = displayName; 53 | Description = description; 54 | ProfileImageUrl = profileImageUrl; 55 | OfflineImageUrl = offlineImageUrl; 56 | Type = type; 57 | BroadcasterType = broadcasterType; 58 | CreatedAt = createdAt; 59 | ViewCount = viewCount; 60 | Email = email; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/IrcCommands.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Twitch.IRC 2 | { 3 | public static class IrcCommands 4 | { 5 | public const string RPL_ENDOFMOTD = "376"; 6 | public const string PING = nameof(PING); 7 | public const string PONG = nameof(PONG); 8 | public const string JOIN = nameof(JOIN); 9 | public const string PART = nameof(PART); 10 | public const string NOTICE = nameof(NOTICE); 11 | public const string PRIVMSG = nameof(PRIVMSG); 12 | } 13 | 14 | public static class TwitchIrcCommands 15 | { 16 | public const string CLEARCHAT = nameof(CLEARCHAT); 17 | public const string CLEARMSG = nameof(CLEARMSG); 18 | public const string GLOBALUSERSTATE = nameof(GLOBALUSERSTATE); 19 | public const string ROOMSTATE = nameof(ROOMSTATE); 20 | public const string USERNOTICE = nameof(USERNOTICE); 21 | public const string USERSTATE = nameof(USERSTATE); 22 | public const string RECONNECT = nameof(RECONNECT); 23 | public const string HOSTTARGET = nameof(HOSTTARGET); 24 | } 25 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/IrcMessageTags.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Twitch.IRC 2 | { 3 | /// 4 | /// All IRC message tag keys that are publicly described by Twitch documentation 5 | /// 6 | /// See also: https://dev.twitch.tv/docs/irc/tags 7 | public static class IrcMessageTags 8 | { 9 | public const string BAN_DURATION = "ban-duration"; 10 | public const string LOGIN = "login"; 11 | public const string TARGET_MSG_ID = "target-msg-id"; 12 | public const string BADGE_INFO = "badge-info"; 13 | public const string BADGES = "badges"; 14 | public const string COLOR = "color"; 15 | public const string DISPLAY_NAME = "display-name"; 16 | public const string EMOTE_SETS = "emote-sets"; 17 | public const string TURBO = "turbo"; 18 | public const string USER_ID = "user-id"; 19 | public const string USER_TYPE = "user-type"; 20 | public const string BITS = "bits"; 21 | public const string EMOTES = "emotes"; 22 | public const string ID = "id"; 23 | public const string MOD = "mod"; 24 | public const string ROOM_ID = "room-id"; 25 | public const string SUBSCRIBER = "subscriber"; 26 | public const string TMI_SENT_TS = "tmi-sent-ts"; 27 | public const string EMOTE_ONLY = "emote-only"; 28 | public const string FOLLOWERS_ONLY = "followers-only"; 29 | public const string R9_K = "r9k"; 30 | public const string SLOW = "slow"; 31 | public const string SUBS_ONLY = "subs-only"; 32 | public const string SYSTEM_MSG = "system-msg"; 33 | public const string MSG_ID = "msg-id"; 34 | 35 | public const string REPLY_PARENT_MSG_ID = "reply-parent-msg-id"; 36 | public const string REPLY_PARENT_USER_ID = "reply-parent-user-id"; 37 | public const string REPLY_PARENT_USER_LOGIN = "reply-parent-user-login"; 38 | public const string REPLY_PARENT_DISPLAY_NAME = "reply-parent-display-name"; 39 | public const string REPLY_PARENT_MSG_BODY = "reply-parent-msg-body"; 40 | 41 | public const string MSG_PARAM_CUMULATIVE_MONTHS = "msg-param-cumulative-months"; 42 | public const string MSG_PARAM_DISPLAY_NAME = "msg-param-displayName"; 43 | public const string MSG_PARAM_LOGIN = "msg-param-login"; 44 | public const string MSG_PARAM_MONTHS = "msg-param-months"; 45 | public const string MSG_PARAM_PROMO_GIFT_TOTAL = "msg-param-promo-gift-total"; 46 | public const string MSG_PARAM_PROMO_NAME = "msg-param-promo-name"; 47 | public const string MSG_PARAM_RECIPIENT_DISPLAY_NAME = "msg-param-recipient-display-name"; 48 | public const string MSG_PARAM_RECIPIENT_ID = "msg-param-recipient-id"; 49 | public const string MSG_PARAM_RECIPIENT_USER_NAME = "msg-param-recipient-user-name"; 50 | public const string MSG_PARAM_SENDER_LOGIN = "msg-param-sender-login"; 51 | public const string MSG_PARAM_SENDER_NAME = "msg-param-sender-name"; 52 | public const string MSG_PARAM_SHOULD_SHARE_STREAK = "msg-param-should-share-streak"; 53 | public const string MSG_PARAM_STREAK_MONTHS = "msg-param-streak-months"; 54 | public const string MSG_PARAM_SUB_PLAN = "msg-param-sub-plan"; 55 | public const string MSG_PARAM_SUB_PLAN_NAME = "msg-param-sub-plan-name"; 56 | public const string MSG_PARAM_VIEWER_COUNT = "msg-param-viewerCount"; 57 | public const string MSG_PARAM_RITUAL_NAME = "msg-param-ritual-name"; 58 | public const string MSG_PARAM_THRESHOLD = "msg-param-threshold"; 59 | public const string MSG_PARAM_GIFT_MONTHS = "msg-param-gift-months"; 60 | } 61 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/MessageSendingRateLimit.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Twitch.IRC 2 | { 3 | internal enum MessageSendingRateLimit 4 | { 5 | /// 6 | /// Applies to everyone who doesn't have broadcaster/moderator permissions in the channel to which the message will be send 7 | /// 8 | Normal = 20, 9 | 10 | /// 11 | /// Applies to the broadcaster and moderators of the channel to which the message will be send 12 | /// 13 | Relaxed = 100 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/TwitchGlobalUserState.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CatCore.Models.Twitch.IRC 4 | { 5 | public sealed class TwitchGlobalUserState 6 | { 7 | // TODO: Look into converting into a dictionary 8 | [PublicAPI] 9 | public string? BadgeInfo { get; internal set; } 10 | 11 | // TODO: Look into converting into a dictionary 12 | [PublicAPI] 13 | public string? Badges { get; internal set; } 14 | 15 | [PublicAPI] 16 | public string? Color { get; internal set; } 17 | 18 | [PublicAPI] 19 | public string UserId { get; internal set; } 20 | 21 | [PublicAPI] 22 | public string? DisplayName { get; internal set; } 23 | 24 | // TODO: Look into converting into a list 25 | [PublicAPI] 26 | public string EmoteSets { get; internal set; } 27 | 28 | public TwitchGlobalUserState(string? badgeInfo, string? badges, string? color, string userId, string? displayName, string emoteSets) 29 | { 30 | BadgeInfo = badgeInfo; 31 | Badges = badges; 32 | Color = color; 33 | UserId = userId; 34 | DisplayName = displayName; 35 | EmoteSets = emoteSets; 36 | } 37 | 38 | /*badge-info= 39 | badges= 40 | color=#FF69B4 41 | display-name=RealEris 42 | emote-sets=0,300374282,303777092,592920959,610186276 43 | user-id=405499635 44 | user-type=*/ 45 | } 46 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/TwitchMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using CatCore.Models.Shared; 3 | using JetBrains.Annotations; 4 | 5 | namespace CatCore.Models.Twitch.IRC 6 | { 7 | public sealed class TwitchMessage : IChatMessage 8 | { 9 | /// 10 | [PublicAPI] 11 | public string Id { get; internal set; } 12 | 13 | /// 14 | [PublicAPI] 15 | public bool IsSystemMessage { get; internal set; } 16 | 17 | /// 18 | [PublicAPI] 19 | public bool IsActionMessage { get; internal set; } 20 | 21 | /// 22 | [PublicAPI] 23 | public bool IsMentioned { get; internal set; } 24 | 25 | /// 26 | [PublicAPI] 27 | public string Message { get; internal set; } 28 | 29 | /// 30 | [PublicAPI] 31 | public IChatUser Sender { get; internal set; } 32 | 33 | /// 34 | [PublicAPI] 35 | public TwitchChannel Channel { get; internal set; } 36 | 37 | /// 38 | [PublicAPI] 39 | public ReadOnlyCollection Emotes { get; internal set; } 40 | 41 | /// 42 | [PublicAPI] 43 | public ReadOnlyDictionary? Metadata { get; internal set; } 44 | 45 | /// 46 | /// The IRC message type for this TwitchMessage 47 | /// 48 | [PublicAPI] 49 | public string Type { get; internal set; } 50 | 51 | /// 52 | /// The number of bits in this message, if any. 53 | /// 54 | [PublicAPI] 55 | public uint Bits { get; internal set; } 56 | 57 | public TwitchMessage(string id, bool isSystemMessage, bool isActionMessage, bool isMentioned, string message, IChatUser sender, TwitchChannel channel, ReadOnlyCollection emotes, 58 | ReadOnlyDictionary? metadata, string type, uint bits) 59 | { 60 | Id = id; 61 | IsSystemMessage = isSystemMessage; 62 | IsActionMessage = isActionMessage; 63 | IsMentioned = isMentioned; 64 | Message = message; 65 | Sender = sender; 66 | Channel = channel; 67 | Metadata = metadata; 68 | Emotes = emotes; 69 | Type = type; 70 | Bits = bits; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/TwitchRoomState.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CatCore.Models.Twitch.IRC 4 | { 5 | public sealed class TwitchRoomState 6 | { 7 | /// 8 | /// The id of the channel to which the room state belongs. 9 | /// 10 | [PublicAPI] 11 | public string RoomId { get; internal set; } 12 | 13 | /// 14 | /// If enabled, only emotes are allowed in chat. 15 | /// 16 | [PublicAPI] 17 | public bool EmoteOnly { get; internal set; } 18 | 19 | /// 20 | /// If enabled, controls which followers can chat. 21 | /// 22 | [PublicAPI] 23 | public bool FollowersOnly { get; internal set; } 24 | 25 | /// 26 | /// If enabled, only subscribers and moderators can chat. 27 | /// 28 | [PublicAPI] 29 | public bool SubscribersOnly { get; internal set; } 30 | 31 | /// 32 | /// If enabled, messages with more than 9 characters must be unique. 33 | /// 34 | [PublicAPI] 35 | public bool R9K { get; internal set; } 36 | 37 | /// 38 | /// The number of seconds a chatter without moderator privileges must wait between sending messages. 39 | /// 40 | [PublicAPI] 41 | public int SlowModeInterval { get; internal set; } 42 | 43 | /// 44 | /// If FollowersOnly is true, this specifies the number of minutes a user must be following before they can chat. 45 | /// 46 | [PublicAPI] 47 | public int MinFollowTime { get; internal set; } 48 | 49 | public TwitchRoomState(string roomId, bool emoteOnly, bool followersOnly, bool subscribersOnly, bool r9K, int slowModeInterval, int minFollowTime) 50 | { 51 | RoomId = roomId; 52 | EmoteOnly = emoteOnly; 53 | FollowersOnly = followersOnly; 54 | SubscribersOnly = subscribersOnly; 55 | R9K = r9K; 56 | SlowModeInterval = slowModeInterval; 57 | MinFollowTime = minFollowTime; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/TwitchUser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using CatCore.Models.Shared; 3 | using JetBrains.Annotations; 4 | 5 | namespace CatCore.Models.Twitch.IRC 6 | { 7 | public sealed class TwitchUser : IChatUser 8 | { 9 | [PublicAPI] 10 | public string Id { get; internal set; } 11 | 12 | [PublicAPI] 13 | public string UserName { get; internal set; } 14 | 15 | [PublicAPI] 16 | public string DisplayName { get; internal set; } 17 | 18 | [PublicAPI] 19 | public string Color { get; internal set; } 20 | 21 | [PublicAPI] 22 | public bool IsModerator { get; internal set; } 23 | 24 | [PublicAPI] 25 | public bool IsBroadcaster { get; internal set; } 26 | 27 | [PublicAPI] 28 | public bool IsSubscriber { get; internal set; } 29 | 30 | [PublicAPI] 31 | public bool IsTurbo { get; internal set; } 32 | 33 | [PublicAPI] 34 | public bool IsVip { get; internal set; } 35 | 36 | [PublicAPI] 37 | public ReadOnlyCollection Badges { get; } 38 | 39 | public TwitchUser(string id, string userName, string displayName, string color, bool isModerator, bool isBroadcaster, bool isSubscriber, bool isTurbo, bool isVip, 40 | ReadOnlyCollection badges) 41 | { 42 | Id = id; 43 | UserName = userName; 44 | DisplayName = displayName; 45 | Color = color; 46 | IsModerator = isModerator; 47 | IsBroadcaster = isBroadcaster; 48 | IsSubscriber = isSubscriber; 49 | IsTurbo = isTurbo; 50 | IsVip = isVip; 51 | Badges = badges; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/IRC/TwitchUserState.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CatCore.Models.Twitch.IRC 4 | { 5 | public sealed class TwitchUserState 6 | { 7 | // TODO: Look into converting into a dictionary 8 | [PublicAPI] 9 | public string? BadgeInfo { get; internal set; } 10 | 11 | // TODO: Look into converting into a dictionary 12 | [PublicAPI] 13 | public string? Badges { get; internal set; } 14 | 15 | [PublicAPI] 16 | public string? Color { get; internal set; } 17 | 18 | [PublicAPI] 19 | public string UserId { get; internal set; } 20 | 21 | [PublicAPI] 22 | public string? DisplayName { get; internal set; } 23 | 24 | // TODO: Look into converting into a list 25 | [PublicAPI] 26 | public string? EmoteSets { get; internal set; } 27 | 28 | [PublicAPI] 29 | public bool IsModerator { get; internal set; } 30 | 31 | [PublicAPI] 32 | public bool IsBroadcaster { get; internal set; } 33 | 34 | [PublicAPI] 35 | public bool IsSubscriber { get; internal set; } 36 | 37 | [PublicAPI] 38 | public bool IsTurbo { get; internal set; } 39 | 40 | [PublicAPI] 41 | public bool IsVip { get; internal set; } 42 | 43 | public TwitchUserState(string? badgeInfo, string? badges, string? color, string userId, string? displayName, string? emoteSets) 44 | { 45 | BadgeInfo = badgeInfo; 46 | Badges = badges; 47 | Color = color; 48 | UserId = userId; 49 | DisplayName = displayName; 50 | EmoteSets = emoteSets; 51 | 52 | UpdatePermissions(); 53 | } 54 | 55 | internal void UpdateState(string? badgeInfo, string? badges, string? color, string userId, string? displayName, string? emoteSets) 56 | { 57 | BadgeInfo = badgeInfo; 58 | Badges = badges; 59 | Color = color; 60 | UserId = userId; 61 | DisplayName = displayName; 62 | EmoteSets = emoteSets; 63 | 64 | UpdatePermissions(); 65 | } 66 | 67 | private void UpdatePermissions() 68 | { 69 | if (Badges != null) 70 | { 71 | IsModerator = Badges.Contains("moderator/"); 72 | IsBroadcaster = Badges.Contains("broadcaster/"); 73 | IsSubscriber = Badges.Contains("subscriber/") || Badges.Contains("founder/"); 74 | IsTurbo = Badges.Contains("turbo/"); 75 | IsVip = Badges.Contains("vip/"); 76 | } 77 | else 78 | { 79 | IsModerator = false; 80 | IsBroadcaster = false; 81 | IsSubscriber = false; 82 | IsTurbo = false; 83 | IsVip = false; 84 | } 85 | } 86 | 87 | /*badge-info= 88 | badges=moderator/1 89 | color=#FF69B4 90 | display-name=RealEris 91 | emote-sets=0,300374282,303777092,592920959,610186276 92 | mod=1 93 | subscriber=0 94 | user-type=mod*/ 95 | } 96 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Media/TwitchBadge.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Shared; 2 | 3 | namespace CatCore.Models.Twitch.Media 4 | { 5 | public class TwitchBadge : IChatBadge 6 | { 7 | public string Id { get; } 8 | public string Name { get; } 9 | public string Uri { get; } 10 | 11 | public TwitchBadge(string id, string name, string uri) 12 | { 13 | Id = id; 14 | Name = name; 15 | Uri = uri; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Media/TwitchCheermoteData.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Shared; 2 | 3 | namespace CatCore.Models.Twitch.Media 4 | { 5 | public sealed class TwitchCheermoteData : IChatResourceData 6 | { 7 | private const string TYPE = "TwitchCheermote"; 8 | 9 | public string Id { get; } 10 | public string Name { get; } 11 | public string Url { get; } 12 | public bool IsAnimated { get; } 13 | public uint MinBits { get; } 14 | public string Color { get; } 15 | public bool CanCheer { get; } 16 | public string Type => TYPE; 17 | 18 | public TwitchCheermoteData(string name, string url, bool isAnimated, uint minBits, string color, bool canCheer) 19 | { 20 | Id = Type + "_" + name; 21 | Name = name; 22 | Url = url; 23 | IsAnimated = isAnimated; 24 | MinBits = minBits; 25 | Color = color; 26 | CanCheer = canCheer; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Media/TwitchEmote.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Shared; 2 | using JetBrains.Annotations; 3 | 4 | namespace CatCore.Models.Twitch.Media 5 | { 6 | public sealed class TwitchEmote : IChatEmote 7 | { 8 | [PublicAPI] 9 | public string Id { get; } 10 | public string Name { get; } 11 | public int StartIndex { get; } 12 | public int EndIndex { get; } 13 | public string Url { get; } 14 | public bool Animated { get; } 15 | public uint Bits { get; } 16 | public string? Color { get; } 17 | 18 | public TwitchEmote(string id, string name, int startIndex, int endIndex, string url) 19 | { 20 | Id = id; 21 | Name = name; 22 | StartIndex = startIndex; 23 | EndIndex = endIndex; 24 | Url = url; 25 | Animated = false; 26 | Bits = 0; 27 | } 28 | 29 | public TwitchEmote(string id, string name, int startIndex, int endIndex, string url, bool animated) 30 | { 31 | Id = id; 32 | Name = name; 33 | StartIndex = startIndex; 34 | EndIndex = endIndex; 35 | Url = url; 36 | Animated = animated; 37 | Bits = 0; 38 | } 39 | 40 | public TwitchEmote(string id, string name, int startIndex, int endIndex, string url, bool animated, uint bits, string color) 41 | { 42 | Id = id; 43 | Name = name; 44 | StartIndex = startIndex; 45 | EndIndex = endIndex; 46 | Url = url; 47 | Animated = animated; 48 | Bits = bits; 49 | Color = color; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/OAuth/ValidationResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.OAuth 5 | { 6 | internal readonly struct ValidationResponse 7 | { 8 | [JsonPropertyName("client_id")] 9 | public string ClientId { get; } 10 | 11 | [JsonPropertyName("login")] 12 | public string LoginName { get; } 13 | 14 | [JsonPropertyName("user_id")] 15 | public string UserId { get; } 16 | 17 | [JsonPropertyName("scopes")] 18 | public string[] Scopes { get; } 19 | 20 | [JsonPropertyName("expires_in")] 21 | public int ExpiresInRaw { get; } 22 | 23 | public DateTimeOffset ExpiresIn { get; } 24 | 25 | [JsonConstructor] 26 | public ValidationResponse(string clientId, string loginName, string userId, string[] scopes, int expiresInRaw) 27 | { 28 | ClientId = clientId; 29 | LoginName = loginName; 30 | UserId = userId; 31 | Scopes = scopes; 32 | ExpiresInRaw = expiresInRaw; 33 | ExpiresIn = DateTimeOffset.Now.AddSeconds(expiresInRaw); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/PubSubMessageTypes.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Twitch.PubSub 2 | { 3 | internal static class PubSubMessageTypes 4 | { 5 | public const string RESPONSE = nameof(RESPONSE); 6 | public const string PONG = nameof(PONG); 7 | public const string RECONNECT = nameof(RECONNECT); 8 | public const string MESSAGE = nameof(MESSAGE); 9 | } 10 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/PubSubTopics.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Models.Twitch.PubSub 2 | { 3 | internal static class PubSubTopics 4 | { 5 | public const string VIDEO_PLAYBACK = "video-playback-by-id"; 6 | public const string FOLLOWING = "following"; 7 | public const string POLLS = "polls"; 8 | public const string PREDICTIONS = "predictions-channel-v1"; 9 | public const string CHANNEL_POINTS_CHANNEL_V1 = "channel-points-channel-v1"; 10 | 11 | public static string FormatVideoPlaybackTopic(string channelId) => VIDEO_PLAYBACK + '.' + channelId; 12 | public static string FormatFollowingTopic(string channelId) => FOLLOWING + '.' + channelId; 13 | public static string FormatPollsTopic(string channelId) => POLLS + '.' + channelId; 14 | public static string FormatPredictionsTopic(string channelId) => PREDICTIONS + '.' + channelId; 15 | public static string FormatChannelPointsChannelV1Topic(string channelId) => CHANNEL_POINTS_CHANNEL_V1 + '.' + channelId; 16 | 17 | internal static class VideoPlaybackSubTopics 18 | { 19 | public const string VIEW_COUNT = "viewcount"; 20 | public const string STREAM_UP = "stream-up"; 21 | public const string STREAM_DOWN = "stream-down"; 22 | public const string COMMERCIAL = "commercial"; 23 | } 24 | 25 | internal static class ChannelPointsChannelV1SubTopics 26 | { 27 | public const string REWARD_REDEEMED = "reward-redeemed"; 28 | } 29 | 30 | // TODO: Check feasibility implementing all topics below 31 | // == Globally available ================ 32 | // video-playback-by-id.{_channelId} 33 | // stream-change-by-channel.{_channelId} 34 | // stream-chat-room-v1.{_channelId} 35 | // raid.{_channelId} 36 | // following.{_channelId} 37 | // chat_moderator_actions.{_twitchAuthService.LoggedInUser!.Value.UserId}.{_channelId} 38 | // community-points-channel-v1.{_channelId} 39 | // chatrooms-user-v1.{channelId} 40 | 41 | // == Only on token channel ============= 42 | // channel-bits-events-v1.{_channelId} 43 | // channel-bits-events-v2.{_channelId} 44 | // channel-bits-badge-unlocks.{_channelId} 45 | // channel-points-channel-v1.{_channelId} 46 | // channel-subscribe-events-v1.{_channelId} 47 | 48 | // whispers.{_channelId} 49 | 50 | // == Unsure ============================ 51 | // leaderboard-events-v1.bits-usage-by-channel-v1-{_channelId}-ALLTIME 52 | // leaderboard-events-v1.sub-gifts-sent-{_channelId}-ALLTIME 53 | // channel-cheer-events-public-v1.{_channelId} 54 | 55 | // hype-train-events-v1.{_channelId} 56 | // polls.{_channelId} 57 | // predictions-channel-v1.{_channelId} 58 | } 59 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Requests/TopicNegotiationMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Requests 4 | { 5 | internal readonly struct TopicNegotiationMessage 6 | { 7 | internal const string LISTEN = nameof(LISTEN); 8 | internal const string UNLISTEN = nameof(UNLISTEN); 9 | 10 | public TopicNegotiationMessage(string type, TopicNegotiationMessageData data, string nonce) 11 | { 12 | Type = type; 13 | Data = data; 14 | Nonce = nonce; 15 | } 16 | 17 | [JsonPropertyName("type")] 18 | public string Type { get; } 19 | 20 | [JsonPropertyName("data")] 21 | public TopicNegotiationMessageData Data { get; } 22 | 23 | [JsonPropertyName("nonce")] 24 | public string Nonce { get; } 25 | } 26 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Requests/TopicNegotiationMessageData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Requests 4 | { 5 | internal readonly struct TopicNegotiationMessageData 6 | { 7 | public TopicNegotiationMessageData(string[] topics, string? token = null) 8 | { 9 | Token = token; 10 | Topics = topics; 11 | } 12 | 13 | [JsonPropertyName("topics")] 14 | public string[] Topics { get; } 15 | 16 | [JsonPropertyName("auth_token")] 17 | public string? Token { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/ChannelPointsChannelV1/GlobalCooldown.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.ChannelPointsChannelV1 4 | { 5 | public readonly struct GlobalCooldown 6 | { 7 | [JsonPropertyName("is_enabled")] 8 | public bool Enabled { get; } 9 | 10 | [JsonPropertyName("global_cooldown_seconds")] 11 | public uint CooldownSeconds { get; } 12 | 13 | [JsonConstructor] 14 | public GlobalCooldown(bool enabled, uint cooldownSeconds) 15 | { 16 | Enabled = enabled; 17 | CooldownSeconds = cooldownSeconds; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/ChannelPointsChannelV1/MaxPerStream.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.ChannelPointsChannelV1 4 | { 5 | public readonly struct MaxPerStream 6 | { 7 | [JsonPropertyName("is_enabled")] 8 | public bool Enabled { get; } 9 | 10 | [JsonPropertyName("max_per_stream")] 11 | public uint MaxCount { get; } 12 | 13 | [JsonConstructor] 14 | public MaxPerStream(bool enabled, uint maxCount) 15 | { 16 | Enabled = enabled; 17 | MaxCount = maxCount; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/ChannelPointsChannelV1/MaxPerUserPerStream.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.ChannelPointsChannelV1 4 | { 5 | public readonly struct MaxPerUserPerStream 6 | { 7 | [JsonPropertyName("is_enabled")] 8 | public bool Enabled { get; } 9 | 10 | [JsonPropertyName("max_per_user_per_stream")] 11 | public uint MaxCount { get; } 12 | 13 | [JsonConstructor] 14 | public MaxPerUserPerStream(bool enabled, uint maxCount) 15 | { 16 | Enabled = enabled; 17 | MaxCount = maxCount; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/ChannelPointsChannelV1/RewardRedeemedData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.ChannelPointsChannelV1 4 | { 5 | public readonly struct RewardRedeemedData 6 | { 7 | [JsonPropertyName("id")] 8 | public string Id { get; } 9 | 10 | [JsonPropertyName("user")] 11 | public User User { get; } 12 | 13 | [JsonPropertyName("channel_id")] 14 | public string ChannelId { get; } 15 | 16 | [JsonPropertyName("redeemed_at")] 17 | public string RedeemedAt { get; } 18 | 19 | [JsonPropertyName("reward")] 20 | public Reward Reward { get; } 21 | 22 | [JsonPropertyName("status")] 23 | public string Status { get; } 24 | 25 | [JsonConstructor] 26 | public RewardRedeemedData(string id, User user, string channelId, string redeemedAt, Reward reward, string status) 27 | { 28 | Id = id; 29 | User = user; 30 | ChannelId = channelId; 31 | RedeemedAt = redeemedAt; 32 | Reward = reward; 33 | Status = status; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/ChannelPointsChannelV1/User.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.ChannelPointsChannelV1 4 | { 5 | public readonly struct User 6 | { 7 | [JsonPropertyName("id")] 8 | public string Id { get; } 9 | 10 | [JsonPropertyName("login")] 11 | public string Login { get; } 12 | 13 | [JsonPropertyName("display_name")] 14 | public string DisplayName { get; } 15 | 16 | [JsonConstructor] 17 | public User(string id, string login, string displayName) 18 | { 19 | Id = id; 20 | Login = login; 21 | DisplayName = displayName; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Follow.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using JetBrains.Annotations; 3 | 4 | namespace CatCore.Models.Twitch.PubSub.Responses 5 | { 6 | [PublicAPI] 7 | public readonly struct Follow 8 | { 9 | [JsonPropertyName("user_id")] 10 | public string UserId { get; } 11 | 12 | [JsonPropertyName("username")] 13 | public string Username { get; } 14 | 15 | [JsonPropertyName("display_name")] 16 | public string DisplayName { get; } 17 | 18 | [JsonConstructor] 19 | public Follow(string userId, string username, string displayName) 20 | { 21 | UserId = userId; 22 | Username = username; 23 | DisplayName = displayName; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/ContributorBase.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public abstract class ContributorBase 6 | { 7 | [JsonPropertyName("user_id")] 8 | public string UserId { get; } 9 | 10 | [JsonPropertyName("display_name")] 11 | public string DisplayName { get; } 12 | 13 | protected ContributorBase(string userId, string displayName) 14 | { 15 | UserId = userId; 16 | DisplayName = displayName; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/PollChoice.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public readonly struct PollChoice 6 | { 7 | [JsonPropertyName("choice_id")] 8 | public string ChoiceId { get; } 9 | 10 | [JsonPropertyName("title")] 11 | public string Title { get; } 12 | 13 | [JsonPropertyName("votes")] 14 | public Votes Votes { get; } 15 | 16 | [JsonPropertyName("tokens")] 17 | public Tokens Tokens { get; } 18 | 19 | [JsonPropertyName("total_voters")] 20 | public uint TotalVoters { get; } 21 | 22 | [JsonConstructor] 23 | public PollChoice(string choiceId, string title, Votes votes, Tokens tokens, uint totalVoters) 24 | { 25 | ChoiceId = choiceId; 26 | Title = title; 27 | Votes = votes; 28 | Tokens = tokens; 29 | TotalVoters = totalVoters; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/PollData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | using CatCore.Helpers.Converters; 4 | using CatCore.Models.Twitch.Shared; 5 | 6 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 7 | { 8 | public readonly struct PollData 9 | { 10 | [JsonPropertyName("poll_id")] 11 | public string PollId { get; } 12 | 13 | [JsonPropertyName("owned_by")] 14 | public string OwnedBy { get; } 15 | 16 | [JsonPropertyName("created_by")] 17 | public string CreatedBy { get; } 18 | 19 | [JsonPropertyName("title")] 20 | public string Title { get; } 21 | 22 | [JsonPropertyName("started_at")] 23 | public string StartedAtRaw { get; } 24 | 25 | [JsonPropertyName("ended_at")] 26 | public string EndedAtRaw { get; } 27 | 28 | [JsonPropertyName("ended_by")] 29 | public object EndedByRaw { get; } 30 | 31 | [JsonPropertyName("duration_seconds")] 32 | public uint DurationSeconds { get; } 33 | 34 | [JsonPropertyName("settings")] 35 | public PollSettings Settings { get; } 36 | 37 | [JsonPropertyName("status")] 38 | [JsonConverter(typeof(JsonStringEnumConverter))] 39 | public PollStatus Status { get; } 40 | 41 | [JsonPropertyName("choices")] 42 | public IReadOnlyList Choices { get; } 43 | 44 | [JsonPropertyName("votes")] 45 | public Votes Votes { get; } 46 | 47 | [JsonPropertyName("tokens")] 48 | public Tokens Tokens { get; } 49 | 50 | [JsonPropertyName("total_voters")] 51 | public uint TotalVoters { get; } 52 | 53 | [JsonPropertyName("remaining_duration_milliseconds")] 54 | public uint RemainingDurationMilliseconds { get; } 55 | 56 | [JsonPropertyName("top_contributor")] 57 | public TopBitsContributor? TopContributor { get; } 58 | 59 | [JsonPropertyName("top_bits_contributor")] 60 | public TopBitsContributor? TopBitsContributor { get; } 61 | 62 | [JsonPropertyName("top_channel_points_contributor")] 63 | public TopChannelPointsContributor? TopChannelPointsContributor { get; } 64 | 65 | [JsonConstructor] 66 | public PollData(string pollId, string ownedBy, string createdBy, string title, string startedAtRaw, string endedAtRaw, object endedByRaw, uint durationSeconds, PollSettings settings, 67 | PollStatus status, IReadOnlyList choices, Votes votes, Tokens tokens, uint totalVoters, uint remainingDurationMilliseconds, TopBitsContributor? topContributor, 68 | TopBitsContributor? topBitsContributor, TopChannelPointsContributor? topChannelPointsContributor) 69 | { 70 | PollId = pollId; 71 | OwnedBy = ownedBy; 72 | CreatedBy = createdBy; 73 | Title = title; 74 | StartedAtRaw = startedAtRaw; 75 | EndedAtRaw = endedAtRaw; 76 | EndedByRaw = endedByRaw; 77 | DurationSeconds = durationSeconds; 78 | Settings = settings; 79 | Status = status; 80 | Choices = choices; 81 | Votes = votes; 82 | Tokens = tokens; 83 | TotalVoters = totalVoters; 84 | RemainingDurationMilliseconds = remainingDurationMilliseconds; 85 | TopContributor = topContributor; 86 | TopBitsContributor = topBitsContributor; 87 | TopChannelPointsContributor = topChannelPointsContributor; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/PollSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public readonly struct PollSettings 6 | { 7 | [JsonPropertyName("multi_choice")] 8 | public PollSettingsEntry MultiChoice { get; } 9 | 10 | [JsonPropertyName("subscriber_only")] 11 | public PollSettingsEntry SubscriberOnly { get; } 12 | 13 | [JsonPropertyName("subscriber_multiplier")] 14 | public PollSettingsEntry SubscriberMultiplier { get; } 15 | 16 | [JsonPropertyName("bits_votes")] 17 | public PollSettingsEntry BitsVotes { get; } 18 | 19 | [JsonPropertyName("channel_points_votes")] 20 | public PollSettingsEntry ChannelPointsVotes { get; } 21 | 22 | [JsonConstructor] 23 | public PollSettings(PollSettingsEntry multiChoice, PollSettingsEntry subscriberOnly, PollSettingsEntry subscriberMultiplier, PollSettingsEntry bitsVotes, PollSettingsEntry channelPointsVotes) 24 | { 25 | MultiChoice = multiChoice; 26 | SubscriberOnly = subscriberOnly; 27 | SubscriberMultiplier = subscriberMultiplier; 28 | BitsVotes = bitsVotes; 29 | ChannelPointsVotes = channelPointsVotes; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/PollSettingsEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public readonly struct PollSettingsEntry 6 | { 7 | [JsonPropertyName("is_enabled")] 8 | public bool IsEnabled { get; } 9 | 10 | [JsonPropertyName("cost")] 11 | public uint? Cost { get; } 12 | 13 | [JsonConstructor] 14 | public PollSettingsEntry(bool isEnabled, uint? cost) 15 | { 16 | IsEnabled = isEnabled; 17 | Cost = cost; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/Tokens.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public readonly struct Tokens 6 | { 7 | [JsonPropertyName("bits")] 8 | public uint Bits { get; } 9 | 10 | [JsonPropertyName("channel_points")] 11 | public uint ChannelPoints { get; } 12 | 13 | public Tokens(uint bits, uint channelPoints) 14 | { 15 | Bits = bits; 16 | ChannelPoints = channelPoints; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/TopBitsContributor.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public sealed class TopBitsContributor : ContributorBase 6 | { 7 | [JsonPropertyName("bits_contributed")] 8 | public uint BitsContributed { get; } 9 | 10 | public TopBitsContributor(string userId, string displayName, uint bitsContributed) : base(userId, displayName) 11 | { 12 | BitsContributed = bitsContributed; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/TopChannelPointsContributor.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public sealed class TopChannelPointsContributor : ContributorBase 6 | { 7 | [JsonPropertyName("channel_points_contributed")] 8 | public uint ChannelPointsContributed { get; } 9 | 10 | [JsonConstructor] 11 | public TopChannelPointsContributor(string userId, string displayName, uint channelPointsContributed) : base(userId, displayName) 12 | { 13 | ChannelPointsContributed = channelPointsContributed; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Polls/Votes.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Polls 4 | { 5 | public readonly struct Votes 6 | { 7 | [JsonPropertyName("total")] 8 | public uint Total { get; } 9 | 10 | [JsonPropertyName("bits")] 11 | public uint Bits { get; } 12 | 13 | [JsonPropertyName("channel_points")] 14 | public uint ChannelPoints { get; } 15 | 16 | [JsonPropertyName("base")] 17 | public uint Base { get; } 18 | 19 | [JsonConstructor] 20 | public Votes(uint total, uint bits, uint channelPoints, uint @base) 21 | { 22 | Total = total; 23 | Bits = bits; 24 | ChannelPoints = channelPoints; 25 | Base = @base; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Predictions/Badge.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Predictions 4 | { 5 | public readonly struct Badge 6 | { 7 | [JsonPropertyName("set_id")] 8 | public string SetId { get; } 9 | 10 | [JsonPropertyName("version")] 11 | public string Version { get; } 12 | 13 | [JsonConstructor] 14 | public Badge(string setId, string version) 15 | { 16 | SetId = setId; 17 | Version = version; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Predictions/Outcome.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.PubSub.Responses.Predictions 5 | { 6 | public readonly struct Outcome 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; } 10 | 11 | [JsonPropertyName("color")] 12 | public string Color { get; } 13 | 14 | [JsonPropertyName("title")] 15 | public string Title { get; } 16 | 17 | [JsonPropertyName("total_points")] 18 | public uint TotalPoints { get; } 19 | 20 | [JsonPropertyName("total_users")] 21 | public uint TotalUsers { get; } 22 | 23 | [JsonPropertyName("top_predictors")] 24 | public IReadOnlyList TopPredictors { get; } 25 | 26 | [JsonPropertyName("badge")] 27 | public Badge Badge { get; } 28 | 29 | [JsonConstructor] 30 | public Outcome(string id, string color, string title, uint totalPoints, uint totalUsers, IReadOnlyList topPredictors, Badge badge) 31 | { 32 | Id = id; 33 | Color = color; 34 | Title = title; 35 | TotalPoints = totalPoints; 36 | TotalUsers = totalUsers; 37 | TopPredictors = topPredictors; 38 | Badge = badge; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Predictions/PredictionData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | using CatCore.Helpers.Converters; 5 | using CatCore.Models.Twitch.Shared; 6 | 7 | namespace CatCore.Models.Twitch.PubSub.Responses.Predictions 8 | { 9 | public readonly struct PredictionData 10 | { 11 | [JsonPropertyName("id")] 12 | public string Id { get; } 13 | 14 | [JsonPropertyName("channel_id")] 15 | public string ChannelId { get; } 16 | 17 | [JsonPropertyName("title")] 18 | public string Title { get; } 19 | 20 | [JsonPropertyName("created_at")] 21 | public DateTime CreatedAt { get; } 22 | 23 | [JsonPropertyName("created_by")] 24 | public User CreatedBy { get; } 25 | 26 | [JsonPropertyName("ended_at")] 27 | public DateTime? EndedAt { get; } 28 | 29 | [JsonPropertyName("ended_by")] 30 | public User? EndedBy { get; } 31 | 32 | [JsonPropertyName("locked_at")] 33 | public DateTime? LockedAt { get; } 34 | 35 | [JsonPropertyName("locked_by")] 36 | public User? LockedBy { get; } 37 | 38 | [JsonPropertyName("outcomes")] 39 | public IReadOnlyList Outcomes { get; } 40 | 41 | [JsonPropertyName("prediction_window_seconds")] 42 | public uint PredictionWindowSeconds { get; } 43 | 44 | [JsonPropertyName("status")] 45 | [JsonConverter(typeof(JsonStringEnumConverter))] 46 | public PredictionStatus Status { get; } 47 | 48 | [JsonPropertyName("winning_outcome_id")] 49 | public string? WinningOutcomeId { get; } 50 | 51 | [JsonConstructor] 52 | public PredictionData(string id, string channelId, string title, DateTime createdAt, User createdBy, DateTime? endedAt, User? endedBy, DateTime? lockedAt, User? lockedBy, 53 | IReadOnlyList outcomes, uint predictionWindowSeconds, PredictionStatus status, string? winningOutcomeId) 54 | { 55 | Id = id; 56 | ChannelId = channelId; 57 | CreatedAt = createdAt; 58 | CreatedBy = createdBy; 59 | EndedAt = endedAt; 60 | EndedBy = endedBy; 61 | LockedAt = lockedAt; 62 | LockedBy = lockedBy; 63 | Outcomes = outcomes; 64 | PredictionWindowSeconds = predictionWindowSeconds; 65 | Status = status; 66 | Title = title; 67 | WinningOutcomeId = winningOutcomeId; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Predictions/PredictorResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Predictions 4 | { 5 | public readonly struct PredictorResult 6 | { 7 | [JsonPropertyName("type")] 8 | public string Type { get; } 9 | 10 | [JsonPropertyName("points_won")] 11 | public uint PointsWon { get; } 12 | 13 | [JsonPropertyName("is_acknowledged")] 14 | public bool IsAcknowledged { get; } 15 | 16 | [JsonConstructor] 17 | public PredictorResult(string type, uint pointsWon, bool isAcknowledged) 18 | { 19 | Type = type; 20 | PointsWon = pointsWon; 21 | IsAcknowledged = isAcknowledged; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Predictions/TopPredictor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCore.Models.Twitch.PubSub.Responses.Predictions 5 | { 6 | public readonly struct TopPredictor 7 | { 8 | [JsonPropertyName("id")] 9 | public string Id { get; } 10 | 11 | [JsonPropertyName("event_id")] 12 | public string EventId { get; } 13 | 14 | [JsonPropertyName("outcome_id")] 15 | public string OutcomeId { get; } 16 | 17 | [JsonPropertyName("channel_id")] 18 | public string ChannelId { get; } 19 | 20 | [JsonPropertyName("points")] 21 | public uint Points { get; } 22 | 23 | [JsonPropertyName("predicted_at")] 24 | public DateTime PredictedAt { get; } 25 | 26 | [JsonPropertyName("updated_at")] 27 | public DateTime UpdatedAt { get; } 28 | 29 | [JsonPropertyName("user_id")] 30 | public string UserId { get; } 31 | 32 | [JsonPropertyName("result")] 33 | public PredictorResult Result { get; } 34 | 35 | [JsonPropertyName("user_display_name")] 36 | public string DisplayName { get; } 37 | 38 | public TopPredictor(string id, string eventId, string outcomeId, string channelId, uint points, DateTime predictedAt, DateTime updatedAt, string userId, PredictorResult result, string displayName) 39 | { 40 | Id = id; 41 | EventId = eventId; 42 | OutcomeId = outcomeId; 43 | ChannelId = channelId; 44 | Points = points; 45 | PredictedAt = predictedAt; 46 | UpdatedAt = updatedAt; 47 | UserId = userId; 48 | Result = result; 49 | DisplayName = displayName; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/Predictions/User.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.Predictions 4 | { 5 | public readonly struct User 6 | { 7 | [JsonPropertyName("type")] 8 | public string Type { get; } 9 | 10 | [JsonPropertyName("user_id")] 11 | public string UserId { get; } 12 | 13 | [JsonPropertyName("user_display_name")] 14 | public string UserDisplayName { get; } 15 | 16 | [JsonPropertyName("extension_client_id")] 17 | public string? ExtensionClientId { get; } 18 | 19 | [JsonConstructor] 20 | public User(string type, string userId, string userDisplayName, string? extensionClientId) 21 | { 22 | Type = type; 23 | UserId = userId; 24 | UserDisplayName = userDisplayName; 25 | ExtensionClientId = extensionClientId; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/VideoPlayback/Commercial.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.VideoPlayback 4 | { 5 | [PublicAPI] 6 | public sealed class Commercial : VideoPlaybackBase 7 | { 8 | public uint Length { get; } 9 | 10 | public Commercial(string serverTimeRaw, uint length) : base(serverTimeRaw) 11 | { 12 | Length = length; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/VideoPlayback/StreamDown.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.VideoPlayback 4 | { 5 | [PublicAPI] 6 | public sealed class StreamDown : VideoPlaybackBase 7 | { 8 | public StreamDown(string serverTimeRaw) : base(serverTimeRaw) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/VideoPlayback/StreamUp.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.VideoPlayback 4 | { 5 | [PublicAPI] 6 | public sealed class StreamUp : VideoPlaybackBase 7 | { 8 | public int PlayDelay { get; } 9 | 10 | public StreamUp(string serverTimeRaw, int playDelay) : base(serverTimeRaw) 11 | { 12 | PlayDelay = playDelay; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/VideoPlayback/VideoPlaybackBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using JetBrains.Annotations; 4 | 5 | namespace CatCore.Models.Twitch.PubSub.Responses.VideoPlayback 6 | { 7 | [PublicAPI] 8 | public abstract class VideoPlaybackBase 9 | { 10 | private const long TICKS_PER_MICROSECOND = TimeSpan.TicksPerMillisecond / 1000L; 11 | 12 | public string ServerTimeRaw { get; } 13 | public Lazy ServerTime { get; } 14 | 15 | protected VideoPlaybackBase(string serverTimeRaw) 16 | { 17 | ServerTimeRaw = serverTimeRaw; 18 | ServerTime = new(() => InitializeServerTime(), LazyThreadSafetyMode.PublicationOnly); 19 | } 20 | 21 | private DateTimeOffset InitializeServerTime() 22 | { 23 | // TODO: Look into fixed index (10) 24 | // Will only break starting Saturday, November 20, 2286 05:46:40 PM UTC 25 | // Actually might break already on Tuesday, January 19, 2038 03:14:08 AM UTC :| 26 | var dotIndex = ServerTimeRaw.IndexOf('.'); 27 | var serverTimeMicrosRaw = ServerTimeRaw.Remove(dotIndex, 1); 28 | var serverTimeMicros = long.Parse(serverTimeMicrosRaw); 29 | return DateTimeOffset.MinValue.AddTicks(serverTimeMicros * TICKS_PER_MICROSECOND); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/PubSub/Responses/VideoPlayback/ViewCountUpdate.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CatCore.Models.Twitch.PubSub.Responses.VideoPlayback 4 | { 5 | [PublicAPI] 6 | public sealed class ViewCountUpdate : VideoPlaybackBase 7 | { 8 | public uint Viewers { get; } 9 | 10 | public ViewCountUpdate(string serverTimeRaw, uint viewers) : base(serverTimeRaw) 11 | { 12 | Viewers = viewers; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Shared/DefaultImage.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Shared 4 | { 5 | public readonly struct DefaultImage 6 | { 7 | [JsonPropertyName("url_1x")] 8 | public string Url1X { get; } 9 | 10 | [JsonPropertyName("url_2x")] 11 | public string Url2X { get; } 12 | 13 | [JsonPropertyName("url_4x")] 14 | public string Url4X { get; } 15 | 16 | [JsonConstructor] 17 | public DefaultImage(string url1X, string url2X, string url4X) 18 | { 19 | Url1X = url1X; 20 | Url2X = url2X; 21 | Url4X = url4X; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Shared/PollStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Shared 4 | { 5 | public enum PollStatus 6 | { 7 | [EnumMember(Value = "ACTIVE")] 8 | Active, 9 | 10 | [EnumMember(Value = "COMPLETED")] 11 | Completed, 12 | 13 | [EnumMember(Value = "TERMINATED")] 14 | Terminated, 15 | 16 | [EnumMember(Value = "ARCHIVED")] 17 | Archived, 18 | 19 | [EnumMember(Value = "MODERATED")] 20 | Moderated, 21 | 22 | [EnumMember(Value = "INVALID")] 23 | Invalid 24 | } 25 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/Shared/PredictionStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace CatCore.Models.Twitch.Shared 4 | { 5 | public enum PredictionStatus 6 | { 7 | [EnumMember(Value = "ACTIVE")] 8 | Active, 9 | 10 | [EnumMember(Value = "RESOLVED")] 11 | Resolved, 12 | 13 | [EnumMember(Value = "CANCELED")] 14 | Cancelled, 15 | 16 | [EnumMember(Value = "LOCKED")] 17 | Locked 18 | } 19 | } -------------------------------------------------------------------------------- /CatCore/Models/Twitch/TwitchChannel.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Shared; 2 | using CatCore.Models.Twitch.IRC; 3 | using CatCore.Services.Twitch.Interfaces; 4 | using JetBrains.Annotations; 5 | 6 | namespace CatCore.Models.Twitch 7 | { 8 | public sealed class TwitchChannel : IChatChannel 9 | { 10 | private readonly ITwitchIrcService _service; 11 | 12 | /// 13 | [PublicAPI] 14 | public string Id { get; } 15 | 16 | [PublicAPI] 17 | public string Name { get; } 18 | 19 | internal TwitchChannel(ITwitchIrcService service, string id, string name) 20 | { 21 | _service = service; 22 | 23 | Id = id; 24 | Name = name; 25 | } 26 | 27 | public object Clone() 28 | { 29 | return new TwitchChannel(_service, Id, Name); 30 | } 31 | 32 | public void SendMessage(string message) => _service.SendMessage(this, message); 33 | } 34 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IChatService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CatCore.Models.Shared; 3 | 4 | namespace CatCore.Services.Interfaces 5 | { 6 | public interface IChatService 7 | where TChat : IChatService 8 | where TChannel : IChatChannel 9 | where TMessage : IChatMessage 10 | { 11 | /// 12 | /// Callback that occurs when the authentication state changes for the provided streaming service 13 | /// 14 | public event Action? OnAuthenticatedStateChanged; 15 | 16 | /// 17 | /// Callback that occurs when the provided streaming service successfully connected to the chat 18 | /// 19 | public event Action? OnChatConnected; 20 | 21 | /// 22 | /// Callback that occurs when the user joins a chat channel 23 | /// 24 | public event Action? OnJoinChannel; 25 | 26 | /// 27 | /// Callback that occurs when a chat channel receives updated info 28 | /// 29 | public event Action? OnRoomStateUpdated; 30 | 31 | /// 32 | /// Callback that occurs when the user leaves a chat channel 33 | /// 34 | public event Action? OnLeaveChannel; 35 | 36 | /// 37 | /// Callback that occurs when a text message is received 38 | /// 39 | public event Action? OnTextMessageReceived; 40 | 41 | /// 42 | /// Callback that occurs when a chat message gets deleted 43 | /// 44 | public event Action OnMessageDeleted; 45 | 46 | /// 47 | /// Callback that occurs when the chat of a particular channel or all messages of a user in a channel gets cleared 48 | /// 49 | public event Action? OnChatCleared; 50 | } 51 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IKittenApiService.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Services.Interfaces 2 | { 3 | internal interface IKittenApiService : INeedAsyncInitialization 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IKittenBrowserLauncherService.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Services.Interfaces 2 | { 3 | internal interface IKittenBrowserLauncherService 4 | { 5 | void LaunchWebPortal(); 6 | } 7 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IKittenPathProvider.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Services.Interfaces 2 | { 3 | internal interface IKittenPathProvider 4 | { 5 | string DataPath { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IKittenPlatformActiveStateManager.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Shared; 2 | 3 | namespace CatCore.Services.Interfaces 4 | { 5 | internal interface IKittenPlatformActiveStateManager 6 | { 7 | bool GetState(PlatformType platformType); 8 | void UpdateState(PlatformType platformType, bool active); 9 | } 10 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IKittenPlatformServiceManagerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Threading.Tasks; 4 | 5 | namespace CatCore.Services.Interfaces 6 | { 7 | internal interface IKittenPlatformServiceManagerBase : IDisposable 8 | { 9 | bool IsRunning { get; } 10 | Task Start(Assembly callingAssembly); 11 | Task Stop(Assembly? callingAssembly); 12 | } 13 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IKittenSettingsService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CatCore.Models.Config; 3 | 4 | namespace CatCore.Services.Interfaces 5 | { 6 | internal interface IKittenSettingsService : INeedInitialization 7 | { 8 | ConfigRoot Config { get; } 9 | event Action? OnConfigChanged; 10 | 11 | void Load(); 12 | void Store(); 13 | IDisposable ChangeTransaction(); 14 | } 15 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IKittenWebSocketProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using CatCore.Helpers; 3 | 4 | namespace CatCore.Services.Interfaces 5 | { 6 | internal interface IKittenWebSocketProvider 7 | { 8 | bool IsConnected { get; } 9 | Task Connect(string uri); 10 | Task Disconnect(string? reason = null); 11 | 12 | event AsyncEventHandlerDefinitions.AsyncEventHandler? ConnectHappened; 13 | event AsyncEventHandlerDefinitions.AsyncEventHandler? DisconnectHappened; 14 | event AsyncEventHandlerDefinitions.AsyncEventHandler? MessageReceived; 15 | } 16 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/INeedAsyncInitialization.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace CatCore.Services.Interfaces 4 | { 5 | internal interface INeedAsyncInitialization 6 | { 7 | Task Initialize(); 8 | } 9 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/INeedInitialization.cs: -------------------------------------------------------------------------------- 1 | namespace CatCore.Services.Interfaces 2 | { 3 | public interface INeedInitialization 4 | { 5 | void Initialize(); 6 | } 7 | } -------------------------------------------------------------------------------- /CatCore/Services/Interfaces/IPlatformService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using CatCore.Models.Shared; 3 | 4 | namespace CatCore.Services.Interfaces 5 | { 6 | public interface IPlatformService 7 | : IChatService 8 | where TPlatform : IPlatformService 9 | where TChannel : IChatChannel 10 | where TMessage : IChatMessage 11 | { 12 | internal Task Start(); 13 | internal Task Stop(); 14 | 15 | /// 16 | /// Indicates whether the user is authenticated for this service 17 | /// 18 | bool LoggedIn { get; } 19 | 20 | /// 21 | /// Returns the default channel for this service 22 | /// 23 | TChannel? DefaultChannel { get; } 24 | } 25 | } -------------------------------------------------------------------------------- /CatCore/Services/KittenBrowserLauncherService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using System.Threading.Tasks; 4 | using CatCore.Services.Interfaces; 5 | 6 | namespace CatCore.Services 7 | { 8 | internal sealed class KittenBrowserLauncherService : IKittenBrowserLauncherService 9 | { 10 | public void LaunchWebPortal() 11 | { 12 | Task.Run(() => 13 | { 14 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 15 | { 16 | Process.Start(ConstantsBase.InternalApiServerUri); 17 | } 18 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 19 | { 20 | Process.Start("xdg-open", ConstantsBase.InternalApiServerUri); 21 | } 22 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 23 | { 24 | Process.Start("open", ConstantsBase.InternalApiServerUri); 25 | } 26 | }); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /CatCore/Services/KittenPathProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CatCore.Services.Interfaces; 4 | 5 | namespace CatCore.Services 6 | { 7 | internal sealed class KittenPathProvider : IKittenPathProvider 8 | { 9 | private string? _dataPath; 10 | 11 | public string DataPath => _dataPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), $".{nameof(CatCore).ToLower()}"); 12 | } 13 | } -------------------------------------------------------------------------------- /CatCore/Services/KittenPlatformActiveStateManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using CatCore.Models.Shared; 3 | using CatCore.Services.Interfaces; 4 | 5 | namespace CatCore.Services 6 | { 7 | internal sealed class KittenPlatformActiveStateManager : IKittenPlatformActiveStateManager 8 | { 9 | private readonly ConcurrentDictionary _platformActiveStates; 10 | 11 | public KittenPlatformActiveStateManager() 12 | { 13 | _platformActiveStates = new ConcurrentDictionary(); 14 | } 15 | 16 | public bool GetState(PlatformType platformType) 17 | { 18 | return _platformActiveStates.ContainsKey(platformType); 19 | } 20 | 21 | public void UpdateState(PlatformType platformType, bool active) 22 | { 23 | if (active) 24 | { 25 | _platformActiveStates.AddOrUpdate(platformType, active, (_, _) => active); 26 | } 27 | else 28 | { 29 | _platformActiveStates.TryRemove(platformType, out _); 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /CatCore/Services/KittenPlatformServiceManagerBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using CatCore.Helpers; 7 | using CatCore.Models.Shared; 8 | using CatCore.Services.Interfaces; 9 | using Serilog; 10 | 11 | namespace CatCore.Services 12 | { 13 | internal abstract class KittenPlatformServiceManagerBase : IKittenPlatformServiceManagerBase 14 | where T : IPlatformService 15 | where TChannel : IChatChannel 16 | where TMessage : IChatMessage 17 | { 18 | private readonly SemaphoreSlim _locker = new(1, 1); 19 | 20 | private readonly ILogger _logger; 21 | private readonly T _platformService; 22 | private readonly IKittenPlatformActiveStateManager _activeStateManager; 23 | private readonly PlatformType _platformType; 24 | 25 | internal HashSet RegisteredAssemblies { get; } 26 | 27 | internal KittenPlatformServiceManagerBase(ILogger logger, T platformService, IKittenPlatformActiveStateManager activeStateManager, PlatformType platformType) 28 | { 29 | _logger = logger; 30 | _platformService = platformService; 31 | _activeStateManager = activeStateManager; 32 | _platformType = platformType; 33 | 34 | RegisteredAssemblies = new HashSet(); 35 | } 36 | 37 | public bool IsRunning => _activeStateManager.GetState(_platformType); 38 | 39 | public async Task Start(Assembly callingAssembly) 40 | { 41 | using var __ = await Synchronization.LockAsync(_locker); 42 | _ = RegisteredAssemblies.Add(callingAssembly); 43 | 44 | if (IsRunning) 45 | { 46 | return; 47 | } 48 | 49 | _activeStateManager.UpdateState(_platformType, true); 50 | await _platformService.Start(); 51 | 52 | _logger.Information("Started"); 53 | } 54 | 55 | public async Task Stop(Assembly? callingAssembly) 56 | { 57 | using var __ = await Synchronization.LockAsync(_locker); 58 | if (!IsRunning) 59 | { 60 | return; 61 | } 62 | 63 | if (callingAssembly != null) 64 | { 65 | _ = RegisteredAssemblies.Remove(callingAssembly); 66 | if (RegisteredAssemblies.Any()) 67 | { 68 | return; 69 | } 70 | } 71 | else 72 | { 73 | RegisteredAssemblies.Clear(); 74 | } 75 | 76 | _activeStateManager.UpdateState(_platformType, false); 77 | await _platformService.Stop(); 78 | 79 | _logger.Information("Stopped"); 80 | } 81 | 82 | public void Dispose() 83 | { 84 | if (IsRunning) 85 | { 86 | // TODO: figure out how you want to handle this 87 | _ = Stop(null!); 88 | } 89 | 90 | _logger.Information("Disposed"); 91 | } 92 | 93 | public T GetService() 94 | { 95 | return _platformService; 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /CatCore/Services/KittenSettingsService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.Json; 4 | using System.Threading; 5 | using CatCore.Helpers; 6 | using CatCore.Models.Config; 7 | using CatCore.Services.Interfaces; 8 | using Serilog; 9 | 10 | namespace CatCore.Services 11 | { 12 | internal sealed class KittenSettingsService : IKittenSettingsService 13 | { 14 | private const string CONFIG_FILENAME = nameof(CatCore) + "Settings.json"; 15 | 16 | private readonly SemaphoreSlim _locker = new(1, 1); 17 | 18 | private readonly ILogger _logger; 19 | private readonly IKittenPathProvider _pathProvider; 20 | private readonly string _configFilePath; 21 | 22 | private readonly JsonSerializerOptions _jsonSerializerOptions; 23 | 24 | public ConfigRoot Config { get; private set; } = null!; 25 | public event Action? OnConfigChanged; 26 | 27 | public KittenSettingsService(ILogger logger, IKittenPathProvider pathProvider) 28 | { 29 | _logger = logger; 30 | _pathProvider = pathProvider; 31 | _configFilePath = Path.Combine(_pathProvider.DataPath, CONFIG_FILENAME); 32 | 33 | _jsonSerializerOptions = new JsonSerializerOptions {WriteIndented = true}; 34 | } 35 | 36 | public void Initialize() 37 | { 38 | Load(); 39 | Store(); 40 | } 41 | 42 | public void Load() 43 | { 44 | try 45 | { 46 | _locker.Wait(); 47 | 48 | _logger.Information("Loading {Name} settings", nameof(CatCore)); 49 | 50 | if (!Directory.Exists(_pathProvider.DataPath)) 51 | { 52 | Directory.CreateDirectory(_pathProvider.DataPath); 53 | } 54 | 55 | if (!File.Exists(_configFilePath)) 56 | { 57 | Config = new ConfigRoot(); 58 | return; 59 | } 60 | 61 | var readAllText = File.ReadAllText(_configFilePath); 62 | Config = JsonSerializer.Deserialize(readAllText, _jsonSerializerOptions) ?? new ConfigRoot(); 63 | } 64 | catch (Exception e) 65 | { 66 | _logger.Error(e, "An error occurred while trying to load the {Name} settings", nameof(CatCore)); 67 | Config = new ConfigRoot(); 68 | } 69 | finally 70 | { 71 | _locker.Release(); 72 | } 73 | } 74 | 75 | public void Store() 76 | { 77 | try 78 | { 79 | _locker.Wait(); 80 | 81 | _logger.Information("Storing {Name} settings", nameof(CatCore)); 82 | 83 | if (!Directory.Exists(_pathProvider.DataPath)) 84 | { 85 | Directory.CreateDirectory(_pathProvider.DataPath); 86 | } 87 | 88 | File.WriteAllText(_configFilePath, JsonSerializer.Serialize(Config, _jsonSerializerOptions)); 89 | 90 | OnConfigChanged?.Invoke(this, Config); 91 | } 92 | catch (Exception e) 93 | { 94 | _logger.Error(e, "An error occurred while trying to store the {Name} settings", nameof(CatCore)); 95 | } 96 | finally 97 | { 98 | _locker.Release(); 99 | } 100 | } 101 | 102 | public IDisposable ChangeTransaction() 103 | { 104 | return WeakActionToken.Create(this, provider => provider.Store()); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /CatCore/Services/Multiplexer/ChatServiceMultiplexerManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using System.Threading.Tasks; 4 | using CatCore.Services.Interfaces; 5 | using Serilog; 6 | 7 | namespace CatCore.Services.Multiplexer 8 | { 9 | internal sealed class ChatServiceMultiplexerManager : IKittenPlatformServiceManagerBase 10 | { 11 | private readonly ILogger _logger; 12 | private readonly ChatServiceMultiplexer _chatServiceMultiplexer; 13 | private readonly IList _platformServices; 14 | 15 | public ChatServiceMultiplexerManager(ILogger logger, ChatServiceMultiplexer chatServiceMultiplexer, IList platformServices) 16 | { 17 | _logger = logger; 18 | _chatServiceMultiplexer = chatServiceMultiplexer; 19 | _platformServices = platformServices; 20 | } 21 | 22 | public bool IsRunning => false; 23 | 24 | public async Task Start(Assembly callingAssembly) 25 | { 26 | foreach (var service in _platformServices) 27 | { 28 | await service.Start(callingAssembly); 29 | } 30 | 31 | _logger.Information("Streaming services have been started"); 32 | } 33 | 34 | public async Task Stop(Assembly? callingAssembly) 35 | { 36 | foreach (var service in _platformServices) 37 | { 38 | await service.Stop(callingAssembly); 39 | } 40 | 41 | _logger.Information("Streaming services have been stopped"); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | foreach (var service in _platformServices) 47 | { 48 | // TODO: how do you want to handle this? 49 | _ = service.Stop(null); 50 | } 51 | 52 | _logger.Information("Disposed"); 53 | } 54 | 55 | public ChatServiceMultiplexer GetMultiplexer() 56 | { 57 | return _chatServiceMultiplexer; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /CatCore/Services/Multiplexer/MultiplexedChannel.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Shared; 2 | 3 | namespace CatCore.Services.Multiplexer 4 | { 5 | public class MultiplexedChannel : IChatChannel 6 | { 7 | private abstract class Info 8 | { 9 | public abstract string GetId(object o); 10 | public abstract string GetName(object o); 11 | public abstract void SendMessage(object o, string msg); 12 | public abstract object Clone(object o); 13 | } 14 | 15 | private class Info : Info 16 | where TChannel : IChatChannel 17 | where TMsg : IChatMessage 18 | { 19 | public static readonly Info INSTANCE = new(); 20 | 21 | public override string GetId(object o) => ((TChannel) o).Id; 22 | public override string GetName(object o) => ((TChannel) o).Name; 23 | public override void SendMessage(object o, string msg) => ((TChannel) o).SendMessage(msg); 24 | public override object Clone(object o) => ((TChannel) o).Clone(); 25 | } 26 | 27 | private readonly Info _info; 28 | private readonly object _channel; 29 | 30 | public object Underlying => _channel; 31 | 32 | private MultiplexedChannel(Info info, object channel) 33 | => (_info, _channel) = (info, channel); 34 | 35 | public static MultiplexedChannel From(TChannel channel) 36 | where TChannel : IChatChannel 37 | where TMsg : IChatMessage 38 | => new(Info.INSTANCE, channel); 39 | 40 | /// 41 | public string Id => _info.GetId(_channel); 42 | 43 | /// 44 | public string Name => _info.GetName(_channel); 45 | 46 | /// 47 | public void SendMessage(string message) => _info.SendMessage(_channel, message); 48 | 49 | public object Clone() => new MultiplexedChannel(_info, _info.Clone(_channel)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CatCore/Services/Multiplexer/MultiplexedMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using CatCore.Models.Shared; 3 | 4 | namespace CatCore.Services.Multiplexer 5 | { 6 | public class MultiplexedMessage : IChatMessage 7 | { 8 | private abstract class Info 9 | { 10 | public abstract string GetId(object o); 11 | public abstract bool IsSystemMessage(object o); 12 | public abstract bool IsActionMessage(object o); 13 | public abstract bool IsMentioned(object o); 14 | public abstract string GetMessage(object o); 15 | public abstract IChatUser GetSender(object o); 16 | public abstract MultiplexedChannel GetChannel(object o); 17 | public abstract ReadOnlyCollection GetEmotes(object o); 18 | public abstract ReadOnlyDictionary? GetMetadata(object o); 19 | } 20 | 21 | private class Info : Info 22 | where TChannel : IChatChannel 23 | where TMsg : IChatMessage 24 | { 25 | public static readonly Info INSTANCE = new(); 26 | 27 | public override string GetId(object o) => ((TMsg) o).Id; 28 | public override bool IsSystemMessage(object o) => ((TMsg) o).IsSystemMessage; 29 | public override bool IsActionMessage(object o) => ((TMsg) o).IsActionMessage; 30 | public override bool IsMentioned(object o) => ((TMsg) o).IsMentioned; 31 | public override string GetMessage(object o) => ((TMsg) o).Message; 32 | public override IChatUser GetSender(object o) => ((TMsg) o).Sender; 33 | public override MultiplexedChannel GetChannel(object o) => MultiplexedChannel.From(((TMsg) o).Channel); 34 | public override ReadOnlyCollection GetEmotes(object o) => ((TMsg) o).Emotes; 35 | public override ReadOnlyDictionary? GetMetadata(object o) => ((TMsg) o).Metadata; 36 | } 37 | 38 | private readonly Info _info; 39 | private readonly object _message; 40 | 41 | internal object Underlying => _message; 42 | 43 | private MultiplexedMessage(Info info, object message) 44 | => (_info, _message) = (info, message); 45 | 46 | public static MultiplexedMessage From(TMsg message) 47 | where TChannel : IChatChannel 48 | where TMsg : IChatMessage 49 | => new(Info.INSTANCE, message); 50 | 51 | public string Id => _info.GetId(_message); 52 | 53 | public bool IsSystemMessage => _info.IsSystemMessage(_message); 54 | 55 | public bool IsActionMessage => _info.IsActionMessage(_message); 56 | 57 | public bool IsMentioned => _info.IsMentioned(_message); 58 | 59 | public string Message => _info.GetMessage(_message); 60 | 61 | public IChatUser Sender => _info.GetSender(_message); 62 | 63 | public MultiplexedChannel Channel => _info.GetChannel(_message); 64 | 65 | public ReadOnlyCollection Emotes => _info.GetEmotes(_message); 66 | 67 | public ReadOnlyDictionary? Metadata => _info.GetMetadata(_message); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Interfaces/ITwitchAuthService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CatCore.Models.Credentials; 4 | using CatCore.Models.Twitch.OAuth; 5 | 6 | namespace CatCore.Services.Twitch.Interfaces 7 | { 8 | internal interface ITwitchAuthService 9 | { 10 | string? AccessToken { get; } 11 | bool HasTokens { get; } 12 | bool TokenIsValid { get; } 13 | 14 | AuthenticationStatus Status { get; } 15 | event Action? OnCredentialsChanged; 16 | event Action? OnAuthenticationStatusChanged; 17 | 18 | ValidationResponse? FetchLoggedInUserInfo(); 19 | Task FetchLoggedInUserInfoWithRefresh(); 20 | 21 | string AuthorizationUrl(string redirectUrl); 22 | Task GetTokensByAuthorizationCode(string authorizationCode, string redirectUrl); 23 | Task ValidateAccessToken(TwitchCredentials credentials, bool resetDataOnFailure = true); 24 | Task RefreshTokens(); 25 | Task RevokeTokens(); 26 | } 27 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Interfaces/ITwitchChannelManagementService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Threading.Tasks; 5 | using CatCore.Models.EventArgs; 6 | using CatCore.Models.Twitch; 7 | using CatCore.Models.Twitch.Helix.Responses; 8 | 9 | namespace CatCore.Services.Twitch.Interfaces 10 | { 11 | public interface ITwitchChannelManagementService 12 | { 13 | event EventHandler? ChannelsUpdated; 14 | 15 | TwitchChannel? GetOwnChannel(); 16 | 17 | List GetAllActiveChannelIds(bool includeSelfRegardlessOfState = false); 18 | List GetAllActiveLoginNames(bool includeSelfRegardlessOfState = false); 19 | List GetAllActiveChannels(bool includeSelfRegardlessOfState = false); 20 | ReadOnlyDictionary GetAllActiveChannelsAsDictionary(bool includeSelfRegardlessOfState = false); 21 | Task> GetAllChannelsEnriched(); 22 | 23 | TwitchChannel CreateChannel(string channelId, string channelName); 24 | 25 | internal void UpdateChannels(bool ownChannelActive, Dictionary additionalChannelsData); 26 | } 27 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Interfaces/ITwitchIrcService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CatCore.Models.Twitch; 4 | using CatCore.Models.Twitch.IRC; 5 | 6 | namespace CatCore.Services.Twitch.Interfaces 7 | { 8 | internal interface ITwitchIrcService 9 | { 10 | event Action? OnChatConnected; 11 | event Action? OnJoinChannel; 12 | event Action? OnLeaveChannel; 13 | event Action? OnRoomStateChanged; 14 | event Action? OnMessageReceived; 15 | event Action? OnMessageDeleted; 16 | event Action? OnChatCleared; 17 | 18 | void SendMessage(TwitchChannel channel, string message); 19 | 20 | public Task Start(); 21 | public Task Stop(); 22 | } 23 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Interfaces/ITwitchPubSubServiceManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CatCore.Models.Twitch.PubSub.Responses; 4 | using CatCore.Models.Twitch.PubSub.Responses.ChannelPointsChannelV1; 5 | using CatCore.Models.Twitch.PubSub.Responses.Polls; 6 | using CatCore.Models.Twitch.PubSub.Responses.Predictions; 7 | using CatCore.Models.Twitch.PubSub.Responses.VideoPlayback; 8 | 9 | namespace CatCore.Services.Twitch.Interfaces 10 | { 11 | public interface ITwitchPubSubServiceManager 12 | { 13 | internal Task Start(); 14 | internal Task Stop(); 15 | 16 | /// 17 | /// Fired whenever there's an update of the count of live viewers for a specific channel. 18 | /// First argument of the callback is the channelId on which the event was triggered. 19 | /// Second argument of the callback is additional data regarding the view count update. 20 | /// 21 | event Action OnViewCountUpdated; 22 | 23 | /// 24 | /// Fired whenever a channel goes live. 25 | /// First argument of the callback is the channelId on which the event was triggered. 26 | /// Second argument of the callback is additional data regarding the StreamUp event. 27 | /// 28 | event Action OnStreamUp; 29 | 30 | /// 31 | /// Fired whenever a channel stops streaming. 32 | /// First argument of the callback is the channelId on which the event was triggered. 33 | /// Second argument of the callback is additional data regarding the StreamDown event. 34 | /// 35 | event Action OnStreamDown; 36 | 37 | /// 38 | /// Fired whenever a commercial is started on a specific channel. 39 | /// First argument of the callback is the channelId on which the event was triggered. 40 | /// Second argument of the callback is additional data regarding the OnCommercial event. 41 | /// 42 | event Action OnCommercial; 43 | 44 | /// 45 | /// Fired whenever a channel receives a new follower. 46 | /// First argument of the callback is the channelId on which the event was triggered. 47 | /// Second argument of the callback is additional data regarding the new follower. 48 | /// 49 | event Action OnFollow; 50 | 51 | /// 52 | /// Fired whenever a channel starts a poll or when there's an update regarding an ongoing one. 53 | /// First argument of the callback is the channelId on which the event was triggered. 54 | /// Second argument of the callback is additional data regarding the poll. 55 | /// 56 | event Action OnPoll; 57 | 58 | /// 59 | /// Fired whenever a channel starts a prediction or when there's an update regarding an ongoing one. 60 | /// First argument of the callback is the channelId on which the event was triggered. 61 | /// Second argument of the callback is additional data regarding the prediction. 62 | /// 63 | event Action OnPrediction; 64 | 65 | /// 66 | /// Fired whenever a viewer redeems a reward on a specific channel. 67 | /// First argument of the callback is the channelId on which the event was triggered. 68 | /// Second argument of the callback is additional data regarding the redeemed reward and redeemer. 69 | /// 70 | event Action OnRewardRedeemed; 71 | } 72 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Interfaces/ITwitchRoomStateTrackerService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using CatCore.Models.Twitch.IRC; 3 | using JetBrains.Annotations; 4 | 5 | namespace CatCore.Services.Twitch.Interfaces 6 | { 7 | public interface ITwitchRoomStateTrackerService 8 | { 9 | /// 10 | /// Returns the RoomState for the specified channelName. 11 | /// 12 | /// This is only available when the channel is successfully joined over IRC 13 | /// RoomState for the specified channel 14 | [PublicAPI] 15 | TwitchRoomState? GetRoomState(string channelName); 16 | 17 | internal TwitchRoomState? UpdateRoomState(string channelName, ReadOnlyDictionary? roomStateUpdate); 18 | } 19 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Interfaces/ITwitchService.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Twitch; 2 | using CatCore.Models.Twitch.IRC; 3 | using CatCore.Services.Interfaces; 4 | using JetBrains.Annotations; 5 | 6 | namespace CatCore.Services.Twitch.Interfaces 7 | { 8 | public interface ITwitchService : IPlatformService 9 | { 10 | /// 11 | /// Returns the PubSub service manager. Allows you to subscribe to various events. 12 | /// 13 | /// Returns the PubSub service manager 14 | [PublicAPI] 15 | ITwitchPubSubServiceManager GetPubSubService(); 16 | 17 | /// 18 | /// Returns the Helix API service. Allows you to interact with the Twitch Helix API. 19 | /// 20 | /// 21 | [PublicAPI] 22 | ITwitchHelixApiService GetHelixApiService(); 23 | 24 | /// 25 | /// Returns the RoomState tracker service. Keeps track of the state of the currently subscribed channels. 26 | /// 27 | /// Returns the RoomState tracker service 28 | [PublicAPI] 29 | ITwitchRoomStateTrackerService GetRoomStateTrackerService(); 30 | 31 | /// 32 | /// Returns the UserState tracker service. Keeps track of the state of the user, both global and channel-specific. 33 | /// 34 | /// Returns the UserState tracker service 35 | [PublicAPI] 36 | ITwitchUserStateTrackerService GetUserStateTrackerService(); 37 | 38 | /// 39 | /// Returns the Channel management service. Keeps track of all channels that were registered through the webportal. 40 | /// 41 | /// Returns the Channel management service 42 | [PublicAPI] 43 | ITwitchChannelManagementService GetChannelManagementService(); 44 | } 45 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Interfaces/ITwitchUserStateTrackerService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using CatCore.Models.Twitch.IRC; 3 | using JetBrains.Annotations; 4 | 5 | namespace CatCore.Services.Twitch.Interfaces 6 | { 7 | public interface ITwitchUserStateTrackerService 8 | { 9 | [PublicAPI] 10 | TwitchGlobalUserState? GlobalUserState { get; } 11 | 12 | [PublicAPI] 13 | TwitchUserState? GetUserState(string channelId); 14 | 15 | internal void UpdateGlobalUserState(ReadOnlyDictionary? globalUserStateUpdate); 16 | internal void UpdateUserState(string channelId, ReadOnlyDictionary? userStateUpdate); 17 | } 18 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/Media/TwitchBadgeDataProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Threading.Tasks; 4 | using CatCore.Models.Twitch.Helix.Responses.Badges; 5 | using CatCore.Models.Twitch.Media; 6 | using CatCore.Services.Twitch.Interfaces; 7 | 8 | namespace CatCore.Services.Twitch.Media 9 | { 10 | public class TwitchBadgeDataProvider 11 | { 12 | private readonly ITwitchHelixApiService _twitchHelixApiService; 13 | 14 | private IReadOnlyDictionary _globalBadges; 15 | private readonly Dictionary> _channelBadges; 16 | 17 | public TwitchBadgeDataProvider(ITwitchHelixApiService twitchHelixApiService) 18 | { 19 | _twitchHelixApiService = twitchHelixApiService; 20 | 21 | _globalBadges = new ReadOnlyDictionary(new Dictionary()); 22 | _channelBadges = new Dictionary>(); 23 | } 24 | 25 | internal async Task TryRequestGlobalResources() 26 | { 27 | var globalBadges = await _twitchHelixApiService.GetGlobalBadges().ConfigureAwait(false); 28 | if (globalBadges == null) 29 | { 30 | return; 31 | } 32 | 33 | _globalBadges = ParseBadgeData("TwitchGlobalBadge_", globalBadges.Value.Data); 34 | } 35 | 36 | internal async Task TryRequestChannelResources(string userId) 37 | { 38 | var channelBadges = await _twitchHelixApiService.GetBadgesForChannel(userId).ConfigureAwait(false); 39 | if (channelBadges == null) 40 | { 41 | return; 42 | } 43 | 44 | _channelBadges[userId] = ParseBadgeData("TwitchChannelBadge_" + userId, channelBadges.Value.Data); 45 | } 46 | 47 | internal void ReleaseAllResources() 48 | { 49 | _globalBadges = new Dictionary(); 50 | _channelBadges.Clear(); 51 | } 52 | 53 | internal void ReleaseChannelResources(string userId) 54 | { 55 | _channelBadges.Remove(userId); 56 | } 57 | 58 | private static ReadOnlyDictionary ParseBadgeData(string identifierPrefix, List badgeData) 59 | { 60 | var parsedTwitchBadges = new Dictionary(); 61 | 62 | foreach (var badge in badgeData) 63 | { 64 | foreach (var badgeVersion in badge.Versions) 65 | { 66 | var simpleIdentifier = badge.SetId + "/" + badgeVersion.Id; 67 | parsedTwitchBadges.Add(simpleIdentifier, new TwitchBadge(identifierPrefix + simpleIdentifier, badge.SetId, badgeVersion.ImageUrl4X)); 68 | } 69 | } 70 | 71 | return new ReadOnlyDictionary(parsedTwitchBadges); 72 | } 73 | 74 | public bool TryGetBadge(string identifier, string userId, out TwitchBadge? badge) 75 | { 76 | if (_channelBadges.TryGetValue(userId, out var channelBadges) && channelBadges.TryGetValue(identifier, out badge)) 77 | { 78 | return true; 79 | } 80 | 81 | return _globalBadges.TryGetValue(identifier, out badge); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/TwitchHelixClientHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Net.Http.Headers; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CatCore.Services.Twitch.Interfaces; 6 | 7 | namespace CatCore.Services.Twitch 8 | { 9 | internal sealed class TwitchHelixClientHandler : HttpClientHandler 10 | { 11 | private readonly ITwitchAuthService _twitchAuthService; 12 | 13 | public TwitchHelixClientHandler(ITwitchAuthService twitchAuthService) 14 | { 15 | _twitchAuthService = twitchAuthService; 16 | 17 | #if !RELEASE 18 | Proxy = Helpers.SharedProxyProvider.PROXY; 19 | #endif 20 | } 21 | 22 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 23 | { 24 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _twitchAuthService.AccessToken); 25 | 26 | return base.SendAsync(request, cancellationToken); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /CatCore/Services/Twitch/TwitchServiceManager.cs: -------------------------------------------------------------------------------- 1 | using CatCore.Models.Shared; 2 | using CatCore.Models.Twitch; 3 | using CatCore.Models.Twitch.IRC; 4 | using CatCore.Services.Interfaces; 5 | using CatCore.Services.Twitch.Interfaces; 6 | using Serilog; 7 | 8 | namespace CatCore.Services.Twitch 9 | { 10 | internal sealed class TwitchServiceManager : KittenPlatformServiceManagerBase 11 | { 12 | public TwitchServiceManager(ILogger logger, ITwitchService twitchService, IKittenPlatformActiveStateManager activeStateManager) 13 | : base(logger, twitchService, activeStateManager, PlatformType.Twitch) 14 | { 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CatCoreBenchmarkSandbox/Benchmarks/Miscellaneous/RandomColorGenerationBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using BenchmarkDotNet.Attributes; 5 | 6 | namespace CatCoreBenchmarkSandbox.Benchmarks.Miscellaneous 7 | { 8 | [MemoryDiagnoser] 9 | [CategoriesColumn, AllStatisticsColumn, BaselineColumn, MinColumn, Q1Column, MeanColumn, Q3Column, MaxColumn, MedianColumn] 10 | public class RandomColorGenerationBenchmark 11 | { 12 | private const string VALID_CHARS = "0123456789ABCDEF"; 13 | private readonly char[] _validCharsArray = VALID_CHARS.ToCharArray(); 14 | 15 | [Params(6)] 16 | public int Length; 17 | 18 | public Random Random = null!; 19 | 20 | [GlobalSetup] 21 | public void GlobalSetup() 22 | { 23 | Random = new Random(8); 24 | } 25 | 26 | [Benchmark(Baseline = true)] 27 | public string ChatCoreBaselineBenchmark() 28 | { 29 | var argb = (Random.Next(255) << 16) + (Random.Next(255) << 8) + Random.Next(255); 30 | return $"#{argb:X6}FF"; 31 | } 32 | 33 | [Benchmark] 34 | public string StringLinqBenchmark() 35 | { 36 | return new string(Enumerable.Repeat(VALID_CHARS, Length).Select(s => s[Random.Next(s.Length)]).ToArray()); 37 | } 38 | 39 | [Benchmark] 40 | public string StringLoopBenchmark() 41 | { 42 | var stringChars = new char[Length]; 43 | for (var i = 0; i < stringChars.Length; i++) 44 | { 45 | stringChars[i] = VALID_CHARS[Random.Next(VALID_CHARS.Length)]; 46 | } 47 | 48 | return new string(stringChars); 49 | } 50 | 51 | [Benchmark] 52 | public string StringCharArrayLoopBenchmark() 53 | { 54 | var stringChars = new char[Length]; 55 | for (var i = 0; i < stringChars.Length; i++) 56 | { 57 | stringChars[i] = _validCharsArray[Random.Next(_validCharsArray.Length)]; 58 | } 59 | 60 | return new string(stringChars); 61 | } 62 | 63 | [Benchmark] 64 | public string StringBuilderSpanBenchmark() 65 | { 66 | var charsAsSpan = VALID_CHARS.AsSpan(); 67 | var sb = new StringBuilder(Length); 68 | for (var i = 0; i < Length; i++) 69 | { 70 | sb.Append(charsAsSpan[Random.Next(charsAsSpan.Length)]); 71 | } 72 | 73 | return sb.ToString(); 74 | } 75 | 76 | [Benchmark] 77 | public string SpanBufferBenchmark() 78 | { 79 | var charsAsSpan = VALID_CHARS.AsSpan(); 80 | 81 | var result = new char[Length]; 82 | var resultSpan = result.AsSpan(); 83 | for (var i = 0; i < result.Length; i++) 84 | { 85 | charsAsSpan.Slice(Random.Next(charsAsSpan.Length), 1).CopyTo(resultSpan.Slice(i)); 86 | } 87 | 88 | return new string(result); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /CatCoreBenchmarkSandbox/Benchmarks/Miscellaneous/StringEqualityBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace CatCoreBenchmarkSandbox.Benchmarks.Miscellaneous 4 | { 5 | internal static class IrcCommands 6 | { 7 | public const string RPL_ENDOFMOTD = "376"; 8 | public const string PING = nameof(PING); 9 | public const string PONG = nameof(PONG); 10 | public const string JOIN = nameof(JOIN); 11 | public const string PART = nameof(PART); 12 | public const string NOTICE = nameof(NOTICE); 13 | public const string PRIVMSG = nameof(PRIVMSG); 14 | } 15 | 16 | internal static class TwitchIrcCommands 17 | { 18 | public const string CLEARCHAT = nameof(CLEARCHAT); 19 | public const string CLEARMSG = nameof(CLEARMSG); 20 | public const string GLOBALUSERSTATE = nameof(GLOBALUSERSTATE); 21 | public const string ROOMSTATE = nameof(ROOMSTATE); 22 | public const string USERNOTICE = nameof(USERNOTICE); 23 | public const string USERSTATE = nameof(USERSTATE); 24 | public const string RECONNECT = nameof(RECONNECT); 25 | public const string HOSTTARGET = nameof(HOSTTARGET); 26 | } 27 | 28 | [MemoryDiagnoser] 29 | [CategoriesColumn, AllStatisticsColumn, BaselineColumn, MinColumn, Q1Column, MeanColumn, Q3Column, MaxColumn, MedianColumn] 30 | public class StringEqualityBenchmark 31 | { 32 | [Params(IrcCommands.PING, IrcCommands.NOTICE, IrcCommands.PRIVMSG, TwitchIrcCommands.GLOBALUSERSTATE, TwitchIrcCommands.USERNOTICE)] 33 | public string CommandType = null!; 34 | 35 | [Benchmark(Baseline = true)] 36 | public bool StringEqualityWithoutPattern() { 37 | // ReSharper disable once MergeIntoLogicalPattern 38 | return CommandType == IrcCommands.NOTICE || CommandType == TwitchIrcCommands.USERNOTICE; 39 | } 40 | 41 | [Benchmark] 42 | public bool StringEqualityWithPattern() 43 | { 44 | return CommandType is IrcCommands.NOTICE or TwitchIrcCommands.USERNOTICE; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /CatCoreBenchmarkSandbox/Benchmarks/Miscellaneous/StringStartsWithBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace CatCoreBenchmarkSandbox.Benchmarks.Miscellaneous 5 | { 6 | [MemoryDiagnoser] 7 | [CategoriesColumn, AllStatisticsColumn, BaselineColumn, MinColumn, Q1Column, MeanColumn, Q3Column, MaxColumn, MedianColumn] 8 | public class StringStartsWithBenchmark 9 | { 10 | [Params( 11 | "ACTION Heya", 12 | "ACTION i definitely dont miss the mass of random charging ports that existed", 13 | "ACTION The phrase “it’s just a game” is such a weak mindset. You are ok with what happened, losing, imperfection of a craft. When you stop getting angry after losing, you’ve lost twice. There’s always something to learn, and always room for improvement, never settle.", 14 | "ACTION Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop p")] 15 | public string Message = null!; 16 | 17 | [Benchmark(Baseline = true)] 18 | public bool StringStartsWith() 19 | { 20 | const string ACTION = "ACTION "; 21 | return Message.StartsWith(ACTION, StringComparison.Ordinal); 22 | } 23 | 24 | [Benchmark] 25 | public bool SpanStartsWith() 26 | { 27 | const string ACTION = "ACTION "; 28 | return Message.AsSpan().StartsWith(ACTION.AsSpan(), StringComparison.Ordinal); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /CatCoreBenchmarkSandbox/Benchmarks/TwitchPubSub/TwitchPubSubNonceGenerationBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using BenchmarkDotNet.Attributes; 5 | 6 | namespace CatCoreBenchmarkSandbox.Benchmarks.TwitchPubSub 7 | { 8 | [MemoryDiagnoser] 9 | [CategoriesColumn, AllStatisticsColumn, BaselineColumn, MinColumn, Q1Column, MeanColumn, Q3Column, MaxColumn, MedianColumn] 10 | public class TwitchPubSubNonceGenerationBenchmark 11 | { 12 | private const string VALID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 13 | private const int VALID_CHARS_LENGTH = 36; 14 | 15 | private readonly char[] _validCharsArray = VALID_CHARS.ToCharArray(); 16 | 17 | [Params(8, 12, 16, 24, 32)] 18 | public int Length; 19 | 20 | public Random Random = null!; 21 | 22 | [GlobalSetup] 23 | public void GlobalSetup() 24 | { 25 | Random = new Random(8); 26 | } 27 | 28 | [Benchmark(Baseline = true)] 29 | public string GuidBenchmark() 30 | { 31 | return Guid.NewGuid().ToString("N").Substring(0, Length); 32 | } 33 | 34 | [Benchmark] 35 | public string StringLinqBenchmark() 36 | { 37 | return new string(Enumerable.Repeat(VALID_CHARS, Length).Select(s => s[Random.Next(VALID_CHARS_LENGTH)]).ToArray()); 38 | } 39 | 40 | [Benchmark] 41 | public string StringLoopBenchmark() 42 | { 43 | var stringChars = new char[Length]; 44 | for (var i = 0; i < stringChars.Length; i++) 45 | { 46 | stringChars[i] = VALID_CHARS[Random.Next(VALID_CHARS_LENGTH)]; 47 | } 48 | 49 | return new string(stringChars); 50 | } 51 | 52 | [Benchmark] 53 | public string StringCharArrayLoopBenchmark() 54 | { 55 | var stringChars = new char[Length]; 56 | for (var i = 0; i < stringChars.Length; i++) 57 | { 58 | stringChars[i] = _validCharsArray[Random.Next(VALID_CHARS_LENGTH)]; 59 | } 60 | 61 | return new string(stringChars); 62 | } 63 | 64 | [Benchmark] 65 | public string StringBuilderSpanBenchmark() 66 | { 67 | var charsAsSpan = VALID_CHARS.AsSpan(); 68 | var sb = new StringBuilder(Length); 69 | for (var i = 0; i < Length; i++) 70 | { 71 | sb.Append(charsAsSpan[Random.Next(VALID_CHARS_LENGTH)]); 72 | } 73 | 74 | return sb.ToString(); 75 | } 76 | 77 | [Benchmark] 78 | public string SpanBufferBenchmark() 79 | { 80 | var charsAsSpan = VALID_CHARS.AsSpan(); 81 | 82 | var result = new char[Length]; 83 | var resultSpan = result.AsSpan(); 84 | for (var i = 0; i < result.Length; i++) 85 | { 86 | charsAsSpan.Slice(Random.Next(VALID_CHARS_LENGTH), 1).CopyTo(resultSpan.Slice(i)); 87 | } 88 | 89 | return new string(result); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /CatCoreBenchmarkSandbox/CatCoreBenchmarkSandbox.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net472;net5.0 6 | 9 7 | Release 8 | AnyCPU 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Resources\Unicode13_1EmojiTest.txt 21 | Always 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CatCoreBenchmarkSandbox/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using BenchmarkDotNet.Columns; 4 | using BenchmarkDotNet.Configs; 5 | using BenchmarkDotNet.Diagnosers; 6 | using BenchmarkDotNet.Environments; 7 | using BenchmarkDotNet.Exporters; 8 | using BenchmarkDotNet.Jobs; 9 | using BenchmarkDotNet.Loggers; 10 | using BenchmarkDotNet.Order; 11 | using BenchmarkDotNet.Running; 12 | 13 | namespace CatCoreBenchmarkSandbox 14 | { 15 | internal class Program 16 | { 17 | private static readonly List MonoRuntimes = new() 18 | { 19 | new MonoRuntime("Mono Unity 2019.4.28f1", @"D:\Program Files\Unity\2019.4.28f1\Editor\Data\MonoBleedingEdge\bin\mono.exe"), 20 | new MonoRuntime("Mono Unity 2021.2.0b4", @"D:\Program Files\Unity\2021.2.0b4\Editor\Data\MonoBleedingEdge\bin\mono.exe"), 21 | new MonoRuntime("Mono Unity 2022.1.0a12", @"D:\Program Files\Unity\2022.1.0a12\Editor\Data\MonoBleedingEdge\bin\mono.exe") 22 | }; 23 | 24 | public static void Main() 25 | { 26 | var benchmarkConfiguration = ManualConfig.CreateEmpty() 27 | .AddJob(Job.Default 28 | .WithRuntime(ClrRuntime.Net472)) 29 | .AddJob(Job.Default 30 | .WithRuntime(CoreRuntime.Core50)) 31 | .AddJob(Job.Default 32 | .WithRuntime(CoreRuntime.Core60)) 33 | .AddJob(MonoRuntimes 34 | .Select(runtimeEntry => Job.Default.WithRuntime(runtimeEntry)) 35 | .ToArray()) 36 | .WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest)) 37 | .AddDiagnoser(MemoryDiagnoser.Default) 38 | .AddColumnProvider(DefaultColumnProviders.Instance) 39 | .AddLogger(ConsoleLogger.Default) 40 | .AddExporter(BenchmarkReportExporter.Default, HtmlExporter.Default, MarkdownExporter.Console); 41 | 42 | // BenchmarkRunner.Run(benchmarkConfiguration); 43 | BenchmarkRunner.Run(typeof(Program).Assembly, benchmarkConfiguration); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /CatCoreStandaloneSandbox/CatCoreStandaloneSandbox.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | 9 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /CatCoreStandaloneSandbox/Models/Messages/MessageBase.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CatCoreStandaloneSandbox.Models.Messages 4 | { 5 | internal abstract class MessageBase 6 | { 7 | [JsonPropertyName("type")] public string Type { get; init; } 8 | } 9 | } -------------------------------------------------------------------------------- /CatCoreStandaloneSandbox/Models/Messages/PingMessage.cs: -------------------------------------------------------------------------------- 1 | namespace CatCoreStandaloneSandbox.Models.Messages 2 | { 3 | internal class PingMessage : MessageBase 4 | { 5 | public PingMessage() 6 | { 7 | Type = "PING"; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /CatCoreStandaloneSandbox/Models/Messages/PongMessage.cs: -------------------------------------------------------------------------------- 1 | namespace CatCoreStandaloneSandbox.Models.Messages 2 | { 3 | internal class PongMessage : MessageBase 4 | { 5 | public PongMessage() 6 | { 7 | Type = "PONG"; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /CatCoreStandaloneSandbox/Models/Messages/ReconnectMessage.cs: -------------------------------------------------------------------------------- 1 | namespace CatCoreStandaloneSandbox.Models.Messages 2 | { 3 | internal class ReconnectMessage : MessageBase 4 | { 5 | public ReconnectMessage() 6 | { 7 | Type = "RECONNECT"; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /CatCoreStandaloneSandbox/Models/Messages/TopicMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace CatCoreStandaloneSandbox.Models.Messages 5 | { 6 | internal class TopicMessage : MessageBase 7 | { 8 | public TopicMessage(string type) 9 | { 10 | Type = type; 11 | } 12 | 13 | [JsonPropertyName("nonce")] public string? Nonce { get; init; } 14 | [JsonPropertyName("data")] public TopicMessageData Data { get; init; } 15 | 16 | internal class TopicMessageData 17 | { 18 | [JsonPropertyName("topics")] public List Topics { get; init; } 19 | [JsonPropertyName("auth_token")] public string Token { get; init; } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /CatCoreTester/CatCoreTester.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net472 6 | 9 7 | Debug;Release;Develop 8 | AnyCPU 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /CatCoreTesterMod/CatCoreTesterMod.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Library 6 | 8 7 | enable 8 | true 9 | ..\Refs 10 | $(LocalRefsDir) 11 | $(MSBuildProjectDirectory)\ 12 | CatCoreTesterMod 13 | CatCoreTesterMod 14 | Debug;Develop;Release 15 | AnyCPU 16 | 17 | 18 | 19 | full 20 | 21 | 22 | 23 | pdbonly 24 | 25 | 26 | 27 | True 28 | 29 | 30 | 31 | True 32 | True 33 | 34 | 35 | 36 | 37 | $(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll 38 | False 39 | 40 | 41 | $(BeatSaberDir)\Libs\CatCore.dll 42 | False 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | all 59 | build; native; contentfiles; analyzers; buildtransitive 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /CatCoreTesterMod/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | true 9 | true 10 | true 11 | 12 | 13 | false 14 | true 15 | true 16 | 17 | -------------------------------------------------------------------------------- /CatCoreTesterMod/Plugin.cs: -------------------------------------------------------------------------------- 1 | using CatCore; 2 | using CatCore.Logging; 3 | using IPA; 4 | using IPA.Logging; 5 | 6 | namespace CatCoreTesterMod 7 | { 8 | [Plugin(RuntimeOptions.DynamicInit), NoEnableDisable] 9 | internal class Plugin 10 | { 11 | private readonly Logger _catCoreLogger; 12 | 13 | private readonly CatCoreInstance _catCoreInstance; 14 | 15 | [Init] 16 | public Plugin(Logger logger) 17 | { 18 | _catCoreLogger = logger.GetChildLogger(nameof(CatCore)); 19 | _catCoreInstance = CatCoreInstance.Create(CatCoreOnLogReceived); 20 | } 21 | 22 | [OnEnable] 23 | public void OnEnable() 24 | { 25 | _catCoreInstance.OnLogReceived -= CatCoreOnLogReceived; 26 | _catCoreInstance.OnLogReceived += CatCoreOnLogReceived; 27 | } 28 | 29 | [OnDisable] 30 | public void OnDisable() 31 | { 32 | _catCoreInstance.OnLogReceived -= CatCoreOnLogReceived; 33 | } 34 | 35 | private void CatCoreOnLogReceived(CustomLogLevel level, string context, string message) 36 | { 37 | _catCoreLogger.Log(level switch 38 | { 39 | CustomLogLevel.Trace => Logger.Level.Trace, 40 | CustomLogLevel.Debug => Logger.Level.Debug, 41 | CustomLogLevel.Information => Logger.Level.Info, 42 | CustomLogLevel.Warning => Logger.Level.Warning, 43 | CustomLogLevel.Error => Logger.Level.Error, 44 | CustomLogLevel.Critical => Logger.Level.Critical, 45 | _ => Logger.Level.Debug 46 | }, $"{context} | {message}"); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /CatCoreTesterMod/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json", 3 | "id": "CatCoreTesterMod", 4 | "name": "CatCore Tester Mod", 5 | "author": "Eris", 6 | "version": "1.0.0", 7 | "description": "A simple mod that has the sole purpose of testing CatCore in Beat Saber", 8 | "gameVersion": "1.22.0", 9 | "dependsOn": { 10 | "BSIPA": "^4.2.1", 11 | "CatCore": "^1.0.0" 12 | } 13 | } -------------------------------------------------------------------------------- /CatCoreTests/CatCoreTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 9 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | 18 | 19 | 20 | build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Resources\Unicode14_0EmojiTest.txt 33 | Always 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------