C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
//#define DEBUG
// Requires: TranslationAPI
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using ProtoBuf;
namespace Oxide.Plugins
{
[Info("Chat Translator", "Mr.Blue", "2.2.2")]
[Description("Translates chat messages to each player's language preference or server default")]
public class ChatTranslator : CovalencePlugin
{
#region Configuration
private Configuration config;
private class Configuration
{
[JsonProperty("Force default server language")]
public bool ForceServerDefault = false;
[JsonProperty("Log translated chat messages")]
public bool LogChatMessages = false;
[JsonProperty("Show original and translation")]
public bool ShowBothMessages = false;
[JsonProperty("Translate message for sender")]
public bool TranslateForSender = false;
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
}
protected override void LoadDefaultConfig() => config = new Configuration();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<Configuration>();
if (config == null)
{
throw new JsonException();
}
if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
{
LogWarning("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
}
catch
{
LogWarning($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig()
{
LogWarning($"Configuration changes saved to {Name}.json");
Config.WriteObject(config, true);
}
#endregion Configuration
#region Chat Translation
[PluginReference]
private readonly Plugin TranslationAPI, BetterChat, BetterChatFilter, BetterChatMute, ChatFilter;
private void Translate(string message, string targetId, string senderId, Action<string> callback)
{
// Get language code to translate to
string langTo = "en";
if (!message.Equals("Translation") && config.ForceServerDefault && CultureInfo.GetCultureInfo(lang.GetServerLanguage()) != null)
{
langTo = lang.GetServerLanguage();
}
else
{
langTo = lang.GetLanguage(targetId);
}
// Get language code to translate from
string langFrom = lang.GetLanguage(senderId) ?? "auto";
#if DEBUG
LogWarning($"To ({targetId}): {langTo}, From ({senderId}): {langFrom}");
#endif
// Translate the message
TranslationAPI.Call("Translate", message, langTo, langFrom, callback);
}
private void TranslateChat(IPlayer target, IPlayer sender, string message, int channel = 0)
{
if (sender.Equals(target) && !config.TranslateForSender)
{
ProcessMessage(sender, sender, message, message, channel);
return;
}
Action<string> callback = translation =>
{
if (config.ShowBothMessages && !message.Equals(translation))
{
Action<string> prefixCallback = prefixResponse =>
{
translation = $"{message}\n{prefixResponse}: {translation}";
ProcessMessage(target, sender, translation, message, channel);
};
Translate("Translation", sender.Id, sender.Id, prefixCallback);
}
else
{
ProcessMessage(target, sender, translation, message, channel);
}
};
Translate(message, target.Id, sender.Id, callback);
}
private void ProcessMessage(IPlayer target, IPlayer sender, string translation, string original, int channel = 0)
{
if (target == null || !target.IsConnected)
{
return;
}
if (Interface.Oxide.CallHook("OnTranslatedChat", sender, translation, original, channel) != null)
{
return;
}
// Rust colors: Admin/moderator = #aaff55, Developer = #ffaa55, Player = #55aaff
string prefixColor = sender.IsAdmin ? "#aaff55" : "#55aaff";
if (config.LogChatMessages)
{
LogToFile("log", translation, this);
#if RUST
Log($"[{(ConVar.Chat.ChatChannel)channel}] {sender.Name}: {translation}");
// Create RCON broadcast for Rust
Facepunch.RCon.Broadcast(Facepunch.RCon.LogType.Chat, new ConVar.Chat.ChatEntry
{
Channel = (ConVar.Chat.ChatChannel)channel,
Message = translation,
UserId = sender.Id,
Username = sender.Name,
Color = prefixColor,
Time = Facepunch.Math.Epoch.Current
});
#else
Log($"{sender.Name}: {translation}");
#endif
}
string formatted;
if (BetterChat != null && BetterChat.IsLoaded)
{
formatted = covalence.FormatText(BetterChat.Call<string>("API_GetFormattedMessage", sender, translation));
}
else
{
formatted = $"{covalence.FormatText($"[{prefixColor}]{sender.Name}[/#]")}: {translation}";
}
#if RUST
switch (channel)
{
case 1: // Team chat
BasePlayer basePlayer = sender.Object as BasePlayer;
RelationshipManager.PlayerTeam team = basePlayer.Team;
if (team != null && team.members.Count != 0)
{
// Rust companion app support
uint current = (uint)Facepunch.Math.Epoch.Current;
ulong targetId = ulong.Parse(target.Id);
CompanionServer.Server.TeamChat.Record(team.teamID, targetId, target.Name, translation, prefixColor, current);
ProtoBuf.AppBroadcast appBroadcast = Facepunch.Pool.Get<ProtoBuf.AppBroadcast>();
appBroadcast.teamMessage = Facepunch.Pool.Get<ProtoBuf.AppNewTeamMessage>();
appBroadcast.teamMessage.message = Facepunch.Pool.Get<ProtoBuf.AppTeamMessage>();
appBroadcast.ShouldPool = false;
AppTeamMessage appChatMessage = appBroadcast.teamMessage.message;
appChatMessage.steamId = targetId;
appChatMessage.name = target.Name;
appChatMessage.message = translation;
appChatMessage.color = prefixColor;
appChatMessage.time = current;
CompanionServer.Server.Broadcast(new CompanionServer.PlayerTarget(targetId), appBroadcast);
appBroadcast.ShouldPool = true;
appBroadcast.Dispose();
}
break;
}
#endif
#if RUST
target.Command("chat.add", channel, sender.Id, formatted);
#else
target.Message(formatted);
#endif
}
#endregion Chat Translation
#region Chat Handling
private object HandleChat(IPlayer sender, string message, int channel = 0, List<string> blockedReceivers = null)
{
if (sender == null || string.IsNullOrEmpty(message))
{
return null;
}
if (ChatFilter != null && ChatFilter.IsLoaded)
{
if (ChatFilter.Call<bool>("ContainsAdvertising"))
{
return true; // TODO: Support filtering, not just blocking
}
}
if (BetterChatMute != null && BetterChatMute.IsLoaded)
{
if (BetterChatMute.Call<bool>("API_IsMuted", sender))
{
return true;
}
}
if (BetterChatFilter != null && BetterChatFilter.IsLoaded)
{
Dictionary<string, object> filteredData = BetterChatFilter.Call<Dictionary<string, object>>("Filter", sender, message);
if (filteredData != null && filteredData.Count > 0)
{
message = filteredData["Message"].ToString();
}
}
switch (channel)
{
#if RUST
case 1: // Team chat
BasePlayer basePlayer = sender.Object as BasePlayer;
RelationshipManager.PlayerTeam team = basePlayer.Team;
if (team != null && team.members.Count != 0)
{
foreach (ulong member in team.members)
{
BasePlayer targetBasePlayer = RelationshipManager.FindByID(member);
if (targetBasePlayer != null && targetBasePlayer.IsConnected)
{
if (blockedReceivers == null || !blockedReceivers.Contains(targetBasePlayer.IPlayer.Id))
{
TranslateChat(targetBasePlayer.IPlayer, sender, message, channel);
}
}
}
}
break;
#endif
default:
foreach (IPlayer target in players.Connected)
{
if (blockedReceivers == null || !blockedReceivers.Contains(target.Id))
{
TranslateChat(target, sender, message, channel);
}
}
break;
}
return true;
}
private object OnBetterChat(Dictionary<string, object> data)
{
HandleChat(data["Player"] as IPlayer, data["Message"] as string, (int)data["ChatChannel"], data["BlockedReceivers"] as List<string>);
data["CancelOption"] = 2;
return data;
}
#if RUST
private object OnPlayerChat(BasePlayer basePlayer, string message, ConVar.Chat.ChatChannel channel)
{
if (BetterChat == null || !BetterChat.IsLoaded)
{
return HandleChat(basePlayer.IPlayer, message, (int)channel);
}
return null;
}
#else
private object OnUserChat(IPlayer player, string message)
{
if (BetterChat == null || !BetterChat.IsLoaded)
{
return HandleChat(player, message);
}
return null;
}
#endif
#endregion Chat Handling
}
}