2023-07-04 18:51:27 -04:00
|
|
|
using System.Security.Cryptography.X509Certificates;
|
2023-07-04 12:58:21 -04:00
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
using RestSharp;
|
|
|
|
using TwitchLib.Api;
|
|
|
|
using TwitchLib.Api.Helix.Models.Users.GetUsers;
|
|
|
|
using TwitchLib.Client;
|
|
|
|
using TwitchLib.Client.Events;
|
|
|
|
using TwitchLib.Client.Models;
|
|
|
|
using TwitchLib.Communication.Clients;
|
|
|
|
using TwitchLib.Communication.Models;
|
|
|
|
using vassago.Behavior;
|
|
|
|
using vassago.Models;
|
|
|
|
|
|
|
|
namespace vassago.TwitchInterface;
|
|
|
|
|
|
|
|
public class TwitchInterface
|
|
|
|
{
|
2023-12-01 14:02:47 -05:00
|
|
|
internal const string PROTOCOL = "twitch";
|
2023-07-04 12:58:21 -04:00
|
|
|
private bool eventsSignedUp = false;
|
|
|
|
private ChattingContext _db;
|
|
|
|
private static SemaphoreSlim twitchChannelSetup = new SemaphoreSlim(1, 1);
|
|
|
|
private Channel protocolAsChannel;
|
|
|
|
TwitchClient client;
|
2023-12-01 14:02:47 -05:00
|
|
|
TwitchAPI api;
|
2023-07-04 12:58:21 -04:00
|
|
|
|
|
|
|
public TwitchInterface()
|
|
|
|
{
|
|
|
|
_db = new ChattingContext();
|
|
|
|
}
|
|
|
|
private async Task SetupTwitchChannel()
|
|
|
|
{
|
|
|
|
await twitchChannelSetup.WaitAsync();
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
protocolAsChannel = _db.Channels.FirstOrDefault(c => c.ParentChannel == null && c.Protocol == PROTOCOL);
|
|
|
|
if (protocolAsChannel == null)
|
|
|
|
{
|
|
|
|
protocolAsChannel = new Channel()
|
|
|
|
{
|
2023-07-04 18:51:27 -04:00
|
|
|
DisplayName = "twitch (itself)",
|
2024-04-05 23:59:39 -04:00
|
|
|
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Medium,
|
|
|
|
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.G,
|
|
|
|
MaxTextChars = 500,
|
|
|
|
MaxAttachmentBytes = 0,
|
|
|
|
LinksAllowed = false,
|
|
|
|
ReactionsPossible = false,
|
2023-07-04 12:58:21 -04:00
|
|
|
ExternalId = null,
|
|
|
|
Protocol = PROTOCOL,
|
|
|
|
SubChannels = new List<Channel>()
|
|
|
|
};
|
|
|
|
protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"twitch itself cannot accept text"); };
|
|
|
|
protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"twitch itself cannot send file"); };
|
|
|
|
_db.Channels.Add(protocolAsChannel);
|
|
|
|
_db.SaveChanges();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
twitchChannelSetup.Release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///<param name="oauth">https://www.twitchapps.com/tmi/</param>
|
|
|
|
public async Task Init(TwitchConfig tc)
|
|
|
|
{
|
|
|
|
await SetupTwitchChannel();
|
|
|
|
|
|
|
|
WebSocketClient customClient = new WebSocketClient(new ClientOptions
|
2023-08-22 14:58:44 -04:00
|
|
|
{
|
|
|
|
MessagesAllowedInPeriod = 750,
|
|
|
|
ThrottlingPeriod = TimeSpan.FromSeconds(30)
|
|
|
|
}
|
2023-07-04 12:58:21 -04:00
|
|
|
);
|
|
|
|
client = new TwitchClient(customClient);
|
|
|
|
client.Initialize(new ConnectionCredentials(tc.username, tc.oauth, capabilities: new Capabilities()));
|
|
|
|
|
|
|
|
client.OnLog += Client_OnLog;
|
|
|
|
client.OnJoinedChannel += Client_OnJoinedChannel;
|
|
|
|
client.OnMessageReceived += Client_OnMessageReceivedAsync;
|
|
|
|
client.OnWhisperReceived += Client_OnWhisperReceivedAsync;
|
|
|
|
client.OnConnected += Client_OnConnected;
|
|
|
|
|
2023-12-01 14:02:47 -05:00
|
|
|
Console.WriteLine("twitch client 1 connecting...");
|
2023-07-04 12:58:21 -04:00
|
|
|
client.Connect();
|
2023-12-01 14:02:47 -05:00
|
|
|
Console.WriteLine("twitch client 1 connected");
|
|
|
|
|
|
|
|
// Console.WriteLine("twitch API client connecting...");
|
|
|
|
// api = new TwitchAPI();
|
|
|
|
// Console.WriteLine("can I just use the same creds as the other client?");
|
|
|
|
// api.Settings.ClientId = tc.username;
|
|
|
|
// api.Settings.AccessToken = tc.oauth;
|
|
|
|
// try{
|
|
|
|
// var neckbreads = await api.Helix.Moderation.GetModeratorsAsync("silvermeddlists");
|
|
|
|
// Console.WriteLine($"{neckbreads?.Data?.Count()} shabby beards that need to be given up on");
|
|
|
|
// }
|
|
|
|
// catch(Exception e){
|
|
|
|
// Console.Error.WriteLine(e);
|
|
|
|
// }
|
|
|
|
// Console.WriteLine("k.");
|
2023-07-04 12:58:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private async void Client_OnWhisperReceivedAsync(object sender, OnWhisperReceivedArgs e)
|
|
|
|
{
|
|
|
|
Console.WriteLine($"whisper#{e.WhisperMessage.Username}[{DateTime.Now}][{e.WhisperMessage.DisplayName} [id={e.WhisperMessage.Username}]][msg id: {e.WhisperMessage.MessageId}] {e.WhisperMessage.Message}");
|
2023-08-22 15:33:09 -04:00
|
|
|
var old = _db.Messages.FirstOrDefault(m => m.ExternalId == e.WhisperMessage.MessageId && m.Protocol == PROTOCOL);
|
|
|
|
if (old != null)
|
2023-08-22 14:58:44 -04:00
|
|
|
{
|
2023-08-22 15:33:09 -04:00
|
|
|
Console.WriteLine($"[whisperreceived]: {e.WhisperMessage.MessageId}? already seent it. Internal id: {old.Id}");
|
2023-08-22 14:58:44 -04:00
|
|
|
return;
|
|
|
|
}
|
2023-07-04 12:58:21 -04:00
|
|
|
var m = UpsertMessage(e.WhisperMessage);
|
2023-12-03 14:33:58 -05:00
|
|
|
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
|
2023-07-04 12:58:21 -04:00
|
|
|
m.MentionsMe = Regex.IsMatch(e.WhisperMessage.Message?.ToLower(), $"\\b@{e.WhisperMessage.BotUsername.ToLower()}\\b");
|
2024-01-10 21:21:31 -05:00
|
|
|
await _db.SaveChangesAsync();
|
2023-07-04 12:58:21 -04:00
|
|
|
|
2023-08-22 14:58:44 -04:00
|
|
|
await Behaver.Instance.ActOn(m);
|
2024-01-10 21:21:31 -05:00
|
|
|
await _db.SaveChangesAsync();
|
2023-07-04 12:58:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private async void Client_OnMessageReceivedAsync(object sender, OnMessageReceivedArgs e)
|
|
|
|
{
|
|
|
|
Console.WriteLine($"#{e.ChatMessage.Channel}[{DateTime.Now}][{e.ChatMessage.DisplayName} [id={e.ChatMessage.Username}]][msg id: {e.ChatMessage.Id}] {e.ChatMessage.Message}");
|
2023-08-22 15:33:09 -04:00
|
|
|
var old = _db.Messages.FirstOrDefault(m => m.ExternalId == e.ChatMessage.Id && m.Protocol == PROTOCOL);
|
|
|
|
if (old != null)
|
2023-08-22 14:58:44 -04:00
|
|
|
{
|
2023-08-22 15:33:09 -04:00
|
|
|
Console.WriteLine($"[messagereceived]: {e.ChatMessage.Id}? already seent it");
|
2023-08-22 14:58:44 -04:00
|
|
|
return;
|
|
|
|
}
|
2023-08-22 15:33:09 -04:00
|
|
|
Console.WriteLine($"[messagereceived]: {e.ChatMessage.Id}? new to me.");
|
2023-07-04 12:58:21 -04:00
|
|
|
var m = UpsertMessage(e.ChatMessage);
|
|
|
|
m.MentionsMe = Regex.IsMatch(e.ChatMessage.Message?.ToLower(), $"@{e.ChatMessage.BotUsername.ToLower()}\\b") ||
|
|
|
|
e.ChatMessage.ChatReply?.ParentUserLogin == e.ChatMessage.BotUsername;
|
2024-01-10 21:21:31 -05:00
|
|
|
await _db.SaveChangesAsync();
|
2023-07-04 12:58:21 -04:00
|
|
|
|
2023-08-22 14:58:44 -04:00
|
|
|
await Behaver.Instance.ActOn(m);
|
2024-01-10 21:21:31 -05:00
|
|
|
await _db.SaveChangesAsync();
|
2023-07-04 12:58:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private async void Client_OnConnected(object sender, OnConnectedArgs e)
|
|
|
|
{
|
2024-01-05 21:39:31 -05:00
|
|
|
var selfAccount = UpsertAccount(e.BotUsername, protocolAsChannel.Id);
|
2023-07-04 12:58:21 -04:00
|
|
|
|
|
|
|
await _db.SaveChangesAsync();
|
2024-01-05 21:39:31 -05:00
|
|
|
Behaver.Instance.MarkSelf(selfAccount);
|
2023-07-04 12:58:21 -04:00
|
|
|
|
|
|
|
Console.WriteLine($"Connected to {e.AutoJoinChannel}");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Client_OnJoinedChannel(object sender, OnJoinedChannelArgs e)
|
|
|
|
{
|
|
|
|
client.SendMessage(e.Channel, "beep boop");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Client_OnLog(object sender, OnLogArgs e)
|
|
|
|
{
|
|
|
|
Console.WriteLine($"{e.DateTime.ToString()}: {e.BotUsername} - {e.Data}");
|
|
|
|
}
|
|
|
|
|
2023-07-04 20:40:37 -04:00
|
|
|
private Account UpsertAccount(string username, Guid inChannel)
|
2023-07-04 12:58:21 -04:00
|
|
|
{
|
2023-07-04 20:40:37 -04:00
|
|
|
var acc = _db.Accounts.FirstOrDefault(ui => ui.ExternalId == username && ui.SeenInChannel.Id == inChannel);
|
2023-07-04 12:58:21 -04:00
|
|
|
if (acc == null)
|
|
|
|
{
|
|
|
|
acc = new Account();
|
|
|
|
_db.Accounts.Add(acc);
|
|
|
|
}
|
|
|
|
acc.Username = username;
|
|
|
|
acc.ExternalId = username;
|
|
|
|
//acc.IsBot =
|
|
|
|
acc.Protocol = PROTOCOL;
|
|
|
|
|
2023-12-01 14:02:47 -05:00
|
|
|
acc.IsUser = _db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol));
|
|
|
|
if (acc.IsUser == null)
|
2023-07-04 12:58:21 -04:00
|
|
|
{
|
2023-12-01 14:02:47 -05:00
|
|
|
acc.IsUser = new vassago.Models.User() { Accounts = new List<Account>() { acc } };
|
|
|
|
_db.Users.Add(acc.IsUser);
|
2023-07-04 12:58:21 -04:00
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Channel UpsertChannel(string channelName)
|
|
|
|
{
|
|
|
|
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channelName && ci.Protocol == PROTOCOL);
|
|
|
|
if (c == null)
|
|
|
|
{
|
|
|
|
c = new Channel();
|
|
|
|
_db.Channels.Add(c);
|
|
|
|
}
|
|
|
|
c.DisplayName = channelName;
|
|
|
|
c.ExternalId = channelName;
|
2023-12-03 14:33:58 -05:00
|
|
|
c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal;
|
2023-07-04 12:58:21 -04:00
|
|
|
c.Messages = c.Messages ?? new List<Message>();
|
|
|
|
c.Protocol = PROTOCOL;
|
|
|
|
c.ParentChannel = protocolAsChannel;
|
|
|
|
c.SubChannels = c.SubChannels ?? new List<Channel>();
|
2023-08-22 14:58:44 -04:00
|
|
|
c.SendMessage = (t) => { return Task.Run(() => { client.SendMessage(channelName, t); }); };
|
|
|
|
c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); };
|
2023-07-04 12:58:21 -04:00
|
|
|
return c;
|
|
|
|
}
|
|
|
|
private Channel UpsertDMChannel(string whisperWith)
|
|
|
|
{
|
|
|
|
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == $"w_{whisperWith}" && ci.Protocol == PROTOCOL);
|
|
|
|
if (c == null)
|
|
|
|
{
|
|
|
|
c = new Channel();
|
|
|
|
_db.Channels.Add(c);
|
|
|
|
}
|
|
|
|
c.DisplayName = $"Whisper: {whisperWith}";
|
|
|
|
c.ExternalId = $"w_{whisperWith}";
|
2023-12-03 14:33:58 -05:00
|
|
|
c.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
|
2023-07-04 12:58:21 -04:00
|
|
|
c.Messages = c.Messages ?? new List<Message>();
|
|
|
|
c.Protocol = PROTOCOL;
|
|
|
|
c.ParentChannel = protocolAsChannel;
|
|
|
|
c.SubChannels = c.SubChannels ?? new List<Channel>();
|
2023-12-01 14:02:47 -05:00
|
|
|
c.SendMessage = (t) => { return Task.Run(() => {
|
|
|
|
try
|
|
|
|
{
|
|
|
|
|
|
|
|
client.SendWhisper(whisperWith, t);
|
|
|
|
}
|
|
|
|
catch(Exception e)
|
|
|
|
{
|
|
|
|
Console.Error.WriteLine(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2023-08-22 14:58:44 -04:00
|
|
|
c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); };
|
2023-07-04 12:58:21 -04:00
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Message UpsertMessage(ChatMessage chatMessage)
|
|
|
|
{
|
|
|
|
var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == chatMessage.Id);
|
|
|
|
if (m == null)
|
|
|
|
{
|
|
|
|
m = new Message();
|
2023-08-22 15:33:09 -04:00
|
|
|
m.Protocol = PROTOCOL;
|
2023-07-04 12:58:21 -04:00
|
|
|
_db.Messages.Add(m);
|
2023-08-22 14:58:44 -04:00
|
|
|
m.Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
2023-07-04 12:58:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
m.Content = chatMessage.Message;
|
|
|
|
m.ExternalId = chatMessage.Id;
|
|
|
|
m.Channel = UpsertChannel(chatMessage.Channel);
|
2023-07-04 20:40:37 -04:00
|
|
|
m.Author = UpsertAccount(chatMessage.Username, m.Channel.Id);
|
2023-07-04 12:58:21 -04:00
|
|
|
m.Author.SeenInChannel = m.Channel;
|
|
|
|
|
|
|
|
m.Reply = (t) => { return Task.Run(() => { client.SendReply(chatMessage.Channel, chatMessage.Id, t); }); };
|
|
|
|
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
private Message UpsertMessage(WhisperMessage whisperMessage)
|
|
|
|
{
|
|
|
|
var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == whisperMessage.MessageId);
|
|
|
|
if (m == null)
|
|
|
|
{
|
|
|
|
m = new Message();
|
2023-08-22 15:33:09 -04:00
|
|
|
m.Protocol = PROTOCOL;
|
2023-07-04 12:58:21 -04:00
|
|
|
_db.Messages.Add(m);
|
2023-08-22 14:58:44 -04:00
|
|
|
m.Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
|
2023-07-04 12:58:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
m.Content = whisperMessage.Message;
|
|
|
|
m.ExternalId = whisperMessage.MessageId;
|
|
|
|
m.Channel = UpsertDMChannel(whisperMessage.Username);
|
2023-12-03 14:33:58 -05:00
|
|
|
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
|
2023-07-04 20:40:37 -04:00
|
|
|
m.Author = UpsertAccount(whisperMessage.Username, m.Channel.Id);
|
2023-07-04 12:58:21 -04:00
|
|
|
m.Author.SeenInChannel = m.Channel;
|
|
|
|
|
2023-08-22 14:58:44 -04:00
|
|
|
m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(whisperMessage.Username, t); }); };
|
2023-07-04 12:58:21 -04:00
|
|
|
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
|
|
|
|
return m;
|
|
|
|
}
|
2023-07-04 18:51:27 -04:00
|
|
|
|
|
|
|
public string AttemptJoin(string channelTarget)
|
|
|
|
{
|
|
|
|
client.JoinChannel(channelTarget);
|
2023-11-30 16:16:07 -05:00
|
|
|
return $"attempt join {channelTarget} - o7";
|
2023-07-04 18:51:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
internal void AttemptLeave(string channelTarget)
|
|
|
|
{
|
|
|
|
client.SendMessage(channelTarget, "o7");
|
|
|
|
client.LeaveChannel(channelTarget);
|
|
|
|
}
|
2023-07-04 12:58:21 -04:00
|
|
|
}
|