Общие принципы написания скриптов для программы RutonyChat - rutony/rutonychat-testscripts GitHub Wiki
В программе есть несколько мест где поддерживаются скрипты. В каждом из мест используются разные аргументы. Но общий принцип почти везде одиннаковый:
- Бот
- Удаленное управление
- Кастомные метки
- Автономные скрипты
Для скриптов используется кодировка UTF-8 без BOM
Шаблон для отладки скриптов. Подойдет Visual Studio Community, Visual Studio Code, Xamarin Studio или другая среда разработки. Так же учитывайте, что в программе доступны некоторые компоненты по умолчанию, например, Newtonsoft json, поэтому он может быть добавлен в скрипте, и будет доступен при вызове скрипта. Но для тестов, его следует добавить в проект.
Список пакетов и библиотек доступных в программе:
- CSScriptLibrary
- Discord
- HtmlAgilityPack
- iTunesLib
- KopiLua
- NAudio
- Newtonsoft.Json
- NLua
- OpenPop
- SocketIoClientDotNet
- SQLite-net
- SuperSocket
- TweetSharp
- websocket-sharp
- WebSocket4Net
Все данные методы сделаны, что бы упростить однотипные действия в скриптах:
Все методы доступны в модуле RutonyBotFunctions
public static string GetScriptDirectory(string scriptname)
Получение каталога в котором размещен скрипт из Мастерской. Может быть полезен, если в скрипте используется какая то база, которая идет в комплекте, что бы получить относительный путь к ней из каталога Workshop.
public static bool FileHasString(string filename, string text)
Проверка наличия строки в текстовом файле.
public static void FileAddString(string filename, string text)
Добавить строку в текстовый файл.
public static void FileClear(string filename)
Очистить текстовый файл.
public static int FileLength(string filename)
Количество строк в текстовом файле.
public static string FileStringAt(string filename, int ind)
Строка из текстовая файла под номер ind.
Для скриптов используется пространство имен - RutonyChat. Вызываемая функция имеет имя RunScript и заключена в класс Script. Аргументы:
- site - имя сайта с которого был осуществлён вызов
- username - имя вызвавшего пользователя
- param - дополнительная информация для точной настройки скрипта пользователем
Важно. Запускаемый скрипт ничего не возвращает. Все вызовы по отсылке сообщений осуществляется методами:
RutonyBot.TwitchBot.Say(<string>); // чат твитча
RutonyBot.TwitchBot.SendCommand(<string>); // отправить команду в чат твитча
RutonyBot.GoodgameBot.Say(<string>); // чат гудгейма
RutonyBot.GoodgameBot.SendCommand(<string>); // отправить команду в чат гудгейма
RutonyBot.GoodgameBot.GetUserId(string username); // получить ID по нику
RutonyBot.GoodgameBot.SendBan(string username, string(int) seconds); // бан на гг
RutonyBot.YoutubeBot.Say(<string>); // чат ютуба
RutonyBot.SayToWindow(string _textParam); // сообщение в окно программы
RutonyBot.SayToWindow(string _textParam, Color color);
RutonyBot.SayToWindow(string _user, string _textParam);
RutonyBot.SayToWindow(string _user, string _textParam, Color color);
RutonyBot.BotSay(string text, List<ProgramProps.SiteEnum> targets); // отправка сообщения на список сайтов
RutonyBot.BotSay(string text, List<string> targets); // аналогично выше команде
RutonyBot.BotSay(string target, string text); // отправить сообщение на сайт target (универсальный метод, под любой сайт, пример, RutonyBot.BotSay("youtube", "123"); )
Важно. Вызов скрипта осуществляется в теле основной программе. А не в отдельном потоке. Поэтому доступны все классы и коллекции самой программы, для прямой работы с ними. Это следует учитывать при создании пауз, они должны быть реализованы отдельными потоками.
using System;
using System.Windows.Forms;
namespace RutonyChat {
public class Script {
public void RunScript(string site, string username, string text, string param) {
RutonyBot.TwitchBot.Say(string.Format("Param: {0}", param));
/* Пример получения ранга пользователя и вывод его статистики
RankControl.ChatterRank cr = RankControl.ListChatters.Find(r => r.Nickname == username);
if (cr != null) {
RutonyBot.TwitchBot.Say(
string.Format("{0} написал {1} сообщения(й)", username, cr.MessageQty)
);
}
*/
// Вывод сообщения
//MessageBox.Show("Это сообщение открыто из чата :D");
}
}
}
Для скриптов используется пространство имен - RutonyChat. Вызываемая функция имеет имя RunScript и заключена в класс Script. Аргументы:
- username - имя вызвавшего пользователя, в случае триггеров по событию
- param - дополнительная информация для точной настройки скрипта пользователем, в случае с донатом, там будет сумма
Важно. Вызов скрипта осуществляется в теле основной программе. А не в отдельном потоке. Поэтому доступны все классы и коллекции самой программы, для прямой работы с ними. Это следует учитывать при создании пауз, они должны быть реализованы отдельными потоками.
using System;
using System.Windows.Forms;
namespace RutonyChat {
public class Script {
public void RunScript(string username, string param) {
MessageBox.Show("Это сообщение открыто из чата :D");
}
}
}
Для скриптов используется пространство имен - RutonyChat. Вызываемая функция имеет имя RunScript и заключена в класс Script. Аргументы:
- param - дополнительная информация для точной настройки скрипта пользователем
Важно. Запускаемый скрипт возвращает значение которое будет помещено в метку. Вызов скрипта осуществляется в теле основной программе. А не в отдельном потоке. Поэтому доступны все классы и коллекции самой программы, для прямой работы с ними. Это следует учитывать при создании пауз, они должны быть реализованы отдельными потоками.
using System;
using System.Windows.Forms;
namespace RutonyChat {
public class Script {
public string RunScript(string param) {
}
}
}
Они отличаются от остальных скриптов одним очень важным моментом - они находятся в отдельных потоках и не выгружаются после завершения. То есть, они работают постоянно. Поэтому их можно использовать в случаях когда требуется накопление данных. Либо более тонкие и сложные условия для работы. Они универсальны в данным случае.
Примеры использования: Антикапс для чата (скрипт запоминает кто уже капсил и в следующий раз уже выдает бан вместо предупреждения), игра Травия (чаттеры отгадывают слова, если отгадали получают очки/баллы, можно посмотреть весь рейтинг, кто больше всего наотгадывал, база баллов сохраняется/загружается один раз), ну и другие примеры, их очень много.
using System;
using System.Threading;
namespace RutonyChat {
public class Script {
public void InitParams(string param) {
RutonyBot.SayToWindow("Test script connected");
}
public void Closing() {
RutonyBot.SayToWindow("Test script disconnected");
}
public void NewMessage(string site, string name, string text, bool system) {
RutonyBot.SayToWindow(string.Format("site={0}, name={1}, text={2}", site, name, text));
}
public void NewMessageEx(string site, string name, string text, bool system, Dictionary<string, string> Params) {
}
public void NewAlert(string site, string typeEvent, string subplan, string name, string text, float donate, string currency, int qty) {
RutonyBot.SayToWindow(string.Format("site={0} typeEvent={1}/{2}, name={3}, text={4}, donate={5}/{6}, qty={7}",
site, typeEvent, subplan, name, text, donate, currency, qty ));
}
}
}
Важно. Естественно такие скрипты так же поддерживают сложные параметры, поэтому у метода InitParams может быть перегрузка InitParams(Dictionary<string, string param).
NewMessageEx в Params, находятся дополнительные данные, например, id пользователя, является ли пользователь модератором и прочее.
Программа поддерживает 2 вида параметров: строкой и файлом описания параметров. В зависимости от параметров в скрипт при вызове передается либо строка, либо коллекция значений. Значения заполняются из пресета бота, либо кастомной метки. В зависимости от поддержки параметров скрипта, рядом с полем выбора скрипта будет либо текстовое поле, либо кнопка с вызовом окошка, где можно задать все параметры.
Не требуется дополнительно никаких настроек. В скрипт передается строковый параметр - param который содержит значение заполненное пользователем в программе. Далее уже скрипт сам волен делать и обрабатывать параметр как ему нужно.
Пример:
public void RunScript(string site, string username, string text, string param)
Если в каталоге со скриптом лежит json файл с тем же именем, что и скрипт. То программа автоматически понимает что у скрипта есть описание параметров. Значение параметров, которые указал пользователь будет отдано в те же самые функции, с той лишь разницей что будет передан в скрипт не строковый параметр, а коллекция Dictionary<string, string>.
Пример: Есть скрипт testscript.cs и файл testscript.json. То будет использоваться перегрузка метода в
public void RunScript(string site, string username, string text, Dictionary<string, string> param)
Где ключи соответственно параметры, значения - значения.
Формат аналогичен параметрам для тем оформлений. Пример использования - тема оформления - default-customs, файл params.json
// Структура файла параметров. Где массив параметры содержит описание параметров
{
"params": [ ]
}
// Цвет
{
"name": "NicknameColor",
"desc": {
"russian": "Цвет ника",
"english": "Nickname color",
"tchinese": "Nickname color"
},
"type": "color",
"default": "#00A4F2"
}
// Шрифт
{
"name": "FontNickname",
"desc": {
"russian": "Шрифт никнейма",
"english": "Font Nickname",
"tchinese": "Font Nickname"
},
"type": "font",
"default": {
"name" : "Roboto",
"size" : 16
}
}
// Список
{
"name": "ShowEffect",
"desc": {
"russian": "Эффект появления сообщений",
"english": "Show messages effect",
"tchinese": "Show messages effect"
},
"type": "list",
"default": "none",
"enumerate": [
{
"value": "none"
},
{
"value": "fadeIn"
},
{
"value": "zoomIn"
},
{
"value": "bounce"
},
{
"value": "bounceInRight"
},
{
"value": "bounceInLeft"
},
{
"value": "bounceIn"
},
{
"value": "flipInX"
},
{
"value": "flipInY"
},
{
"value": "lightSpeedIn"
},
{
"value": "slideInRight"
},
{
"value": "slideInLeft"
},
]
}
// Булево
{
"name": "HideMessages",
"desc": {
"russian": "Скрывать сообщения",
"english": "Hide messages",
"tchinese": "Hide messages"
},
"type": "bool",
"default": false
}
// Целое число со знаком
{
"name": "TimeNewMessages",
"desc": {
"russian": "Время показа сообщений",
"english": "Time showing messages",
"tchinese": "Time showing messages"
},
"type": "integer",
"default": 10,
"min":2,
"max": 60
}
// Вещественное число со знаком
{
"name": "ShadowSize",
"desc": {
"russian": "Размер тени",
"english": "Shadow size",
"tchinese": "Shadow size"
},
"type": "float",
"default": 1,
"min": 1,
"max": 100
}
// Строка
{
"name": "StringExample",
"desc": {
"russian": "Пример строки (не используется)",
"english": "String example (unabled)",
"tchinese": "String example (unabled)"
},
"type": "string",
"default": "Simple string text"
}
Доступ к нему осуществаляется через ChatDB.ChatData и обращение к методам через ChatDB
Доступные коллекции
public class FollowerClass {
public int Index;
public DateTime DT;
public ProgramProps.SiteEnum Site;
public string Name;
public bool isTest = false;
}
public class SubscriberClass {
public int Index;
public DateTime DT;
public ProgramProps.SiteEnum Site;
public string Name;
public string Text = "";
public bool isTest = false;
}
public class DonateClass {
public int Index;
public DateTime DT;
public ProgramProps.SiteEnum Site;
public string Name;
public float Sum;
public string Currency;
public string Text;
public bool isTest = false;
}
public class DonateSumClass {
public int Index;
public ProgramProps.SiteEnum Site;
public string Name;
public float Sum;
public string Currency;
public bool isTest = false;
}
public class ChatDataClass {
public List<FollowerClass> ListFollowers = new List<FollowerClass>();
public List<SubscriberClass> ListSubscribers = new List<SubscriberClass>();
public List<DonateClass> ListDonates = new List<DonateClass>();
public List<DonateSumClass> ListDonateSum = new List<DonateSumClass>();
public Dictionary<LabelBase.LabelType, int> ListCounters = new Dictionary<LabelBase.LabelType,int>();
}
public static ChatDataClass ChatData = new ChatDataClass();
Console.WriteLine(LabelBase.DictLabels[LabelBase.LabelType.Viewers_Youtube]);
LabelBase.DictLabels[LabelBase.LabelType.Viewers_Youtube].Params["QTY"] = qtyYT.ToString();
LabelBase.DictLabels[LabelBase.LabelType.Viewers_Youtube].Params["Like"] = intLike.ToString();
LabelBase.DictLabels[LabelBase.LabelType.Viewers_Youtube].Params["Dislike"] = intDislike.ToString();
LabelBase.DictLabels[LabelBase.LabelType.Viewers_Youtube].Save();
Общий принцип
В программе есть база данных с рангами. Она храниться в файле /dataX/ranks.db в формате SQLite3 (программа для работы с базой DB Browser for SQLite). Программа использует файл базы SQLite3 только для первичной загрузки и сохранения данных в оперативную базу. Для оперативной же работы используется база RankControl.ListChatters. Итого есть 2 базы:
- База SQLite3 (используемая для хранения)
- База RankControl.ListChatters(используемая для оперативной работы, добавления, чтения, изменения)
Получается что работа вся происходит с RankControl.ListChatters, но для того что бы зафиксировать изменения нужно так же сделать изменения и в SQLite3 базе.
Такая организация вынужденная для ускорения работы программы. Просто пример, нужно внести изменения на 50000 пользователей. Проще внести их в оперативную базу, и добавить в список изменений в SQLite3 базу, которая постепенно будет их вносить. Это позволит и сохранить данные по рангам на случай падения программы, так и сократит скорость закрытия программы.
// База чаттеров, оперативная база, отсюда читаем ранги, вносим изменения, используется в первую очередь именно для чтения
RankControl.ListChatters<ChatterRank>
// Метод для обновления данных по чаттеру, по сути это список обьектов ChatterRank, которые в пакетном режиме вносятся в базу SQLite3, как есть возможность провести операцию
RankControl.ListUpdateSQL.Add(ChatterRank crItem);
По команде в чате !rank бот отправляет сообщение: Никнейм у тебя N кредитов
Файл скрипта: auto_simplerank.cs
using System;
using System.Collections.Generic;
using System.Threading;
using Newtonsoft.Json;
using System.Linq;
using System.Globalization;
using System.Drawing;
namespace RutonyChat {
public class Script {
public void InitParams(string param) {
}
public void Closing() {
}
public void NewMessage(string site, string name, string text, bool system) {
}
public void ShowAlert(string name, float sum, string text) {
}
public void NewAlert(string site, string typeEvent, string subplan, string name, string text, float donate, string currency, int qty) {
}
public void NewMessageEx(string site, string name, string text, bool system, Dictionary<string, string> Params) {
if (text == "!rank") {
RankControl.ChatterRank crItem = RankControl.ListChatters.FindLast(r => r.Nickname == "rutony");
if (crItem != null) {
RutonyBot.BotSay(site, name + " у тебя " + crItem.CreditsQty.ToString() + " кредитов");
}
}
}
}
}
По команде в чате !цена или !price и название предмета, выдавать информацию о цене предмета из игры Escape from Tarkov. Предусмотреть возможность задать имя команды в программе.
Файл скрипта: auto_tarkovprices.cs
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Linq;
namespace RutonyChat {
public class Script {
public List<string> ListKeywords = new List<string>();
public void InitParams(Dictionary<string, string> param) {
// параметры скрипта
string Keywords = "";
if (param.ContainsKey("Keywords")) {
try {
Keywords = param["Keywords"];
} catch { }
}
ListKeywords = Keywords.Split(',').ToList();
RutonyBot.SayToWindow("Tarkov prices script connected");
}
public void Closing() {
RutonyBot.SayToWindow("Tarkov prices script disconnected");
}
public void NewMessage(string site, string name, string text, bool system) {
//RutonyBot.SayToWindow(string.Format("site={0}, name={1}, text={2}", site, name, text));
}
public class Ru {
public string id;
public string name;
public string shortName;
public int lastLowPrice;
}
public class Data {
public List<Ru> ru = new List<Ru>();
}
public class RootItems {
public Data data = new Data();
}
public async Task<string> GetProductsData(string _itemName) {
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, @"https://api.tarkov.dev/graphql");
var content = new StringContent("{\"query\": \"{ru: items(lang: ru name: \\\"" + _itemName + "\\\") {id name shortName lastLowPrice} }\"}", null, "application/json");
string res = "";
try {
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
res = await response.Content.ReadAsStringAsync();
} catch { }
return res;
}
public void NewMessageEx(string site, string name, string text, bool system, Dictionary<string, string> Params) {
foreach (string itemKeyword in ListKeywords) {
if (text.ToLower().StartsWith(itemKeyword)) {
string[] arrString = text.Split(' ');
string itemName = text.Replace(itemKeyword, "").Trim();
string itemsDataRequest = GetProductsData(itemName).GetAwaiter().GetResult();
RootItems itemsData = JsonConvert.DeserializeObject<RootItems>(itemsDataRequest);
if (itemsData.data.ru.Count > 0) {
RutonyBot.BotSay(site, itemsData.data.ru[0].name + " стоит " + itemsData.data.ru[0].lastLowPrice + " рублей");
} else {
RutonyBot.BotSay(site, "Информации об " + itemName + " не обнаружено!");
}
return;
}
}
}
public void NewAlert(string site, string typeEvent, string subplan, string name, string text, float donate, string currency, int qty) {
}
}
}
Файл параметров: auto_tarkovprices.json
{
"params": [
{
"name": "Keywords",
"desc": {
"russian": "Команда для отображения цены",
"english": "Command",
"tchinese": "Command "
},
"type": "string",
"default": "!price,!цена"
}
]
}
Реализовать автоматический сбор ссылок из чата в которых содержится steamcommunity упоминание в текстовый файл.
Файл скрипта: auto_savelinks.cs
using System;
using System.Collections.Generic;
using System.Threading;
using Newtonsoft.Json;
using System.Linq;
namespace RutonyChat {
public class Script {
string SaveFilename = @"c:\links.txt";
public void InitParams(Dictionary<string, string> param) {
RutonyBot.SayToWindow("Сбор ссылок на steamcommunity включен");
if (param.ContainsKey("SaveFile")) {
try {
SaveFilename = param["SaveFile"];
} catch { }
}
}
public void Closing() {
RutonyBot.SayToWindow("Сбор ссылок на steamcommunity отключен");
}
public void NewMessage(string site, string name, string text, bool system) {
if (text.Contains("steamcommunity")) {
RutonyBotFunctions.FileAddString(SaveFilename, string.Format("{0}> {1}: {2}", DateTime.Now.ToString(), name, text));
}
}
public void NewAlert(string site, string typeEvent, string subplan, string name, string text, float donate, string currency, int qty) {
}
}
}
Автоматическое приветствие Новых зрителей, предусмотреть вариативность приветствия
Файл скрипта: auto_newviewer_hello.cs
using System;
using System.Threading;
using System.Collections.Generic;
namespace RutonyChat {
public class Script {
public void InitParams(string param) {
RutonyBot.SayToWindow("Приветствие подключено!");
}
public void Closing() {
}
public void NewMessage(string site, string name, string text, bool system) {
if (system) {
return;
}
}
public void NewAlert(string site, string typeEvent, string subplan, string name, string text, float donate, string currency, int qty) {
if (typeEvent == "new_viewer") {
List<string> ListHello = new List<string>();
ListHello.Add("$name, привет! Как дела?");
ListHello.Add("Вот это поворот сам $name зашел на огонек!");
ListHello.Add("$name, привет!");
ListHello.Add("Дай пятюню $name!");
ListHello.Add("Всем выйти из сумрака $name в чате!");
ListHello.Add("Здраствуй $name садись присаживайся");
ListHello.Add("Я вижу тебя $name 🕵🏼");
Random rnd = new Random();
int ind = rnd.Next(ListHello.Count - 1);
string msg = ListHello[ind].Replace("$name", name);
RutonyBot.SayToWindow(msg);
}
}
}
}
Реализовать автоматическую выдачу VIP для Твитч за баллы канала. VIP выдается на месяц. При запуске скрипта производится проверка на просроченность VIP и его снятие. Предусмотреть возможность настройки параметров скрипта.
Файл скрипта: auto_vip.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Threading;
using System.Drawing;
using System.IO;
using System.Xml.Linq;
namespace RutonyChat {
public class Script {
public int VipPrice = 15000;
public int VipExpireDays = 30;
public string VipText = "$name, выдана VIP, носи с гордостью и не позорь канал";
public string VipTextExpire = "$name, подписка на VIP кончилась";
private string FileName = "vip_expires.json";
public class VipInfoItem {
public string Nickname = string.Empty;
public DateTime DT = DateTime.MinValue;
}
public List<VipInfoItem> ListVipInfo = new List<VipInfoItem>();
public void InitParams(Dictionary<string, string> param) {
RutonyBot.SayToWindow("Авто випки [Подключились]", Color.YellowGreen);
if (param.ContainsKey("VipPrice")) {
try {
VipPrice = int.Parse(param["VipPrice"]);
} catch { }
}
if (param.ContainsKey("VipExpireDays")) {
try {
VipExpireDays = int.Parse(param["VipExpireDays"]);
} catch { }
}
if (File.Exists(ProgramProps.dir + FileName)) {
try {
ListVipInfo = JsonConvert.DeserializeObject<List<VipInfoItem>>(ProgramProps.dir + FileName);
} catch {
ListVipInfo = new List<VipInfoItem>() { };
}
foreach (VipInfoItem itemVipInfo in ListVipInfo) {
if (itemVipInfo.DT.AddDays(VipExpireDays) > DateTime.Now) {
RutonyBot.SendTwitchCommand(RutonyBot.TwitchCommands.unvip, itemVipInfo.Nickname, itemVipInfo.Nickname, itemVipInfo.Nickname, itemVipInfo.Nickname);
RutonyBot.BotSay("twitch", VipTextExpire.Replace("$name", itemVipInfo.Nickname));
}
}
SaveData();
}
}
public void Closing() {
RutonyBot.SayToWindow("Авто випки [Отключились]", Color.Red);
SaveData();
}
public void SaveData() {
try {
string textForSave = JsonConvert.SerializeObject(ListVipInfo, Formatting.Indented);
File.WriteAllText(ProgramProps.dir + FileName, textForSave);
} catch { }
}
public void NewMessage(string site, string name, string text, bool system) {
}
public void NewMessageEx(string site, string name, string text, bool system, Dictionary<string, string> Params) {
}
public void NewAlert(string site, string typeEvent, string subplan, string name, string text, float donate, string currency, int qty) {
if (typeEvent == "TwitchPoints" && donate == VipPrice) {
RutonyBot.SendTwitchCommand(RutonyBot.TwitchCommands.vip, name, name, name, name);
ListVipInfo.Add(new VipInfoItem() {
Nickname = name,
DT = DateTime.Now,
});
RutonyBot.BotSay("twitch", VipText.Replace("$name", name));
SaveData();
}
}
}
}
Файл параметров auto_vip.json
{
"params": [
{
"name": "VipPrice",
"desc": {
"russian": "Цена VIP",
"english": "Цена VIP",
"tchinese": "Цена VIP"
},
"type": "integer",
"default": 15000,
"min": 10,
"max": 10000000
},
{
"name": "VipExpireDays",
"desc": {
"russian": "Период выдачи VIP (дни)",
"english": "Период выдачи VIP (дни)",
"tchinese": "Период выдачи VIP (дни)"
},
"type": "integer",
"default": 30,
"min": 0,
"max": 100000000
},
{
"name": "VipText",
"desc": {
"russian": "Текст выдачи VIP",
"english": "Текст выдачи VIP",
"tchinese": "Текст выдачи VIP"
},
"type": "string",
"default": "$name, выдана VIP, носи с гордостью и не позорь канал"
},
{
"name": "VipTextExpire",
"desc": {
"russian": "Текст отнятия VIP",
"english": "Текст отнятия VIP",
"tchinese": "Текст отнятия VIP"
},
"type": "string",
"default": "$name, подписка на VIP кончилась"
}
]
}