mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-02 04:22:04 -04:00
Initial commit
This commit is contained in:
267
KickWsClient/KickWsClient.cs
Normal file
267
KickWsClient/KickWsClient.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using KickWsClient.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Websocket.Client;
|
||||
using NLog;
|
||||
|
||||
|
||||
namespace KickWsClient;
|
||||
|
||||
public class KickWsClient
|
||||
{
|
||||
public event EventHandlers.OnPusherConnectionEstablishedEventHandler OnPusherConnectionEstablished;
|
||||
public event EventHandlers.OnPusherSubscriptionSucceededEventHandler OnPusherSubscriptionSucceeded;
|
||||
public event EventHandlers.OnPusherPongEventHandler OnPusherPong;
|
||||
public event EventHandlers.OnFollowersUpdatedEventHandler OnFollowersUpdated;
|
||||
public event EventHandlers.OnChatMessageEventHandler OnChatMessage;
|
||||
public event EventHandlers.OnChannelSubscriptionEventHandler OnChannelSubscription;
|
||||
public event EventHandlers.OnSubscriptionEventHandler OnSubscription;
|
||||
public event EventHandlers.OnMessageDeletedEventHandler OnMessageDeleted;
|
||||
public event EventHandlers.OnUserBannedEventHandler OnUserBanned;
|
||||
public event EventHandlers.OnUserUnbannedEventHandler OnUserUnbanned;
|
||||
public event EventHandlers.OnUpdatedLiveStreamEventHandler OnUpdatedLiveStream;
|
||||
public event EventHandlers.OnStopStreamBroadcastEventHandler OnStopStreamBroadcast;
|
||||
public event EventHandlers.OnStreamerIsLiveEventHandler OnStreamerIsLive;
|
||||
public event EventHandlers.OnWsDisconnectionEventHandler OnWsDisconnection;
|
||||
public event EventHandlers.OnWsReconnectEventHandler OnWsReconnect;
|
||||
// You really shouldn't use this unless you're extending the functionality of the library, e.g. adding support for
|
||||
// not yet implemented message types.
|
||||
public event EventHandlers.OnWsMessageReceivedEventHandler OnWsMessageReceived;
|
||||
public event EventHandlers.OnPollUpdateEventHandler OnPollUpdate;
|
||||
|
||||
private WebsocketClient _wsClient;
|
||||
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private Uri _kickPusherUri;
|
||||
private int _reconnectTimeout;
|
||||
private string? _proxy;
|
||||
|
||||
public KickWsClient(
|
||||
string kickPusherUri =
|
||||
"wss://ws-us2.pusher.com/app/eb1d5f283081a78b932c?protocol=7&client=js&version=7.6.0&flash=false",
|
||||
string? proxy = null, int reconnectTimeout = 30)
|
||||
{
|
||||
_kickPusherUri = new Uri(kickPusherUri);
|
||||
_proxy = proxy;
|
||||
_reconnectTimeout = reconnectTimeout;
|
||||
}
|
||||
|
||||
public async Task StartWsClient()
|
||||
{
|
||||
_logger.Debug("StartWsClient() called, creating client");
|
||||
_wsClient = await CreateWsClient();
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
_logger.Debug("Disconnect() called, closing Websocket");
|
||||
_wsClient.Stop(WebSocketCloseStatus.NormalClosure, "Closing websocket").Wait();
|
||||
}
|
||||
|
||||
private async Task<WebsocketClient> CreateWsClient()
|
||||
{
|
||||
var factory = new Func<ClientWebSocket>(() =>
|
||||
{
|
||||
var clientWs = new ClientWebSocket();
|
||||
if (_proxy == null) return clientWs;
|
||||
clientWs.Options.Proxy = new WebProxy(_proxy);
|
||||
return clientWs;
|
||||
});
|
||||
|
||||
var client = new WebsocketClient(_kickPusherUri, factory)
|
||||
{
|
||||
ReconnectTimeout = TimeSpan.FromSeconds(_reconnectTimeout)
|
||||
};
|
||||
|
||||
client.ReconnectionHappened.Subscribe(WsReconnection);
|
||||
client.MessageReceived.Subscribe(WsMessageReceived);
|
||||
client.DisconnectionHappened.Subscribe(WsDisconnection);
|
||||
|
||||
_logger.Debug("Websocket client has been built, about to start");
|
||||
await client.Start();
|
||||
_logger.Debug("Websocket client started!");
|
||||
return client;
|
||||
}
|
||||
|
||||
public bool IsConnected()
|
||||
{
|
||||
return _wsClient is { IsRunning: true };
|
||||
}
|
||||
|
||||
private void WsDisconnection(DisconnectionInfo disconnectionInfo)
|
||||
{
|
||||
_logger.Error($"Client disconnected from the chat (or never successfully connected). Type is {disconnectionInfo.Type}");
|
||||
_logger.Error(disconnectionInfo.Exception);
|
||||
OnWsDisconnection?.Invoke(this, disconnectionInfo);
|
||||
}
|
||||
|
||||
private void WsReconnection(ReconnectionInfo reconnectionInfo)
|
||||
{
|
||||
_logger.Error($"Websocket connection dropped and reconnected. Reconnection type is {reconnectionInfo.Type}");
|
||||
if (reconnectionInfo.Type == ReconnectionType.Initial)
|
||||
{
|
||||
_logger.Error("Not firing the reconnection event as this is the initial event");
|
||||
return;
|
||||
}
|
||||
OnWsReconnect?.Invoke(this, reconnectionInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a generic Pusher packet
|
||||
/// </summary>
|
||||
/// <param name="eventName">Event name</param>
|
||||
/// <param name="data">Event data</param>
|
||||
public void SendPusherPacket(string eventName, object data)
|
||||
{
|
||||
var pkt = new PusherModels.BasePusherRequestModel { Event = eventName, Data = data};
|
||||
var json = JsonConvert.SerializeObject(pkt);
|
||||
_logger.Debug("Sending message to Pusher");
|
||||
_logger.Debug(json);
|
||||
_wsClient.Send(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a ping packet. You should expect a pong response immediately after
|
||||
/// </summary>
|
||||
public void SendPusherPing()
|
||||
{
|
||||
SendPusherPacket("pusher:ping", new object());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a pusher subscribe packet to subscribe to a channel or chatroom. You should receive a subscription succeeded packet
|
||||
/// </summary>
|
||||
/// <param name="channel">Channel string e.g. channel.2515504</param>
|
||||
/// <param name="auth">Optional authentication string. Empty string means guest</param>
|
||||
public void SendPusherSubscribe(string channel, string auth = "")
|
||||
{
|
||||
var subPacket = new PusherModels.PusherSubscribeRequestModel { Auth = auth, Channel = channel };
|
||||
SendPusherPacket("pusher:subscribe", subPacket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send pusher unsubscribe packet to unsub from a channel or chatroom. Expect no response
|
||||
/// </summary>
|
||||
/// <param name="channel">Channel string e.g. channel.2515504</param>
|
||||
public void SendPusherUnsubscribe(string channel)
|
||||
{
|
||||
var unsubPacket = new PusherModels.PusherUnsubscribeRequestModel { Channel = channel };
|
||||
SendPusherPacket("pusher:unsubscribe", unsubPacket);
|
||||
}
|
||||
|
||||
private void WsMessageReceived(ResponseMessage message)
|
||||
{
|
||||
OnWsMessageReceived?.Invoke(this, message);
|
||||
|
||||
if (message.Text == null)
|
||||
{
|
||||
_logger.Info("Websocket message was null, ignoring packet");
|
||||
return;
|
||||
}
|
||||
|
||||
PusherModels.BasePusherEventModel pusherMsg;
|
||||
try
|
||||
{
|
||||
pusherMsg = JsonConvert.DeserializeObject<PusherModels.BasePusherEventModel>(message.Text) ??
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Failed to parse Pusher message. Exception follows:");
|
||||
_logger.Error(e);
|
||||
_logger.Error("--- Message from Pusher follows ---");
|
||||
_logger.Error(message.Text);
|
||||
_logger.Error("--- /end of message ---");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Pusher event receievd: {pusherMsg.Event}");
|
||||
|
||||
switch (pusherMsg.Event)
|
||||
{
|
||||
case "pusher:connection_established":
|
||||
{
|
||||
var data =
|
||||
JsonConvert.DeserializeObject<PusherModels.PusherConnectionEstablishedEventModel>(pusherMsg.Data);
|
||||
OnPusherConnectionEstablished?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case "pusher_internal:subscription_succeeded":
|
||||
OnPusherSubscriptionSucceeded?.Invoke(this, pusherMsg);
|
||||
return;
|
||||
case "pusher:pong":
|
||||
OnPusherPong?.Invoke(this, pusherMsg);
|
||||
return;
|
||||
case @"App\Events\FollowersUpdated":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.FollowersUpdatedEventModel>(pusherMsg.Data);
|
||||
OnFollowersUpdated?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\ChatMessageEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.ChatMessageEventModel>(pusherMsg.Data);
|
||||
OnChatMessage?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\ChannelSubscriptionEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.ChannelSubscriptionEventModel>(pusherMsg.Data);
|
||||
OnChannelSubscription?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\SubscriptionEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.SubscriptionEventModel>(pusherMsg.Data);
|
||||
OnSubscription?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\MessageDeletedEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.MessageDeletedEventModel>(pusherMsg.Data);
|
||||
OnMessageDeleted?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\UserBannedEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.UserBannedEventModel>(pusherMsg.Data);
|
||||
OnUserBanned?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\UserUnbannedEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.UserUnbannedEventModel>(pusherMsg.Data);
|
||||
OnUserUnbanned?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\LiveStream\UpdatedLiveStreamEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.UpdatedLiveStreamEventModel>(pusherMsg.Data);
|
||||
OnUpdatedLiveStream?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\StopStreamBroadcast":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.StopStreamBroadcastEventModel>(pusherMsg.Data);
|
||||
OnStopStreamBroadcast?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\StreamerIsLive":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.StreamerIsLiveEventModel>(pusherMsg.Data);
|
||||
OnStreamerIsLive?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
case @"App\Events\PollUpdateEvent":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<KickModels.PollUpdateEventModel>(pusherMsg.Data);
|
||||
OnPollUpdate?.Invoke(this, data);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
_logger.Info("Event unhandled. JOSN payload follows");
|
||||
_logger.Info(message.Text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
KickWsClient/KickWsClient.csproj
Normal file
15
KickWsClient/KickWsClient.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="Websocket.Client" Version="5.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
41
KickWsClient/Models/EventHandlers.cs
Normal file
41
KickWsClient/Models/EventHandlers.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Websocket.Client;
|
||||
|
||||
namespace KickWsClient.Models;
|
||||
|
||||
public class EventHandlers
|
||||
{
|
||||
public delegate void OnPusherConnectionEstablishedEventHandler(object sender,
|
||||
PusherModels.PusherConnectionEstablishedEventModel? e);
|
||||
|
||||
public delegate void OnPusherSubscriptionSucceededEventHandler(object sender, PusherModels.BasePusherEventModel e);
|
||||
|
||||
public delegate void OnPusherPongEventHandler(object sender, PusherModels.BasePusherEventModel e);
|
||||
|
||||
public delegate void OnFollowersUpdatedEventHandler(object sender, KickModels.FollowersUpdatedEventModel? e);
|
||||
|
||||
public delegate void OnChatMessageEventHandler(object sender, KickModels.ChatMessageEventModel? e);
|
||||
|
||||
public delegate void OnChannelSubscriptionEventHandler(object sender, KickModels.ChannelSubscriptionEventModel? e);
|
||||
|
||||
public delegate void OnSubscriptionEventHandler(object sender, KickModels.SubscriptionEventModel? e);
|
||||
|
||||
public delegate void OnMessageDeletedEventHandler(object sender, KickModels.MessageDeletedEventModel? e);
|
||||
|
||||
public delegate void OnUserBannedEventHandler(object sender, KickModels.UserBannedEventModel? e);
|
||||
|
||||
public delegate void OnUserUnbannedEventHandler(object sender, KickModels.UserUnbannedEventModel? e);
|
||||
|
||||
public delegate void OnUpdatedLiveStreamEventHandler(object sender, KickModels.UpdatedLiveStreamEventModel? e);
|
||||
|
||||
public delegate void OnStopStreamBroadcastEventHandler(object sender, KickModels.StopStreamBroadcastEventModel? e);
|
||||
|
||||
public delegate void OnStreamerIsLiveEventHandler(object sender, KickModels.StreamerIsLiveEventModel? e);
|
||||
|
||||
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
|
||||
|
||||
public delegate void OnWsReconnectEventHandler(object sender, ReconnectionInfo e);
|
||||
|
||||
public delegate void OnWsMessageReceivedEventHandler(object sender, ResponseMessage e);
|
||||
|
||||
public delegate void OnPollUpdateEventHandler(object sender, KickModels.PollUpdateEventModel? e);
|
||||
}
|
||||
519
KickWsClient/Models/KickModels.cs
Normal file
519
KickWsClient/Models/KickModels.cs
Normal file
@@ -0,0 +1,519 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace KickWsClient.Models;
|
||||
|
||||
public class KickModels
|
||||
{
|
||||
public class ChatMessageSenderIdentityBadgeModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal type for badge e.g. moderator
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public required string Type { get; set; }
|
||||
/// <summary>
|
||||
/// Friendly name for badge e.g. Moderator
|
||||
/// </summary>
|
||||
[JsonProperty("text")]
|
||||
public required string Text { get; set; }
|
||||
/// <summary>
|
||||
/// Count (if applicable) for badge (e.g. sub count for gifted subs)
|
||||
/// </summary>
|
||||
[JsonProperty("count")]
|
||||
public int? Count { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMessageSenderIdentityModel
|
||||
{
|
||||
/// <summary>
|
||||
/// User's hex color
|
||||
/// </summary>
|
||||
[JsonProperty("color")]
|
||||
public required string Color { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Badges a user has
|
||||
/// </summary>
|
||||
[JsonProperty("badges")]
|
||||
public List<ChatMessageSenderIdentityBadgeModel> Badges = [];
|
||||
}
|
||||
|
||||
public class ChatMessageSenderModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Kick internal user ID
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Kick display name
|
||||
/// </summary>
|
||||
[JsonProperty("username")]
|
||||
public required string Username { get; set; }
|
||||
/// <summary>
|
||||
/// Kick slug (for URLs)
|
||||
/// </summary>
|
||||
[JsonProperty("slug")]
|
||||
public required string Slug { get; set; }
|
||||
/// <summary>
|
||||
/// Identity info for display color and badges
|
||||
/// </summary>
|
||||
[JsonProperty("identity")]
|
||||
public required ChatMessageSenderIdentityModel Identity { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMessageMetadataOriginalSenderModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Original sender's user ID
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Original sender's username
|
||||
/// </summary>
|
||||
[JsonProperty("username")]
|
||||
public required string Username { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMessageMetadataOriginalMessageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID (GUID) of the original message
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public required string Id { get; set; }
|
||||
/// <summary>
|
||||
/// Content of the original message
|
||||
/// </summary>
|
||||
[JsonProperty("content")]
|
||||
public required string Content { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMessageMetadataModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Sender of the message that this message is in reply to
|
||||
/// </summary>
|
||||
[JsonProperty("original_sender")]
|
||||
public required ChatMessageMetadataOriginalSenderModel OriginalSender { get; set; }
|
||||
/// <summary>
|
||||
/// Content of the message that this message is in reply to
|
||||
/// </summary>
|
||||
[JsonProperty("original_message")]
|
||||
public required ChatMessageMetadataOriginalMessageModel OriginalMessage { get; set; }
|
||||
}
|
||||
|
||||
public class FollowersUpdatedEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Channel follower count
|
||||
/// </summary>
|
||||
[JsonProperty("followersCount")]
|
||||
public int FollowersCount { get; set; }
|
||||
/// <summary>
|
||||
/// ID to identify what chatroom this event belongs to
|
||||
/// </summary>
|
||||
[JsonProperty("chatroom_id")]
|
||||
public int ChatroomId { get; set; }
|
||||
/// <summary>
|
||||
/// Maybe returns your username if you're auth'd? No idea. Just returned null for me
|
||||
/// </summary>
|
||||
[JsonProperty("username")]
|
||||
public string? Username { get; set; }
|
||||
/// <summary>
|
||||
/// Epoch value that signifies ???
|
||||
/// </summary>
|
||||
[JsonProperty("created_at")]
|
||||
public int? CreatedAtEpoch { get; set; }
|
||||
// It returned true even though I'm not signed in which makes no sense, so I'll assume there's a chance it'll
|
||||
// suddenly appear and mark as nullable as it's not really a useful property anyway.
|
||||
/// <summary>
|
||||
/// Does it mean we're following? Who knows, returns true even if you're a guest
|
||||
/// </summary>
|
||||
[JsonProperty("followed")]
|
||||
public bool? Followed { get; set; }
|
||||
}
|
||||
|
||||
public class ChatMessageEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Message unique GUID that's referenced for replies and deletions
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public required string Id { get; set; }
|
||||
/// <summary>
|
||||
/// Chatroom ID you can use to differentiate this from other rooms if you sub to multiple at a time
|
||||
/// </summary>
|
||||
[JsonProperty("chatroom_id")]
|
||||
public int ChatroomId { get; set; }
|
||||
/// <summary>
|
||||
/// Content of the message. Emotes are encoded like [emote:161238:russW] which translates to -> https://files.kick.com/emotes/161238/fullsize
|
||||
/// </summary>
|
||||
[JsonProperty("content")]
|
||||
public required string Content { get; set; }
|
||||
/// <summary>
|
||||
/// Regular message is 'message', replies are 'reply'
|
||||
/// </summary>
|
||||
[JsonProperty("type")]
|
||||
public required string Type { get; set; }
|
||||
// Why created at is an epoch for followers updated but ISO8601 for chat messages is just a mystery
|
||||
/// <summary>
|
||||
/// Time message was sent
|
||||
/// </summary>
|
||||
[JsonProperty("created_at")]
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Sender of the message
|
||||
/// </summary>
|
||||
[JsonProperty("sender")]
|
||||
public required ChatMessageSenderModel Sender { get; set; }
|
||||
/// <summary>
|
||||
/// Message metadata which is set for replies only
|
||||
/// </summary>
|
||||
[JsonProperty("metadata")]
|
||||
public ChatMessageMetadataModel? Metadata { get; set; }
|
||||
}
|
||||
|
||||
public class ChannelSubscriptionEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// User IDs of subscription recipients
|
||||
/// </summary>
|
||||
[JsonProperty("user_ids")]
|
||||
public List<int> UserIds { get; set; } = [];
|
||||
/// <summary>
|
||||
/// Username of the person who subbed / gifted
|
||||
/// </summary>
|
||||
[JsonProperty("username")]
|
||||
public required string Username { get; set; }
|
||||
/// <summary>
|
||||
/// Channel ID where the sub event occurred
|
||||
/// </summary>
|
||||
[JsonProperty("channel_id")]
|
||||
public int ChannelId { get; set; }
|
||||
}
|
||||
|
||||
public class SubscriptionEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of channel where the subscription event occurred
|
||||
/// </summary>
|
||||
[JsonProperty("chatroom_id")]
|
||||
public int ChatroomId { get; set; }
|
||||
/// <summary>
|
||||
/// Username of the person who bought a sub
|
||||
/// </summary>
|
||||
[JsonProperty("username")]
|
||||
public required string Username { get; set; }
|
||||
/// <summary>
|
||||
/// Number of months they've subbed now (e.g. 2 if they bought their 2nd month)
|
||||
/// </summary>
|
||||
[JsonProperty("months")]
|
||||
public int Months { get; set; }
|
||||
}
|
||||
|
||||
public class MessageDeletedMessageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the message that was deleted
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public required string Id { get; set; }
|
||||
}
|
||||
|
||||
public class MessageDeletedEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of this event (NOT the message to be removed!)
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public required string Id { get; set; }
|
||||
/// <summary>
|
||||
/// Message that was deleted
|
||||
/// </summary>
|
||||
[JsonProperty("message")]
|
||||
public required MessageDeletedMessageModel Message { get; set; }
|
||||
}
|
||||
|
||||
public class UserBannedUserModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the user. Note it'll be 0 for the janny
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// User's username
|
||||
/// </summary>
|
||||
[JsonProperty("username")]
|
||||
public required string Username { get; set; }
|
||||
/// <summary>
|
||||
/// Slug suitable for URLs
|
||||
/// </summary>
|
||||
[JsonProperty("slug")]
|
||||
public required string Slug { get; set; }
|
||||
}
|
||||
|
||||
public class UserBannedEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// GUID of the event
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public required string Id { get; set; }
|
||||
/// <summary>
|
||||
/// User who was banished
|
||||
/// </summary>
|
||||
[JsonProperty("user")]
|
||||
public required UserBannedUserModel User { get; set; }
|
||||
/// <summary>
|
||||
/// Janny who did the sweeping
|
||||
/// </summary>
|
||||
[JsonProperty("banned_by")]
|
||||
public required UserBannedUserModel BannedBy { get; set; }
|
||||
/// <summary>
|
||||
/// Datetime that the ban expires. Null for permabans
|
||||
/// </summary>
|
||||
[JsonProperty("expires_at")]
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
}
|
||||
|
||||
public class UserUnbannedEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// GUID of the event
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public required string Id { get; set; }
|
||||
/// <summary>
|
||||
/// User who was unbanned
|
||||
/// </summary>
|
||||
[JsonProperty("user")]
|
||||
public required UserBannedUserModel User { get; set; }
|
||||
/// <summary>
|
||||
/// Janny who unbanned
|
||||
/// </summary>
|
||||
[JsonProperty("unbanned_by")]
|
||||
public required UserBannedUserModel UnbannedBy { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatedLiveStreamCategoryParentModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID representing the category
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Slug representing the category
|
||||
/// </summary>
|
||||
[JsonProperty("slug")]
|
||||
public required string Slug { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatedLiveStreamCategoryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the category
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Friendly name of the category
|
||||
/// </summary>
|
||||
[JsonProperty("name")]
|
||||
public required string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Category's slug for forming URls etc.
|
||||
/// </summary>
|
||||
[JsonProperty("slug")]
|
||||
public required string Slug { get; set; }
|
||||
/// <summary>
|
||||
/// Tags for the category
|
||||
/// </summary>
|
||||
[JsonProperty("tags")]
|
||||
public List<string> Tags { get; set; } = [];
|
||||
/// <summary>
|
||||
/// Parent category, if one is present. I think there usually is one, but made it nullable just in case
|
||||
/// </summary>
|
||||
[JsonProperty("parent_category")]
|
||||
public UpdatedLiveStreamCategoryParentModel? ParentCategory { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatedLiveStreamEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the livestream (numeric)
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Livestream slug
|
||||
/// </summary>
|
||||
[JsonProperty("slug")]
|
||||
public required string Slug { get; set; }
|
||||
/// <summary>
|
||||
/// Livestream title
|
||||
/// </summary>
|
||||
[JsonProperty("session_title")]
|
||||
public required string SessionTitle { get; set; }
|
||||
/// <summary>
|
||||
/// Livestream start time
|
||||
/// </summary>
|
||||
[JsonProperty("created_at")]
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Language of the livestream (e.g. English)
|
||||
/// </summary>
|
||||
[JsonProperty("language")]
|
||||
public string? Language { get; set; }
|
||||
/// <summary>
|
||||
/// Whether the stream is marked as for a mature audience
|
||||
/// </summary>
|
||||
[JsonProperty("is_mature")]
|
||||
public bool IsMature { get; set; }
|
||||
/// <summary>
|
||||
/// Number of viewers presently watching
|
||||
/// </summary>
|
||||
[JsonProperty("viewers")]
|
||||
public int Viewers { get; set; }
|
||||
/// <summary>
|
||||
/// Category of the livestream. I believe this is always required but marked it as nullable just in case
|
||||
/// </summary>
|
||||
[JsonProperty("category")]
|
||||
public UpdatedLiveStreamCategoryModel? Category { get; set; }
|
||||
}
|
||||
|
||||
public class StopStreamBroadcastLiveStreamChannelModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the channel
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Whether the streamer was sent to ban world
|
||||
/// </summary>
|
||||
[JsonProperty("is_banned")]
|
||||
public bool IsBanned { get; set; }
|
||||
}
|
||||
|
||||
public class StopStreamBroadcastLiveStreamModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Livestream event ID
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Channel that stopped streaming
|
||||
/// </summary>
|
||||
[JsonProperty("channel")]
|
||||
public required StopStreamBroadcastLiveStreamChannelModel Channel { get; set; }
|
||||
}
|
||||
|
||||
public class StopStreamBroadcastEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Object containing information related to the livestream that stopped
|
||||
/// </summary>
|
||||
[JsonProperty("livestream")]
|
||||
public required StopStreamBroadcastLiveStreamModel Livestream { get; set; }
|
||||
}
|
||||
|
||||
public class StreamerIsLiveLiveStreamModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the livestream
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// ID of the channel
|
||||
/// </summary>
|
||||
[JsonProperty("channel_id")]
|
||||
public int ChannelId { get; set; }
|
||||
/// <summary>
|
||||
/// Title of the stream
|
||||
/// </summary>
|
||||
[JsonProperty("session_title")]
|
||||
public required string SessionTitle { get; set; }
|
||||
/// <summary>
|
||||
/// No idea, just null on my end
|
||||
/// </summary>
|
||||
[JsonProperty("source")]
|
||||
public string? Source { get; set; }
|
||||
/// <summary>
|
||||
/// Time stream started
|
||||
/// </summary>
|
||||
[JsonProperty("created_at")]
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class StreamerIsLiveEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Object containing information related to the livestream that has just started
|
||||
/// </summary>
|
||||
[JsonProperty("livestream")]
|
||||
public required StreamerIsLiveLiveStreamModel Livestream { get; set; }
|
||||
}
|
||||
|
||||
public class PollUpdatePollOptionModel
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the poll option
|
||||
/// </summary>
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Label of the poll option
|
||||
/// </summary>
|
||||
[JsonProperty("label")]
|
||||
public required string Label { get; set; }
|
||||
/// <summary>
|
||||
/// Number of votes the poll option has gotten
|
||||
/// </summary>
|
||||
[JsonProperty("votes")]
|
||||
public int Votes { get; set; }
|
||||
}
|
||||
|
||||
public class PollUpdatePollModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Title of the poll
|
||||
/// </summary>
|
||||
[JsonProperty("title")]
|
||||
public required string Title { get; set; }
|
||||
/// <summary>
|
||||
/// Poll options
|
||||
/// </summary>
|
||||
[JsonProperty("options")]
|
||||
public List<PollUpdatePollOptionModel> Options { get; set; } = [];
|
||||
/// <summary>
|
||||
/// Duration of the poll in seconds
|
||||
/// </summary>
|
||||
[JsonProperty("duration")]
|
||||
public int Duration { get; set; }
|
||||
/// <summary>
|
||||
/// Remaining time in seconds
|
||||
/// </summary>
|
||||
[JsonProperty("remaining")]
|
||||
public int Remaining { get; set; }
|
||||
/// <summary>
|
||||
/// Time in seconds to display the results after completion?
|
||||
/// </summary>
|
||||
[JsonProperty("result_display_duration")]
|
||||
public int ResultDisplayDuration { get; set; }
|
||||
}
|
||||
|
||||
public class PollUpdateEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Poll data
|
||||
/// </summary>
|
||||
[JsonProperty("poll")]
|
||||
public required PollUpdatePollModel Poll { get; set; }
|
||||
}
|
||||
}
|
||||
76
KickWsClient/Models/PusherModels.cs
Normal file
76
KickWsClient/Models/PusherModels.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace KickWsClient.Models;
|
||||
|
||||
public class PusherModels
|
||||
{
|
||||
public class BasePusherEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the event
|
||||
/// </summary>
|
||||
[JsonProperty("event")]
|
||||
public required string Event { get; set; }
|
||||
/// <summary>
|
||||
/// Stringified JSON payload
|
||||
/// </summary>
|
||||
[JsonProperty("data")]
|
||||
public required string Data { get; set; }
|
||||
/// <summary>
|
||||
/// Channel where event originates. Only included events where a channel is applicable
|
||||
/// </summary>
|
||||
[JsonProperty("channel")]
|
||||
public string? Channel { get; set; }
|
||||
}
|
||||
|
||||
public class BasePusherRequestModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the event
|
||||
/// </summary>
|
||||
[JsonProperty("event")]
|
||||
public required string Event { get; set; }
|
||||
/// <summary>
|
||||
/// Data as object. It's only stringified for responses
|
||||
/// </summary>
|
||||
[JsonProperty("data")]
|
||||
public required object Data { get; set; }
|
||||
}
|
||||
|
||||
public class PusherConnectionEstablishedEventModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal socket ID
|
||||
/// </summary>
|
||||
[JsonProperty("socket_id")]
|
||||
public required string SocketId { get; set; }
|
||||
/// <summary>
|
||||
/// Timeout on no activity in seconds
|
||||
/// </summary>
|
||||
[JsonProperty("activity_timeout")]
|
||||
public int ActivityTimeout { get; set; }
|
||||
}
|
||||
|
||||
public class PusherSubscribeRequestModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Token to authenticate with, use an empty string for guest.
|
||||
/// </summary>
|
||||
[JsonProperty("auth")]
|
||||
public string Auth { get; set; } = "";
|
||||
/// <summary>
|
||||
/// Channel you wish to subscribe to. 'channel.2515504' for stream events. 'chatrooms.2515504.v2' for chat where 2515504 is the channel ID
|
||||
/// </summary>
|
||||
[JsonProperty("channel")]
|
||||
public required string Channel { get; set; }
|
||||
}
|
||||
|
||||
public class PusherUnsubscribeRequestModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Channel you wish to unsubscribe from, e.g. 'channel.2515504'
|
||||
/// </summary>
|
||||
[JsonProperty("channel")]
|
||||
public required string Channel { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user