C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Ext.Discord;
using Oxide.Ext.Discord.Attributes;
using Oxide.Ext.Discord.DiscordObjects;
namespace Oxide.Plugins
{
[Info("Discord Connect", "Iv Misticos", "1.0.11")]
[Description("Discord account connection with API")]
public class DiscordConnect : CovalencePlugin
{
#region Variables
[DiscordClient]
private DiscordClient _client = null;
private static List<KeyInfo> _keys = new List<KeyInfo>();
private static List<PlayerData> _data = new List<PlayerData>();
private static DiscordConnect _ins;
private static Time _time = GetLibrary<Time>();
#endregion
#region Configuration
private static Configuration _config;
private class Configuration
{
[JsonProperty(PropertyName = "Discord Bot Token")]
public string Token = string.Empty;
[JsonProperty(PropertyName = "Channel ID For Authentication Log")]
public string AuthLogChannel = string.Empty;
[JsonProperty(PropertyName = "Enable Bot Status")]
public bool EnableStatus = true;
[JsonProperty(PropertyName = "Bot Status")]
public string Status = "Send me your code";
[JsonProperty(PropertyName = "Group To Assign Once Connected")]
public string GroupConnected = "discord-connected";
[JsonProperty(PropertyName = "Group To Revoke Once Left")]
public string GroupLeft = "discord-connected";
[JsonProperty(PropertyName = "Delete Data On Discord Leave")]
public bool DeleteData = false;
[JsonProperty(PropertyName = "Allow Data Overwrite")]
public bool OverwriteData = true;
[JsonProperty(PropertyName = "Chat Prefix")]
public string Prefix = "Auth";
[JsonProperty(PropertyName = "Auth Command")]
public string Command = "auth";
[JsonProperty(PropertyName = "Code Lifetime")]
public string CodeLifetime = "15m";
[JsonIgnore] public uint ParsedCodeLifetime;
[JsonProperty(PropertyName = "Code Length")]
public int CodeLength = 6;
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null) throw new Exception();
}
catch
{
PrintError("Your configuration file contains an error.Using default configuration values.");
LoadDefaultConfig();
}
}
protected override void SaveConfig() => Config.WriteObject(_config);
protected override void LoadDefaultConfig() => _config = new Configuration();
#endregion
#region Work with Data
// Temporary
private class KeyInfo
{
public IPlayer Player;
public string Key;
public uint ValidUntil;
public static KeyInfo FindByID(string userId)
{
for (var i = 0; i < _keys.Count; i++)
{
if (_keys[i].Player.Id == userId)
return _keys[i];
}
return null;
}
public static KeyInfo FindByKey(string key)
{
for (var i = 0; i < _keys.Count; i++)
{
if (_keys[i].Key == key)
return _keys[i];
}
return null;
}
public void ExpireMessage()
{
Player.Reply(GetMsg("Code Expired", Player.Id), _config.Prefix);
}
public void ResetValidUntil()
{
ValidUntil = _time.GetUnixTimestamp() + _config.ParsedCodeLifetime;
}
}
// Non-Temporary
private class PlayerData
{
public string GameId;
public string DiscordId;
public static PlayerData FindByGame(string id)
{
for (var i = 0; i < _data.Count; i++)
{
if (_data[i].GameId == id)
return _data[i];
}
return null;
}
public static PlayerData FindByDiscord(string id)
{
for (var i = 0; i < _data.Count; i++)
{
if (_data[i].DiscordId == id)
return _data[i];
}
return null;
}
}
private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, _data);
private void LoadData()
{
try
{
_data = Interface.Oxide.DataFileSystem.ReadObject<List<PlayerData>>(Name);
}
catch (Exception e)
{
PrintError(e.ToString());
}
if (_data == null) _data = new List<PlayerData>();
}
#endregion
#region Hooks
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
{"Code Generation", "Here is your code: {code}"},
{"Code Expired", "Your code has expired!"},
{"Authenticated", "Thank you for authenticating your account!"},
{"Already Authenticated", "You have already authenticated your account, no need to do it again!"},
{"Unable To Find Code", "Sorry, we couldn't find your code.Try to authenticate again."},
{"Log", "{discordName} ({discordId}) has authenticated his account to in-game {gameName} ({gameId})"},
{"Discord Overwrite", "Your old connection with this Discord account will be overwritten!"},
{"In-Game Overwrite", "Your old connection with this in-game account will be overwritten!"},
{"Authentication Denied By Plugin", "Your authentication was denied by a plugin"}
}, this);
}
private void Init()
{
_ins = this;
var parsed = ConvertToSeconds(_config.CodeLifetime, out _config.ParsedCodeLifetime);
if (!parsed)
{
PrintError("Unable to parse code lifetime");
_config.ParsedCodeLifetime = 60;
}
if (!string.IsNullOrEmpty(_config.Token))
{
Discord.CreateClient(this, _config.Token);
if (_config.EnableStatus)
{
_client?.UpdateStatus(new Presence
{
Game = new Ext.Discord.DiscordObjects.Game
{
Name = _config.Status,
Type = ActivityType.Game
}
});
}
}
AddCovalenceCommand(_config.Command, nameof(CommandAuth));
LoadData();
timer.Every(1f, DoExpiration);
}
private void OnServerSave() => SaveData();
private void Unload()
{
SaveData();
Discord.CloseClient(_client);
}
#region Discord Hooks
// Called when a member leaves the Discord server
private void Discord_MemberRemoved(GuildMember member)
{
// No user found
var found = PlayerData.FindByDiscord(member?.user?.id);
if (found == null)
return;
if (!string.IsNullOrEmpty(_config.GroupLeft))
{
var player = players.FindPlayerById(found.GameId);
player?.RemoveFromGroup(_config.GroupLeft);
}
Interface.Oxide.CallHook("OnDiscordAuthLeave", found.GameId, found.DiscordId);
// Disabled in config
if (!_config.DeleteData)
return;
_data.Remove(found);
}
// Called when a message is created on the Discord server
private void Discord_MessageCreate(Message message)
{
// Bot-check
if (message.author.bot == true)
return;
Channel.GetChannel(_client, message.channel_id, channel =>
{
// DM-check
if (channel.type != ChannelType.DM)
return;
// No code found
var info = KeyInfo.FindByKey(message.content);
if (info == null)
{
channel.CreateMessage(_client, GetMsg("Unable To Find Code"));
return;
}
var canAuthenticate = Interface.Oxide.CallHook("CanDiscordAuthenticate", info.Player.Id, message.author.id);
if (canAuthenticate is bool && !(bool) canAuthenticate)
{
channel.CreateMessage(_client, GetMsg("Authentication Denied By Plugin", info.Player.Id));
return;
}
// Already authenticated-check
var data1 = PlayerData.FindByGame(info.Player.Id);
var data2 = PlayerData.FindByDiscord(message.author.id);
if (!_config.OverwriteData && (data1 != null || data2 != null))
{
channel.CreateMessage(_client, GetMsg("Already Authenticated"));
}
else
{
if (data1 != null)
{
channel.CreateMessage(_client, GetMsg("In-Game Overwrite", info.Player.Id));
Interface.Oxide.CallHook("OnDiscordAuthOverwrite", data1.GameId, info.Player.Id, data1.DiscordId,
message.author.id);
_data.Remove(data1);
}
if (data2 != null)
{
channel.CreateMessage(_client, GetMsg("Discord Overwrite", info.Player.Id));
Interface.Oxide.CallHook("OnDiscordAuthOverwrite", data2.GameId, info.Player.Id, data2.DiscordId,
message.author.id);
_data.Remove(data2);
}
}
// Adds to data file
_data.Add(new PlayerData
{
DiscordId = message.author.id,
GameId = info.Player.Id
});
channel.CreateMessage(_client, GetMsg("Authenticated", info.Player.Id));
if (!string.IsNullOrEmpty(_config.AuthLogChannel))
{
Channel.GetChannel(_client, _config.AuthLogChannel, logChannel =>
{
logChannel?.CreateMessage(_client,
new StringBuilder(GetMsg("Log")).Replace("{discordName}", message.author.username).Replace("{discordId}", message.author.id).Replace("{gameId}", info.Player.Id).Replace("{gameName}", info.Player.Name).ToString());
});
}
if (!string.IsNullOrEmpty(_config.GroupConnected))
{
info.Player.AddToGroup(_config.GroupConnected);
}
Interface.Oxide.CallHook("OnDiscordAuthenticate", info.Player.Id, message.author.id);
});
}
#endregion
#endregion
#region Commands
private void CommandAuth(IPlayer player, string command, string[] args)
{
// Already authenticated-check
if (PlayerData.FindByGame(player.Id) != null)
{
player.Reply(GetMsg("Already Authenticated", player.Id), _config.Prefix);
return;
}
var info = KeyInfo.FindByID(player.Id);
var code = GenerateCode();
if (info == null)
{
info = new KeyInfo();
_keys.Add(info);
}
info.Key = code;
info.Player = player;
info.ResetValidUntil();
player.Reply(GetMsg("Code Generation", player.Id).Replace("{code}", code));
}
#endregion
#region API
private string GetDiscordOf(string id) => PlayerData.FindByGame(id)?.DiscordId;
private string GetGameOf(string id) => PlayerData.FindByDiscord(id)?.GameId;
#endregion
#region Code Expiration
private void DoExpiration()
{
var time = _time.GetUnixTimestamp();
for (var i = _keys.Count - 1; i >= 0; i--)
{
var key = _keys[i];
if (key.ValidUntil > time) continue;
key.ExpireMessage();
_keys.RemoveAt(i);
}
}
#endregion
#region Helpers
private string GenerateCode()
{
var builder = new StringBuilder();
while (builder.Length < _config.CodeLength)
{
builder.Append(Guid.NewGuid().ToString().Replace("-", ""));
}
builder.Length = _config.CodeLength;
return builder.ToString();
}
private static string GetMsg(string key, string userId = null) => _ins.lang.GetMessage(key, _ins, userId);
#endregion
#region Parsers
private static readonly Regex RegexStringTime = new Regex(@"(\d+)([dhms])", RegexOptions.Compiled);
private static bool ConvertToSeconds(string time, out uint seconds)
{
seconds = 0;
if (time == "0" || string.IsNullOrEmpty(time)) return true;
var matches = RegexStringTime.Matches(time);
if (matches.Count == 0) return false;
for (var i = 0; i < matches.Count; i++)
{
var match = matches[i];
// ReSharper disable once SwitchStatementMissingSomeCases
switch (match.Groups[2].Value)
{
case "d":
{
seconds += uint.Parse(match.Groups[1].Value) * 24 * 60 * 60;
break;
}
case "h":
{
seconds += uint.Parse(match.Groups[1].Value) * 60 * 60;
break;
}
case "m":
{
seconds += uint.Parse(match.Groups[1].Value) * 60;
break;
}
case "s":
{
seconds += uint.Parse(match.Groups[1].Value);
break;
}
}
}
return true;
}
#endregion
}
}