RustServer/plugins/DiscordAuth.cs

514 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Ext.Discord;
using Oxide.Ext.Discord.Attributes;
using Oxide.Ext.Discord.DiscordObjects;
namespace Oxide.Plugins
{
[Info("Discord Auth", "Tricky", "1.1.3")]
[Description("Allows players to connect their discord account with steam")]
public class DiscordAuth : CovalencePlugin
{
#region Defined
[DiscordClient]
private DiscordClient Client;
#endregion
#region Config
Configuration config;
class Configuration
{
[JsonProperty(PropertyName = "Settings")]
public Settings Info = new Settings();
[JsonProperty(PropertyName = "Authentication Code")]
public AuthCode Code = new AuthCode();
public class Settings
{
[JsonProperty(PropertyName = "Bot Token")]
public string BotToken = string.Empty;
[JsonProperty(PropertyName = "Oxide Group")]
public string Group = "authenticated";
[JsonProperty(PropertyName = "Enable Logging")]
public bool Log = false;
[JsonProperty(PropertyName = "Auth Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public string[] AuthCommands = new string[] { "auth", "authenticate" };
[JsonProperty(PropertyName = "Deauth Commands", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public string[] DeauthCommands = new string[] { "deauth", "deauthenticate" };
[JsonProperty(PropertyName = "Discord Roles to Assign", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<string> Roles = new List<string>()
{
"Authenticated"
};
[JsonProperty(PropertyName = "Revoke Oxide Group on Discord Leave")]
public bool RemovefromGroup = true;
[JsonProperty(PropertyName = "Deauthenticate on Discord Leave")]
public bool Deauthenticate = false;
[JsonProperty(PropertyName = "Chat Prefix")]
public string ChatPrefix = "[#1874CD](Auth)[/#]:";
}
public class AuthCode
{
[JsonProperty(PropertyName = "Code Lifetime (minutes)")]
public int CodeLifetime = 60;
[JsonProperty(PropertyName = "Code Length")]
public int CodeLength = 5;
[JsonProperty(PropertyName = "Lowercase")]
public bool Lowercase = false;
}
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<Configuration>();
if (config == null) throw new Exception();
}
catch
{
Config.WriteObject(config, false, $"{Interface.Oxide.ConfigDirectory}/{Name}.jsonError");
PrintError("The configuration file contains an error and has been replaced with a default config.\n" +
"The error configuration file was saved in the .jsonError extension");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig() => config = new Configuration();
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Classes and Stored Data
private enum MemberRole { Add, Remove }
private const string authPerm = "discordauth.auth";
private const string deauthPerm = "discordauth.deauth";
private Data data;
private class Data
{
public Dictionary<string, string> Players = new Dictionary<string, string>();
}
private Dictionary<string, string> Codes = new Dictionary<string, string>();
#endregion
#region Lang
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Code Generation"] = "Here is your code: [#1874CD]{0}[/#]\nJoin our [#EE3B3B]Discord[/#] and PM the code to the Discord Bot",
["Code Expired"] = "Your code has [#EE3B3B]Expired![/#]",
["Authenticated"] = "Thank you for authenticating your account",
["Game-Deauthenticated"] = "Successfully deauthenticated your account",
["Discord-Deauthenticated"] = "You have been deauthenticated from {0}",
["Already Authenticated"] = "You have already [#1874CD]authenticated[/#] your account, no need to do it again",
["Not Authenticated"] = "You are not authenticated",
["Group Revoked"] = "Your '{0}' Group has been revoked! Join the server back to achieve it",
["Group Granted"] = "Granted '{0}' Group and the discord roles for joining {1} back",
["Unable to find code"] = "Sorry, we couldn't find your code, please try to authenticate again, If you haven't generated a code, please type /auth in-game",
["No Permission"] = "You dont have permission to use this command"
}, this);
}
#endregion
#region Commands
private void AuthCommand(IPlayer player, string command, string[] args)
{
// No Permission
if (!player.HasPermission(authPerm))
{
Message(player, "No Permission");
return;
}
// Already authenticated-check
if (data.Players.ContainsKey(player.Id))
{
Message(player, "Already Authenticated");
return;
}
// Sends the code if already exist to prevent duplication
if (Codes.ContainsKey(player.Id))
{
Message(player, "Code Generation", Codes[player.Id]);
return;
}
// Adds a random code and send it to the player if doesn't already exist
var code = GenerateCode(config.Code.CodeLength, config.Code.Lowercase);
Codes.Add(player.Id, code);
Message(player, "Code Generation", code);
// Code Expiration Function
timer.In(config.Code.CodeLifetime * 60, () =>
{
if (Codes.ContainsKey(player.Id))
{
Codes.Remove(player.Id);
if (player != null) Message(player, "Code Expired");
}
});
}
private void DeauthCommand(IPlayer player, string command, string[] args)
{
// No Permission
if (!player.HasPermission(deauthPerm))
{
Message(player, "No Permission");
return;
}
if (!data.Players.ContainsKey(player.Id))
{
Message(player, "Not Authenticated");
return;
}
Oxide.Ext.Discord.DiscordObjects.User.GetUser(Client, data.Players[player.Id], user => HandleRoles(user, MemberRole.Remove));
Deauthenticate(player.Id, data.Players[player.Id]);
Message(player, "Game-Deauthenticated");
}
#endregion
#region Oxide Hooks
private void Init()
{
data = Interface.Oxide.DataFileSystem.ReadObject<Data>(Name);
CreateClient();
AddCovalenceCommand(config.Info.AuthCommands, nameof(AuthCommand));
AddCovalenceCommand(config.Info.DeauthCommands, nameof(DeauthCommand));
permission.CreateGroup(config.Info.Group, config.Info.Group, 0);
permission.RegisterPermission(authPerm, this);
permission.RegisterPermission(deauthPerm, this);
if (!config.Info.Deauthenticate && !config.Info.RemovefromGroup)
Unsubscribe(nameof(Discord_MemberRemoved));
if (!config.Info.RemovefromGroup)
Unsubscribe(nameof(Discord_MemberAdded));
if (!config.Info.Log)
Unsubscribe(nameof(DiscordSocket_Initalized));
}
private void Unload()
{
CloseClient();
SaveData();
}
private void OnServerSave() => SaveData();
#endregion
#region Discord Hooks
// 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, dm =>
{
// DM-check
if (dm.type != ChannelType.DM)
return;
// Length-check
if (message.content.Length != config.Code.CodeLength)
return;
// No code found
if (!Codes.ContainsValue(message.content))
{
dm.CreateMessage(Client, GetEmbed(Formatter.ToPlaintext(Lang("Unable to find code")), 16098851));
return;
}
// Already authenticated-check
if (data.Players.ContainsValue(message.author.id))
{
dm.CreateMessage(Client, GetEmbed(Formatter.ToPlaintext(Lang("Already Authenticated")), 4886754));
return;
}
Codes.Keys.ToList().ForEach(playerId =>
{
if (Codes[playerId] != message.content)
return;
Authenticate(playerId, message.author.id);
});
HandleRoles(message.author, MemberRole.Add);
dm.CreateMessage(Client, GetEmbed(Formatter.ToPlaintext(Lang("Authenticated")), 11523722));
});
}
// Called when a member leaves the Discord server
private void Discord_MemberRemoved(GuildMember member)
{
var steamId = API_GetSteam(member.user.id);
// No user found
if (steamId == null)
return;
if (config.Info.Deauthenticate)
{
Deauthenticate(steamId, member.user.id);
member.user.CreateDM(Client, dm => dm.CreateMessage(Client, GetEmbed(Formatter.ToPlaintext(Lang("Discord-Deauthenticated", steamId, Client.DiscordServer.name)), 9905970)));
return;
}
if (config.Info.RemovefromGroup)
{
permission.RemoveUserGroup(steamId, config.Info.Group);
member.user.CreateDM(Client, dm => dm.CreateMessage(Client, GetEmbed(Formatter.ToPlaintext(Lang("Group Revoked", steamId, config.Info.Group)), 16098851)));
}
}
// Called when a user joins the discord server
private void Discord_MemberAdded(GuildMember member)
{
var steamId = API_GetSteam(member.user.id);
// No user found
if (steamId == null)
return;
HandleRoles(member.user, MemberRole.Add);
permission.AddUserGroup(steamId, config.Info.Group);
member.user.CreateDM(Client, dm => dm.CreateMessage(Client, GetEmbed(Formatter.ToPlaintext(Lang("Group Granted", steamId, config.Info.Group, Client.DiscordServer.name)), 4886754)));
}
// Called when the client is created, and the plugin can use it
private void DiscordSocket_Initalized(DiscordClient client) => Puts("Discord Initalized!");
#endregion
#region Core
private void Authenticate(string steamId, string discordId)
{
if (Interface.Oxide.CallHook("OnAuthenticate", steamId, discordId) != null)
return;
data.Players.Add(steamId, discordId);
permission.AddUserGroup(steamId, config.Info.Group);
}
private void Deauthenticate(string steamId, string discordId)
{
if (Interface.Oxide.CallHook("OnDeauthenticate", steamId, discordId) != null)
return;
data.Players.Remove(steamId);
permission.RemoveUserGroup(steamId, config.Info.Group);
}
private void HandleRoles(Oxide.Ext.Discord.DiscordObjects.User user, MemberRole memberRole)
{
config.Info.Roles.ForEach(roleName =>
{
var role = GetRoleByName(roleName);
if (role == null)
{
PrintError($"Unable to find '{roleName}'");
return;
}
switch (memberRole)
{
case MemberRole.Add:
Client.DiscordServer.AddGuildMemberRole(Client, user, role);
break;
case MemberRole.Remove:
Client.DiscordServer.RemoveGuildMemberRole(Client, user, role);
break;
}
});
}
#endregion
#region API
//private bool API_Authenticate(string steamId, string discordId, bool addtoGroup = true, bool callHook = true)
//{
// if (!data.Players.ContainsKey(steamId) || !data.Players.ContainsValue(discordId))
// {
// if(addtoGroup)
// permission.AddUserGroup(steamId, config.Info.Group);
// if(callHook)
// Interface.Oxide.CallHook("OnAuthenticate", steamId, discordId);
// data.Players.Add(steamId, discordId);
// return true;
// }
// return false;
//}
//private bool API_Deauthenticate(string Id, bool removefromGroup = true, bool callHook = true)
//{
// // If Id is steamId
// if (data.Players.ContainsKey(Id))
// {
// if (removefromGroup)
// permission.RemoveUserGroup(Id, config.Info.Group);
// if (callHook)
// Interface.Oxide.CallHook("OnDeauthenticate", Id, data.Players[Id]);
// data.Players.Remove(Id);
// return true;
// }
// // If Id is discordId
// if (data.Players.ContainsValue(Id))
// {
// foreach (var steamid in data.Players.Keys)
// {
// if (data.Players[steamid] == Id)
// {
// if (removefromGroup)
// permission.RemoveUserGroup(steamid, config.Info.Group);
// if (callHook)
// Interface.Oxide.CallHook("OnDeauthenticate", steamid, Id);
// data.Players.Remove(steamid);
// return true;
// }
// }
// }
// return false;
//}
private bool API_IsAuthenticated(string Id)
{
if (data.Players.ContainsKey(Id))
return true;
if (data.Players.ContainsValue(Id))
return true;
return false;
}
private string API_GetSteam(string Id)
{
if (data.Players.ContainsValue(Id))
{
foreach (var steamid in data.Players.Keys)
{
if (data.Players[steamid] == Id)
return steamid;
}
}
return null;
}
private string API_GetDiscord(string Id)
{
if (data.Players.ContainsKey(Id))
{
return data.Players[Id];
}
return null;
}
private int API_GetAuthCount() => data.Players.Count;
private List<string> API_GetSteamList() => data.Players.Keys.ToList();
private List<string> API_GetDiscordList() => data.Players.Values.ToList();
#endregion
#region Helpers
private void CreateClient()
{
if (config.Info.BotToken != string.Empty)
Discord.CreateClient(this, config.Info.BotToken);
}
private void CloseClient() => Discord.CloseClient(Client);
private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, data);
private string Lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args);
private void Message(IPlayer player, string key, params object[] args) => player.Reply($"{config.Info.ChatPrefix} {Lang(key, player.Id, args)}");
private string GenerateCode(int size, bool lowerCase)
{
var builder = new StringBuilder();
var random = new System.Random();
char ch;
for (int i = 0; i < size; i++)
{
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
builder.Append(ch);
}
if (lowerCase)
return builder.ToString().ToLower();
return builder.ToString();
}
private Embed GetEmbed(string text, int color)
{
var embed = new Embed
{
description = text,
color = color
};
return embed;
}
private Role GetRoleByName(string roleName)
{
foreach (var role in Client.DiscordServer.roles)
{
if (role.name == roleName)
return role;
}
return null;
}
#endregion
}
}