├── .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