C#:
using System;
using System.Linq;
using Random=System.Random;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Newtonsoft.Json;
using UnityEngine;
using Facepunch;
namespace Oxide.Plugins {
[Info("RaidAlert", "Own3r", "1.0.0")]
class RaidAlert : RustPlugin {
#region Variables
[PluginReference]
private Plugin Friends;
private const string URL_SendMessage = "https://api.vk.com/method/messages.send?user_id={0}&message={1}&v=5.68&random_id={2}&access_token={3}";
private const string URL_GetUserInfo = "https://api.vk.com/method/users.get?v=5.73&user_ids={0}&access_token={1}";
private const string AllowPermission = "raidalert.access";
private readonly int triggerLayer = LayerMask.GetMask("Trigger");
private Random Rnd = new Random();
private Dictionary<ulong, Dictionary<string, string>> UsersVk = new Dictionary<ulong, Dictionary<string, string>>();
private Dictionary<ulong, DateTime> LastChangeVk = new Dictionary<ulong, DateTime>();
private Dictionary<string, string> InfoMessages = new Dictionary<string, string>() {
{
"CMD.RA.HELP", "ДОСТУПНЫЕ КОМАНДЫ:\n/ra add \"ссылка на профиль\" - прикрепить профиль ВК\n/ra remove - открепить профиль ВК"
}
, {
"CMD.RA.ADD.HELP", "Используйте /ra add \"ссылка на профиль\" чтобы прикрепить профиль ВК.\nПример: /ra add \"vk.com/durov\" или /ra add \"vk.com/id1\""
}
, {
"CMD.RA.REM.HELP", "Используйте /ra remove чтобы открепить свой профиль ВК от системы оповещений о рейде."
}
, {
"VK.PROFILE.ERROR.URL", "Вы указали неверную ссылку на профиль."
}
, {
"VK.PROFILE.FIND.DATABASE", "Поиск профиля в базе данных социальной сети ВК..."
}
, {
"VK.PROFILE.NOT.FOUND", "Указанный вами профиль не найден в базе данных социальной сети ВК.Проверьте правильность введенных данных."
}
, {
"VK.PROFILE.NOT.LINKED", "Вы не прикрепляли свой профиль ВК к системе оповещений о рейде."
}
, {
"VK.PROFILE.LINKED", "Ваш профиль ВК успешно прикреплен к системе оповещений о рейде."
}
, {
"VK.PROFILE.WAIT", "Вам нужно подождать {0}м.прежде чем вы сможете снова сменить ссылку на профиль ВК."
}
, {
"VK.PROFILE.SUCCESS.UNLINKED", "Ваш профиль ВК успешно отвязан от системы оповещений о рейде."
}
, {
"VK.PROFILE.NO.ACCESS", "Сервис ВК временно недоступен.Повторите запрос позже."
}
, {
"VK.PROFILE.BAN", "Ошибка отправки сообщения, указанный пользователь ВК в чёрном списке группы."
}
, {
"VK.PROFILE.NO.MESSAGE", "Ошибка отправки сообщения, указанный пользователь ВК запретил приём сообщений."
}
, {
"VK.PROFILE.SEND.TEST.MESSAGE", "Профиль \"{0} {1}\" в социальной сети ВК успешно найден, пытаемся отправить проверочное сообщение..."
}
, {
"VK.PROFILE.ERROR.REQUEST", "При выполнении запроса произошла ошибка, сообщите о проблемах администратору."
}
, {
"VK.PROFILE.ERROR.NOT.START.DIALOG", "Для подключения системы оповещений о рейде вам необходимо начать диалог с сообществом ВК.Зайдите в сообщество \"{0}\", нажмите на кнопку \"Написать сообщение\" и отправьте любое сообщение.После чего повторно используйте команду /ra add \"ссылка на профиль\""
}
, {
"VK.PROFILE.REQUEST.UNKNOWN.ERROR", "При выполнении запроса произошла неизвестная ошибка, попробуйте еще раз."
}
, {
"VK.PROFILE.REQUEST.WAIT.ERROR", "Ошибка ожидания выполнения запроса, попробуйте еще раз."
}
, {
"VK.PROFILE.REQUEST.BLOCK.USER", "Ошибка выполнения запроса, страница указанного пользователя ВК удалена или заблокирована."
}
, {
"NO.ACCESS", "У вас нет доступа к этой команде."
}
}
;
private Dictionary<int, ulong> GetUserInfoStatus = new Dictionary<int, ulong>();
private Dictionary<int, bool> SendMessageStatus = new Dictionary<int, bool>();
#endregion
#region HTTP
private ulong ProcessGetUserInfoRequest(BasePlayer player, string response) {
Dictionary<string, object> Response = JsonConvert.DeserializeObject<Dictionary<string, object>>(response, new KeyValuesConverter());
if (Response.ContainsKey("error")) {
Dictionary<string, object> data = Response["error"] as Dictionary<string, object>;
if (data.ContainsKey("error_code")) {
var code = Convert.ToString(data["error_code"]);
switch (code) {
case "1" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return 0;
case "5" : PrintError("Не верный Token ключ !");
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.ERROR.REQUEST"]);
return 0;
case "8" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return 0;
case "10" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return 0;
case "18" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.BLOCK.USER"]);
return 0;
case "113" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.NOT.FOUND"]);
return 0;
case "405" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.NO.ACCESS"]);
return 0;
case "503" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.NO.ACCESS"]);
return 0;
default : PrintWarning("Неизвестная ошибка c кодом: "+ code);
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return 0;
}
}
else {
PrintWarning("Ошибка возвращаемой структуры данных ВК");
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return 0;
}
}
if (Response.ContainsKey("response") && (Response["response"] as List<object>).Count() > 0) {
List<object> json = Response["response"] as List<object>;
var data = json[0] as Dictionary<string, object>;
ulong vkID = 0;
string firstName = "N/A";
string lastName = "N/A";
if (data.ContainsKey("id")) vkID = (ulong)Convert.ToInt64(data["id"]);
if (data.ContainsKey("first_name")) firstName = Convert.ToString(data["first_name"]);
if (data.ContainsKey("last_name")) lastName = Convert.ToString(data["last_name"]);
if (player != null) SendReply(player, string.Format(InfoMessages["VK.PROFILE.SEND.TEST.MESSAGE"], firstName, lastName));
return vkID;
}
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return 0;
}
private bool ProcessSendMessageRequest(BasePlayer player, string response, string success) {
Dictionary<string, object> Response = JsonConvert.DeserializeObject<Dictionary<string, object>>(response, new KeyValuesConverter());
if (Response.ContainsKey("error")) {
Dictionary<string, object> data = Response["error"] as Dictionary<string, object>;
if (data.ContainsKey("error_code")) {
var code = Convert.ToString(data["error_code"]);
switch (code) {
case "1" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return false;
case "5" : PrintError("Не верный Token ключ !");
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.ERROR.REQUEST"]);
return false;
case "8" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return false;
case "10" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return false;
case "405" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.NO.ACCESS"]);
return false;
case "503" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.NO.ACCESS"]);
return false;
case "900" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.BAN"]);
return false;
case "901" : if (player != null) SendReply(player, string.Format(InfoMessages["VK.PROFILE.ERROR.NOT.START.DIALOG"], configData.VkGroupName));
return false;
case "902" : if (player != null) SendReply(player, InfoMessages["VK.PROFILE.NO.MESSAGE"]);
return false;
default : PrintWarning("Неизвестная ошибка c кодом: "+ code);
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return false;
}
}
else {
PrintWarning("Ошибка возвращаемой структуры данных ВК");
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return false;
}
}
if (Response.ContainsKey("response")) {
if (player != null) SendReply(player, success);
return true;
}
if (player != null) SendReply(player, InfoMessages["VK.PROFILE.REQUEST.UNKNOWN.ERROR"]);
return false;
}
private void GetUserInfo(BasePlayer player, string VkUser, int ID) {
webrequest.Enqueue(string.Format(URL_GetUserInfo, VkUser, configData.Token), null, (code, response) => {
GetUserInfoStatus.Add(ID, ProcessGetUserInfoRequest(player, response));
}
, this);
}
private void SendMessage(BasePlayer player, ulong VkID, string Message, int ID, string success) {
webrequest.Enqueue(string.Format(URL_SendMessage, VkID, Message, ID, configData.Token), null, (code, response) => {
SendMessageStatus.Add(ID, ProcessSendMessageRequest(player, response, success));
}
, this);
}
#endregion
#region Main
private string TryParseVkNameOrID(string vk) {
string vk_ = vk.ToLower();
//попытка раcпарсить: https://vk.com/id353770849
if ((vk_.Contains("/id")||vk_.StartsWith("id")) && vk_.Length>3) {
string result = "";
int count = 0;
int startPos = 2;
if (vk_.Contains("/id")) startPos = vk_.IndexOf("/id")+3;
foreach(var ch in vk_) {
if (count >= startPos && "0123456789".IndexOf(ch)>=0) result += ch;
else if (count >= startPos && "0123456789".IndexOf(ch)<0) break;
count++;
}
if (string.IsNullOrEmpty(result)) return null;
return result;
}
else //попытка раcпарсить: https://vk.com/nimant_true
if (vk_.Contains(".com/") && vk_.Length>5) {
string result = "";
int count = 0;
int startPos = vk_.IndexOf(".com/")+5;
foreach(var ch in vk_) {
if (count >= startPos && "_0123456789abcdefghijklmnopqrstuvwxyz".IndexOf(ch)>=0) result += ch;
else if (count >= startPos && "_0123456789abcdefghijklmnopqrstuvwxyz".IndexOf(ch)<0) break;
count++;
}
if (string.IsNullOrEmpty(result)) return null;
return result;
}
else {
string result = "";
//попытка раcпарсить: 353770849 (указанный без префикса)
bool notID = false;
foreach(var ch in vk_) {
if ("0123456789".IndexOf(ch)>=0) result += ch;
else {
notID = true;
break;
}
}
if (!notID && !string.IsNullOrEmpty(result)) return result;
//попытка раcпарсить: nimant_true (указанный без префикса)
bool notName = false;
foreach(var ch in vk_) {
if ("_0123456789abcdefghijklmnopqrstuvwxyz".IndexOf(ch)>=0) result += ch;
else {
notName = true;
break;
}
}
if (!notName && !string.IsNullOrEmpty(result)) return result;
}
return null;
}
private void TryGetUserInfo(BasePlayer player, string Vk, int random = -1, int wait = 0) {
if (player == null) return;
if (wait>=100) {
SendReply(player, InfoMessages["VK.PROFILE.REQUEST.WAIT.ERROR"]);
return;
}
if (wait==0) {
SendReply(player, InfoMessages["VK.PROFILE.FIND.DATABASE"]);
int random_ = Rnd.Next(1, int.MaxValue);
while (GetUserInfoStatus.ContainsKey(random_)) random_ = Rnd.Next(1, int.MaxValue);
GetUserInfo(player, Vk, random_);
random = random_;
}
if (GetUserInfoStatus.ContainsKey(random)) {
if (GetUserInfoStatus[random] > 0) TrySendTestMsg(player, GetUserInfoStatus[random]);
GetUserInfoStatus.Remove(random);
}
else {
int wait_ = wait + 1;
int random_ = random;
timer.Once(0.1f, ()=> TryGetUserInfo(player, Vk, random_, wait_));
}
return;
}
private void TrySendTestMsg(BasePlayer player, ulong VkID, int random = -1, int wait = 0) {
if (player == null) return;
if (wait>=100) {
SendReply(player, InfoMessages["VK.PROFILE.REQUEST.WAIT.ERROR"]);
return;
}
if (wait==0) {
int random_ = Rnd.Next(1, int.MaxValue);
while (SendMessageStatus.ContainsKey(random_)) random_ = Rnd.Next(1, int.MaxValue);
SendMessage(player, VkID, string.Format(configData.TestMessage, configData.ServerName), random_, InfoMessages["VK.PROFILE.LINKED"]);
random = random_;
}
if (SendMessageStatus.ContainsKey(random)) {
if (SendMessageStatus[random]) {
if (!UsersVk.ContainsKey(player.userID)) UsersVk.Add(player.userID, new Dictionary<string, string>() {
{
"VK", VkID.ToString()
}
, {
"DT", ""
}
}
);
else UsersVk[player.userID] = new Dictionary<string, string>() {
{
"VK", VkID.ToString()
}
, {
"DT", ""
}
}
;
if (!LastChangeVk.ContainsKey(player.userID)) LastChangeVk.Add(player.userID, DateTime.Now);
else LastChangeVk[player.userID] = DateTime.Now;
SaveData();
SendMessageStatus.Remove(random);
}
}
else {
int wait_ = wait + 1;
int random_ = random;
timer.Once(0.1f, ()=> TrySendTestMsg(player, VkID, random_, wait_));
}
}
private void TrySendUnlinkMsg(BasePlayer player, ulong VkID, int random = -1, int wait = 0) {
if (player == null) return;
if (wait>=100) {
SendReply(player, InfoMessages["VK.PROFILE.REQUEST.WAIT.ERROR"]);
return;
}
if (wait==0) {
int random_ = Rnd.Next(1, int.MaxValue);
while (SendMessageStatus.ContainsKey(random_)) random_ = Rnd.Next(1, int.MaxValue);
SendMessage(player, VkID, string.Format(configData.MessageOff, configData.ServerName), random_, InfoMessages["VK.PROFILE.SUCCESS.UNLINKED"]);
random = random_;
}
if (SendMessageStatus.ContainsKey(random)) {
if (SendMessageStatus[random]) {
UsersVk.Remove(player.userID);
SaveData();
SendMessageStatus.Remove(random);
}
}
else {
int wait_ = wait + 1;
int random_ = random;
timer.Once(0.1f, ()=> TrySendUnlinkMsg(player, VkID, random_, wait_));
}
}
#endregion
#region Commands
[ChatCommand("ra")]
private void CmdChatRA(BasePlayer player, string command, string[] args) {
if (player==null) return;
if(!IsHavePerm(player)) {
SendReply(player, InfoMessages["NO.ACCESS"]);
return;
}
if (args?.Length>=1) {
if (args[0].ToLower() != "add" && args[0].ToLower() != "remove") {
SendReply(player, InfoMessages["CMD.RA.HELP"]);
return;
}
if (!((args.Length==2 && args[0].ToLower()=="add") || (args.Length==1 && args[0].ToLower()=="remove") || (args.Length==2 && args[0].ToLower()=="remove") )) {
if (args[0].ToLower()=="add") SendReply(player, InfoMessages["CMD.RA.ADD.HELP"]);
else SendReply(player, InfoMessages["CMD.RA.REM.HELP"]);
return;
}
if (args[0].ToLower()=="remove") {
if (UsersVk.ContainsKey(player.userID)) TrySendUnlinkMsg(player, (ulong)Convert.ToInt64((UsersVk[player.userID])["VK"]));
else SendReply(player, InfoMessages["VK.PROFILE.NOT.LINKED"]);
return;
}
if (LastChangeVk.ContainsKey(player.userID)) {
var min = DateTime.Now.Subtract(LastChangeVk[player.userID]).TotalMinutes;
if (min < configData.NextRegTime) {
SendReply(player, string.Format(InfoMessages["VK.PROFILE.WAIT"], (int)Math.Ceiling(min)));
return;
}
}
var vk = TryParseVkNameOrID(args[1]);
if (string.IsNullOrEmpty(vk)) {
SendReply(player, InfoMessages["VK.PROFILE.ERROR.URL"]);
return;
}
TryGetUserInfo(player, vk);
}
else {
SendReply(player, InfoMessages["CMD.RA.HELP"]);
return;
}
}
#endregion
#region Raid
private void OnEntityDeath(BaseCombatEntity entity, HitInfo hitInfo) {
if (hitInfo == null || entity == null) return;
if (hitInfo.Initiator != null) {
BasePlayer initiator = hitInfo.Initiator.ToPlayer();
if (initiator is BasePlayer) if (IsEntityRaidable(entity)) CheckAndSendInfo(initiator, entity);
}
}
public bool IsEntityRaidable(BaseCombatEntity entity) {
if (entity is BuildingBlock) if ((entity as BuildingBlock).grade.ToString() == "Twigs") return false;
string prefabName = entity.ShortPrefabName;
foreach (var p in configData.InfoBlocks) if (p.Key==prefabName) return true;
return false;
}
private void CheckAndSendInfo(BasePlayer raider, BaseCombatEntity entity) {
foreach(var player in GetRaidedPlayers(entity)) {
if (IsHavePerm(player) && UsersVk.ContainsKey(player)) {
if (!string.IsNullOrEmpty((UsersVk[player])["DT"]) && DateTime.Now.Subtract(Convert.ToDateTime((UsersVk[player])["DT"])).TotalMinutes < configData.NextSendTime) continue;
if (raider.userID==player) continue;
if (configData.NoInfoIfFriends && Friends != null) {
var areFriends = Friends.CallHook("AreFriends", player, raider.userID);
if (areFriends != null && areFriends is bool) if (Convert.ToBoolean(areFriends)==true) continue;
}
string obj = configData.DefaultBlock;
if (configData.InfoBlocks.ContainsKey(entity.ShortPrefabName)) obj = configData.InfoBlocks[entity.ShortPrefabName];
if (BasePlayer.activePlayerList.ToList().Exists(x=>x.userID==player)) SendReply(BasePlayer.activePlayerList.ToList().Find(x=>x.userID==player), string.Format(configData.SrvRaidMessage, raider.displayName + " ("+raider.UserIDString+")", obj));
else {
int random_ = Rnd.Next(1, int.MaxValue);
while (SendMessageStatus.ContainsKey(random_)) random_ = Rnd.Next(1, int.MaxValue);
SendMessage(null, (ulong)Convert.ToInt64((UsersVk[player])["VK"]), string.Format(configData.VkRaidMessage, raider.displayName + " ("+raider.UserIDString+")", configData.ServerName, obj), random_, null);
}
(UsersVk[player])["DT"] = DateTime.Now.ToString();
SaveData();
}
}
}
private List<ulong> GetRaidedPlayers(BaseCombatEntity entity) {
List<ulong> result = new List<ulong>();
if (entity.OwnerID>0) result.Add(entity.OwnerID);
var bp = entity.GetBuildingPrivilege();
if (bp == null) return result;
foreach(var player in bp.authorizedPlayers) {
if (!result.Contains(player.userid)) result.Add(player.userid);
}
return result;
}
#endregion
#region Common
private bool IsHavePerm(BasePlayer player) {
if (permission.UserHasPermission(player.UserIDString, AllowPermission)) return true;
return false;
}
private bool IsHavePerm(ulong userID) {
if (permission.UserHasPermission(userID.ToString(), AllowPermission)) return true;
return false;
}
#endregion
#region Hooks
private void Loaded() {
LoadVariables();
LoadData();
permission.RegisterPermission(AllowPermission, this);
}
#endregion
#region Config
private ConfigData configData;
private class ConfigData {
public string Token;
public string VkGroupName;
public string ServerName;
public int NextSendTime;
public double NextRegTime;
public string TestMessage;
public string VkRaidMessage;
public string SrvRaidMessage;
public string MessageOff;
public bool NoInfoIfFriends;
public string DefaultBlock;
public Dictionary<string, string> InfoBlocks;
}
private void LoadVariables() => configData = Config.ReadObject<ConfigData>();
protected override void LoadDefaultConfig() {
var config = new ConfigData {
Token = "", VkGroupName = "Test", ServerName = "Test", NextSendTime = 15, NextRegTime = 1, NoInfoIfFriends = true, TestMessage = "РЕЙД БОТ:\nВы успешно подключили систему оповещений о рейде на сервере {0}.", VkRaidMessage = "РЕЙД БОТ:\nИгрок {0} на сервере {1} разрушил ваше строение {2}.", SrvRaidMessage = "<color=#FA8072>РЕЙД БОТ:</color>\nИгрок <color=yellow>{0}</color> разрушил ваше строение <color=yellow>{1}</color>.", MessageOff = "РЕЙД БОТ:\nВы успешно отключили систему оповещений о рейде на сервере {0}.", DefaultBlock = "Строительный блок", InfoBlocks = new Dictionary<string, string>() {
{
"floor.grill", "Решетчатый настил"
}
, {
"door.hinged.toptier", "Бронированная дверь"
}
, {
"door.double.hinged.toptier", "Двойная бронированная дверь"
}
, {
"gates.external.high.stone", "Высокие внешние каменные ворота"
}
, {
"wall.external.high.stone", "Высокая внешняя каменная стена"
}
, {
"gates.external.high.wood", "Высокие внешние деревянные ворота"
}
, {
"wall.external.high", "Высокая внешняя деревянная стена"
}
, {
"floor.ladder.hatch", "Люк с лестницей"
}
, {
"shutter.metal.embrasure.a", "Металлическая горизонтальная бойница"
}
, {
"shutter.metal.embrasure.b", "Металлическая вертикальная бойница"
}
, {
"wall.window.bars.metal", "Металлические оконные решетки"
}
, {
"wall.frame.cell.gate", "Тюремная дверь"
}
, {
"wall.frame.cell", "Тюремная решетка"
}
, {
"wall.window.bars.toptier", "Укрепленные оконные решетки"
}
, {
"door.hinged.metal", "Металлическая дверь"
}
, {
"door.double.hinged.metal", "Двойная металлическая дверь"
}
, {
"door.hinged.wood", "Деревянная дверь"
}
, {
"door.double.hinged.wood", "Двойная деревянная дверь"
}
, {
"wall.frame.garagedoor", "Гаражная дверь"
}
, {
"wall.frame.shopfront.metal", "Металлическая витрина магазина"
}
, {
"foundation.triangle", "Треугольный фундамент"
}
, {
"foundation.steps", "Ступеньки для фундамента"
}
, {
"foundation", "Фундамент"
}
, {
"wall.frame", "Настенный каркас"
}
, {
"wall.window", "Оконный проём"
}
, {
"wall.doorway", "Дверной проём"
}
, {
"wall", "Стена"
}
, {
"floor.frame", "Потолочный каркас"
}
, {
"floor.triangle", "Треугольный пол"
}
, {
"floor", "Пол"
}
, {
"roof", "Крыша"
}
, {
"block.stair.lshape", "L-образная лестница"
}
, {
"block.stair.ushape", "U-образная лестница"
}
, {
"pillar", "Опора"
}
, {
"wall.low", "Низкая стена"
}
}
}
;
SaveConfig(config);
timer.Once(0.1f, ()=>SaveConfig(configData));
}
private void SaveConfig(ConfigData config) => Config.WriteObject(config, true);
private void LoadData() => UsersVk = Interface.GetMod().DataFileSystem.ReadObject<Dictionary<ulong, Dictionary<string, string>>>("RaidAlertData");
private void SaveData() => Interface.GetMod().DataFileSystem.WriteObject("RaidAlertData", UsersVk);
#endregion
}
}