You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

668 lines
28 KiB
C#

using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using TwitchLib.Client;
using TwitchLib.Client.Events;
using TwitchLib.Client.Models;
using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Models;
using Newtonsoft.Json.Linq;
using SysBot.Base;
using TwitchLib.Api;
using User = TwitchLib.Api.V5.Models.Users.User;
namespace TwitchPlaySwitch
{
class Program
{
private static SwitchConnectionAsync _connection;
private static readonly Queue StickQueue = new Queue();
private static readonly Queue ButtonQueue = new Queue();
private static bool _broadcasterOnly = false;
private class StickMovement
{
public SwitchStick Stick;
public short X = -1;
public short Y = -1;
public int Hold = 1000;
}
private static readonly JObject Config = JObject.Parse(File.ReadAllText("config.json"));
private static readonly JObject NSwitch = JObject.Parse(Config.GetValue("nSwitch").ToString());
private static readonly string[] Stick =
{
"LSU", "LSUR", "LSUL", "LSDR", "LSDL", "LSD", "LSL", "LSR", "RSU", "RSD", "RSL", "RSR"
};
private static readonly string[] Button =
{
"DLEFT", "DRIGHT", "DUP", "DDOWN", "A", "B", "X", "Y", "+", "-", "ZL", "ZR", "L", "R", "LSP", "RSP"
};
private static void Log(string msg, ConsoleColor color = default)
{
string log = $"{DateTime.Now}: {msg}";
if (!Directory.Exists("logs"))
{
Directory.CreateDirectory("logs");
}
File.AppendAllText($"logs/{Convert.ToDateTime(DateTime.Today).ToString("dd.MM.yy")}.txt",
log + Environment.NewLine);
Console.ForegroundColor = color;
Console.WriteLine(log);
Console.ResetColor();
}
private class WebhookContent
{
public string username = "Twitch Chat";
public string content;
public string avatar_url = "https://brand.twitch.tv/assets/logos/svg/glitch/purple.svg";
}
private static async Task SendWebhook(WebhookContent content)
{
if (_broadcasterOnly) return;
string json = JsonConvert.SerializeObject(content);
StringContent data = new StringContent(json, Encoding.UTF8, "application/json");
var url = (string) Config.GetValue("DiscordWebhook");
var client = new HttpClient();
await client.PostAsync(url, data);
client.Dispose();
}
private static void ConnectSwitch(string ip, int port = 6000)
{
_connection = new SwitchConnectionAsync(ip, port);
_connection.Connect();
if (_connection.Connected)
{
Log($"Connected to nintendo switch on {_connection.IP}:{_connection.Port}!", ConsoleColor.Green);
Log($"Say \"exit\" in twitch or press any key to quit the application!", ConsoleColor.Green);
}
}
public static void Main()
{
Log(
$@"{Environment.NewLine} _____ _ _ _ ____ _ ____ _ _ _
|_ ___ _(_| |_ ___| |__ | _ \| | __ _ _ _ ___/ _____ _(_| |_ ___| |__
| | \ \ /\ / | | __/ __| '_ \| |_) | |/ _` | | | / __\___ \ \ /\ / | | __/ __| '_ \
| | \ V V /| | || (__| | | | __/| | (_| | |_| \__ \___) \ V V /| | || (__| | | |
|_| \_/\_/ |_|\__\___|_| |_|_| |_|\__,_|\__, |___|____/ \_/\_/ |_|\__\___|_| |_|
|___/", ConsoleColor.Green);
ConnectSwitch((string) NSwitch.GetValue("IP"));
Bot bot = new Bot();
Console.ReadLine();
}
class Bot
{
TwitchClient _client;
TwitchAPI api;
public Bot()
{
api = new TwitchAPI();
api.Settings.ClientId = (string) Config.GetValue("ClientID");
api.Settings.AccessToken = (string) Config.GetValue("AccessToken");
ConnectionCredentials credentials =
new ConnectionCredentials((string) Config.GetValue("username"), (string) Config.GetValue("OAuth"));
var clientOptions = new ClientOptions
{
MessagesAllowedInPeriod = 750,
ThrottlingPeriod = TimeSpan.FromSeconds(30)
};
WebSocketClient customClient = new WebSocketClient(clientOptions);
_client = new TwitchClient(customClient);
_client.Initialize(credentials, (string) Config.GetValue("username"));
_client.OnLog += Client_OnLog;
_client.OnMessageReceived += Client_OnMessageReceived;
_client.OnConnected += Client_OnConnected;
_client.Connect();
}
private void Client_OnLog(object sender, OnLogArgs e)
{
Log($"{e.BotUsername} - {e.Data}");
}
private void Client_OnConnected(object sender, OnConnectedArgs e)
{
Log($"Connected to {e.AutoJoinChannel}", ConsoleColor.Green);
}
private async void Client_OnMessageReceived(object sender, OnMessageReceivedArgs e)
{
User user = await api.V5.Users.GetUserByIDAsync(e.ChatMessage.UserId);
WebhookContent webhookContent = new WebhookContent();
if (user.CreatedAt > DateTime.Now.AddDays(-7))
{
Log($"User {e.ChatMessage.DisplayName} account is too new: {user.CreatedAt}", ConsoleColor.DarkRed);
_client.SendMessage(e.ChatMessage.Channel, "Your account is too new... Wait a little more...");
webhookContent.username = "!WARNING!";
webhookContent.avatar_url = "https://upload.wikimedia.org/wikipedia/en/thumb/1/15/Ambox_warning_pn.svg/1178px-Ambox_warning_pn.svg.png";
webhookContent.content = $"User `{e.ChatMessage.DisplayName}` account is too new... Created at: `{user.CreatedAt}`";
SendWebhook(webhookContent);
}
else if (_broadcasterOnly && !e.ChatMessage.IsBroadcaster)
_client.SendMessage(e.ChatMessage.Channel, "Bot is in Broadcaster only mode.");
else
HandleInput(e, _client);
webhookContent.username = e.ChatMessage.DisplayName;
webhookContent.avatar_url = user.Logo;
webhookContent.content = e.ChatMessage.Message;
SendWebhook(webhookContent);
}
}
private static async Task HandleInput(OnMessageReceivedArgs message, TwitchClient client)
{
string msg = message.ChatMessage.Message;
string args = msg.ToUpper();
string[] words = args.Split(' ');
switch (args)
{
case "HELP":
client.SendMessage(message.ChatMessage.Channel, "You get to control the Nintendo Switch!");
client.SendMessage(message.ChatMessage.Channel, "You can say any of the following and they will execute on the console! They can also be chained together");
client.SendMessage(message.ChatMessage.Channel, $"{string.Join(" ", Button)} {string.Join(" ", Stick)}");
client.SendMessage(message.ChatMessage.Channel, "You can specify \"slower\", \"slow\" or \"medium\" to control the speed of the joystick");
client.SendMessage(message.ChatMessage.Channel, "You can also add \"short\" or \"long\" to control the duration of how long the joystick is held");
return;
case "EXIT GAME":
{
if (message.ChatMessage.IsBroadcaster)
{
CancellationTokenSource cancellationToken = new CancellationTokenSource();
await _connection.SendAsync(SwitchCommand.Click(SwitchButton.HOME),
cancellationToken.Token);
cancellationToken.Cancel();
Thread.Sleep(500);
cancellationToken = new CancellationTokenSource();
await _connection.SendAsync(SwitchCommand.Click(SwitchButton.X),
cancellationToken.Token);
cancellationToken.Cancel();
Thread.Sleep(500);
cancellationToken = new CancellationTokenSource();
await _connection.SendAsync(SwitchCommand.Click(SwitchButton.A),
cancellationToken.Token);
cancellationToken.Cancel();
Log("Game exited", ConsoleColor.Red);
client.SendMessage(message.ChatMessage.Channel, "Game exited");
}
else
{
client.SendMessage(message.ChatMessage.Channel,
"Command only available for the broadcaster.");
}
break;
}
case "EXIT":
{
if (message.ChatMessage.IsBroadcaster)
{
_connection.Disconnect();
Log("Bye bye!", ConsoleColor.Red);
client.SendMessage(message.ChatMessage.Channel, "Bye bye!");
Environment.Exit(1);
}
else
{
client.SendMessage(message.ChatMessage.Channel,
"Command only available for the broadcaster.");
}
break;
}
case "RESTART":
{
if (message.ChatMessage.IsBroadcaster)
{
_connection.Disconnect();
Log("Restarting!!!", ConsoleColor.DarkRed);
client.SendMessage(message.ChatMessage.Channel, "Restarting");
ConnectSwitch((string) NSwitch.GetValue("IP"));
}
else
{
client.SendMessage(message.ChatMessage.Channel,
"Command only available for the broadcaster.");
}
break;
}
case "HOME":
{
if (message.ChatMessage.IsBroadcaster)
{
CancellationTokenSource cancellationToken = new CancellationTokenSource();
await _connection.SendAsync(SwitchCommand.Click(SwitchButton.HOME),
cancellationToken.Token);
cancellationToken.Cancel();
}
else
{
client.SendMessage(message.ChatMessage.Channel,
"Command only available for the broadcaster.");
}
break;
}
case "BROADCASTERONLY":
if (message.ChatMessage.IsBroadcaster)
{
if (_broadcasterOnly)
{
_broadcasterOnly = false;
Log($"BroadcasterOnly mode turned off: {_broadcasterOnly}", ConsoleColor.Green);
client.SendMessage(message.ChatMessage.Channel,
"Bot is no longer in Broadcaster only mode.");
}
else
{
_broadcasterOnly = true;
Log($"BroadcasterOnly mode turned on: {_broadcasterOnly}", ConsoleColor.Red);
client.SendMessage(message.ChatMessage.Channel, "Bot is now in Broadcaster only mode.");
}
}
else
{
client.SendMessage(message.ChatMessage.Channel,
"Command only available for the broadcaster.");
}
break;
}
if (Stick.Any(args.Contains))
{
short speed = short.MaxValue;
int hold = 1000;
if (args.Contains("SLOWER"))
{
speed = 8000;
//speed = 10000;
}
else if (args.Contains("SLOW"))
{
speed = 10000;
//speed = 16382;
}
else if (args.Contains("MEDIUM"))
{
speed = 16382;
//speed = 20000;
}
if (args.Contains("SHORT"))
{
hold = 500;
}
else if (args.Contains("LONG"))
{
hold = 5000;
}
foreach (var word in words)
{
if (Array.Exists(Stick, e => e == word))
{
short x = 0;
short y = 0;
SwitchStick stick;
StickMovement stickMovement = new StickMovement {Hold = hold};
Log($"{message.ChatMessage.DisplayName} moved stick {word}", ConsoleColor.Green);
switch (word)
{
case "LSU":
stick = SwitchStick.LEFT;
x = 0;
y = speed;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, 0, speed);
break;
case "LSUL":
stick = SwitchStick.LEFT;
x = short.Parse((speed * -1).ToString());
y = speed;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, short.Parse((speed * -1).ToString()), speed);
break;
case "LSUR":
stick = SwitchStick.LEFT;
x = speed;
y = speed;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, speed, speed);
break;
case "LSD":
stick = SwitchStick.LEFT;
x = 0;
y = short.Parse((speed * -1).ToString());
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, 0, short.Parse((speed * -1).ToString()));
break;
case "LSDL":
stick = SwitchStick.LEFT;
x = short.Parse((speed * -1).ToString());
y = short.Parse((speed * -1).ToString());
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, short.Parse((speed * -1).ToString()), short.Parse((speed * -1).ToString()));
break;
case "LSDR":
stick = SwitchStick.LEFT;
x = speed;
y = short.Parse((speed * -1).ToString());
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, speed, short.Parse((speed * -1).ToString()));
break;
case "LSL":
stick = SwitchStick.LEFT;
x = short.Parse((speed * -1).ToString());
y = 0;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, short.Parse((speed * -1).ToString()), 0);
break;
case "LSR":
stick = SwitchStick.LEFT;
x = speed;
y = 0;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.LEFT, speed, 0);
break;
case "RSU":
stick = SwitchStick.RIGHT;
x = 0;
y = speed;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, 0, speed);
break;
case "RSD":
stick = SwitchStick.RIGHT;
x = 0;
y = short.Parse((speed * -1).ToString());
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, 0, short.Parse((speed * -1).ToString()));
break;
case "RSL":
stick = SwitchStick.RIGHT;
x = short.Parse((speed * -1).ToString());
y = 0;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, short.Parse((speed * -1).ToString()), 0);
break;
case "RSR":
stick = SwitchStick.RIGHT;
x = speed;
y = 0;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, speed, 0);
break;
case "RSUL":
stick = SwitchStick.RIGHT;
x = short.Parse((speed * -1).ToString());
y = speed;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, short.Parse((speed * -1).ToString()), speed);
break;
case "RSUR":
stick = SwitchStick.RIGHT;
x = speed;
y = speed;
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, speed, speed);
break;
case "RSDL":
stick = SwitchStick.RIGHT;
x = short.Parse((speed * -1).ToString());
y = short.Parse((speed * -1).ToString());
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, short.Parse((speed * -1).ToString()), short.Parse((speed * -1).ToString()));
break;
case "RSDR":
stick = SwitchStick.RIGHT;
x = speed;
y = short.Parse((speed * -1).ToString());
stickMovement.Stick = stick;
stickMovement.X = x;
stickMovement.Y = y;
//MoveStick(SwitchStick.RIGHT, speed, short.Parse((speed * -1).ToString()));
break;
}
Log("QUEUED", ConsoleColor.Yellow);
StickQueue.Enqueue(stickMovement);
}
}
foreach (StickMovement stick in StickQueue)
{
Log($"Moving {stick.Stick} stick x: {stick.X} y: {stick.Y} hold: {stick.Hold}", ConsoleColor.Red);
await MoveStick(stick.Stick, stick.X, stick.Y, stick.Hold);
}
StickQueue.Clear();
}
if (Button.Any(args.Contains))
{
foreach (var word in words)
{
if (Array.Exists(Button, e => e == word))
{
SwitchButton button = SwitchButton.CAPTURE; // Capture button is never used, let's use it as a "null" value
Log($"{message.ChatMessage.DisplayName} Pressed button {word}", ConsoleColor.Green);
switch (word)
{
case "DLEFT":
button = SwitchButton.DLEFT;
//PressButton(SwitchButton.DLEFT);
break;
case "DRIGHT":
button = SwitchButton.DRIGHT;
//PressButton(SwitchButton.DRIGHT);
break;
case "DUP":
button = SwitchButton.DUP;
//PressButton(SwitchButton.DUP);
break;
case "DDOWN":
button = SwitchButton.DDOWN;
//PressButton(SwitchButton.DDOWN);
break;
case "A":
button = SwitchButton.A;
//PressButton(SwitchButton.A);
break;
case "B":
button = SwitchButton.B;
//PressButton(SwitchButton.B);
break;
case "X":
button = SwitchButton.X;
//PressButton(SwitchButton.X);
break;
case "Y":
button = SwitchButton.Y;
//PressButton(SwitchButton.Y);
break;
case "+":
button = SwitchButton.PLUS;
//PressButton(SwitchButton.PLUS);
break;
case "-":
button = SwitchButton.MINUS;
//PressButton(SwitchButton.MINUS);
break;
case "ZL":
button = SwitchButton.ZL;
//PressButton(SwitchButton.ZL);
break;
case "ZR":
button = SwitchButton.ZR;
//PressButton(SwitchButton.ZR);
break;
case "L":
button = SwitchButton.L;
//PressButton(SwitchButton.L);
break;
case "R":
button = SwitchButton.R;
//PressButton(SwitchButton.R);
break;
case "LSP":
button = SwitchButton.LSTICK;
//PressButton(SwitchButton.LSTICK);
break;
case "RSP":
button = SwitchButton.RSTICK;
//PressButton(SwitchButton.RSTICK);
break;
}
Log("QUEUED", ConsoleColor.Yellow);
if (button != SwitchButton.CAPTURE
) // Capture button is never used, let's use it as a "null" value
ButtonQueue.Enqueue(button);
}
}
foreach (SwitchButton button in ButtonQueue)
{
Log($"Pressed {button}", ConsoleColor.Red);
await PressButton(button);
}
ButtonQueue.Clear();
}
}
private static async Task PressButton(SwitchButton button)
{
CancellationTokenSource cancellationToken = new CancellationTokenSource();
if (button == SwitchButton.ZL || button == SwitchButton.ZR)
{
await _connection.SendAsync(SwitchCommand.Hold(button), cancellationToken.Token);
Thread.Sleep(2000);
await _connection.SendAsync(SwitchCommand.Release(button), cancellationToken.Token);
}
else
{
await _connection.SendAsync(SwitchCommand.Click(button), cancellationToken.Token);
}
Thread.Sleep(500);
cancellationToken.Cancel();
//StickQueue.Clear();
}
private static async Task MoveStick(SwitchStick stick, short x, short y, int hold)
{
CancellationTokenSource cancellationToken = new CancellationTokenSource();
await _connection.SendAsync(SwitchCommand.SetStick(stick, x, y), cancellationToken.Token);
//.Sleep(stick == SwitchStick.RIGHT ? 500 : 1000);
Thread.Sleep(hold);
await _connection.SendAsync(SwitchCommand.ResetStick(stick), cancellationToken.Token);
cancellationToken.Cancel();
//ButtonQueue.Clear();
}
}
}