Archived
1
0
Fork 0

import from nicobot repo

This commit is contained in:
flash 2017-05-14 14:02:51 +02:00
parent acca3401d6
commit 833f4fe8e5
80 changed files with 5657 additions and 15 deletions

21
LICENSE.md Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Julian van de Groep
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maki", "Maki\Maki.csproj", "{4DC6C0A6-520E-4A0A-9A47-3515458B44A2}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maki", "Maki\Maki.csproj", "{97523AED-B694-42C2-96AC-86A1D65109F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MakiTest", "MakiTest\MakiTest.csproj", "{3B147886-0307-4AB8-92A5-4C5CBB93B580}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -11,10 +13,14 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4DC6C0A6-520E-4A0A-9A47-3515458B44A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4DC6C0A6-520E-4A0A-9A47-3515458B44A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DC6C0A6-520E-4A0A-9A47-3515458B44A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4DC6C0A6-520E-4A0A-9A47-3515458B44A2}.Release|Any CPU.Build.0 = Release|Any CPU
{97523AED-B694-42C2-96AC-86A1D65109F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97523AED-B694-42C2-96AC-86A1D65109F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97523AED-B694-42C2-96AC-86A1D65109F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97523AED-B694-42C2-96AC-86A1D65109F7}.Release|Any CPU.Build.0 = Release|Any CPU
{3B147886-0307-4AB8-92A5-4C5CBB93B580}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B147886-0307-4AB8-92A5-4C5CBB93B580}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B147886-0307-4AB8-92A5-4C5CBB93B580}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B147886-0307-4AB8-92A5-4C5CBB93B580}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

838
Maki/Discord.cs Normal file
View file

@ -0,0 +1,838 @@
using Maki.Gateway;
using Maki.Rest;
using Maki.Structures.Auth;
using Maki.Structures.Channels;
using Maki.Structures.Gateway;
using Maki.Structures.Guilds;
using Maki.Structures.Messages;
using Maki.Structures.Presences;
using Maki.Structures.Roles;
using Maki.Structures.Users;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Maki
{
/// <summary>
/// Discord Client
/// </summary>
public class Discord : IDisposable
{
/// <summary>
/// Discord Gateway/API version we're targeting
/// </summary>
internal const int GATEWAY_VERSION = 6;
/// <summary>
/// Whether this client is a bot
/// </summary>
public bool IsBot => TokenType == DiscordTokenType.Bot;
/// <summary>
/// Token type, bot and user connections are handled differently
/// </summary>
public DiscordTokenType TokenType = DiscordTokenType.Bot;
/// <summary>
/// Discord token
/// </summary>
public string Token = string.Empty;
/// <summary>
/// Gateway Url
/// </summary>
internal string Gateway { get; private set; }
/// <summary>
/// Rest API request client
/// </summary>
internal readonly RestClient RestClient;
/// <summary>
/// Gateway shard client
/// </summary>
internal readonly GatewayShardClient ShardClient;
#region Containers
/// <summary>
/// Servers we're in
/// </summary>
internal readonly List<DiscordServer> servers = new List<DiscordServer>();
/// <summary>
/// Users we are affiliated with
/// </summary>
internal readonly List<DiscordUser> users = new List<DiscordUser>();
/// <summary>
/// Members of servers, different from users!
/// </summary>
internal readonly List<DiscordMember> members = new List<DiscordMember>();
/// <summary>
/// Roles in servers
/// </summary>
internal readonly List<DiscordRole> roles = new List<DiscordRole>();
/// <summary>
/// Channels
/// </summary>
internal readonly List<DiscordChannel> channels = new List<DiscordChannel>();
/// <summary>
/// Messages
/// </summary>
internal readonly List<DiscordMessage> messages = new List<DiscordMessage>();
#endregion
#region Events
/// <summary>
/// A channel has been created
/// </summary>
public event Action<DiscordChannel> OnChannelCreate;
/// <summary>
/// A channel has been updated
/// </summary>
public event Action<DiscordChannel> OnChannelUpdate;
/// <summary>
/// A channel has been deleted
/// </summary>
public event Action<DiscordChannel> OnChannelDelete;
/// <summary>
/// Someone has been banned from a server
/// </summary>
public event Action<DiscordUser, DiscordServer> OnBanAdd;
/// <summary>
/// Someone's ban has been lifted from a server
/// </summary>
public event Action<DiscordUser, DiscordServer> OnBanRemove;
/// <summary>
/// A server has been created
/// </summary>
public event Action<DiscordServer> OnServerCreate;
/// <summary>
/// A server has been updated
/// </summary>
public event Action<DiscordServer> OnServerUpdate;
/// <summary>
/// A server has been deleted
/// </summary>
public event Action<DiscordServer> OnServerDelete;
/// <summary>
/// A server's emoji set has been updated
/// </summary>
public event Action<DiscordServer> OnEmojisUpdate;
/// <summary>
/// Someone joined a server
/// </summary>
public event Action<DiscordMember> OnMemberAdd;
/// <summary>
/// A server member has been updated
/// </summary>
public event Action<DiscordMember> OnMemberUpdate;
/// <summary>
/// Someone left a server
/// </summary>
public event Action<DiscordMember> OnMemberRemove;
/// <summary>
/// A user role has been created in a server
/// </summary>
public event Action<DiscordRole> OnRoleCreate;
/// <summary>
/// A user role has been updated in a server
/// </summary>
public event Action<DiscordRole> OnRoleUpdate;
/// <summary>
/// A user role has been deleted in a server
/// </summary>
public event Action<DiscordRole> OnRoleDelete;
/// <summary>
/// A message has been received
/// </summary>
public event Action<DiscordMessage> OnMessageCreate;
/// <summary>
/// A message has been edited
/// </summary>
public event Action<DiscordMessage> OnMessageUpdate;
/// <summary>
/// A message has been deleted
/// </summary>
public event Action<DiscordMessage> OnMessageDelete;
/// <summary>
/// Someone started typing, fired again after 8 seconds
/// </summary>
public event Action<DiscordMember, DiscordChannel> OnTypingStart;
/// <summary>
/// Someone's status and/or game updated
/// </summary>
public event Action<DiscordMember> OnPresenceUpdate;
/// <summary>
/// Fired when the connection was successful and we're currently logged into Discord
/// </summary>
public event Action<DiscordUser> OnReady;
/// <summary>
/// Someone updated their account
/// </summary>
public event Action<DiscordUser> OnUserUpdate;
#endregion
public DiscordServer[] Servers => servers.ToArray();
public DiscordUser Me => users.FirstOrDefault();
/// <summary>
/// Constructor
/// </summary>
public Discord()
{
RestClient = new RestClient(this);
ShardClient = new GatewayShardClient(this);
ShardClient.OnChannelCreate += ShardManager_OnChannelCreate;
ShardClient.OnChannelUpdate += ShardManager_OnChannelUpdate;
ShardClient.OnChannelDelete += ShardManager_OnChannelDelete;
ShardClient.OnGuildBanAdd += ShardManager_OnGuildBanAdd;
ShardClient.OnGuildBanRemove += ShardManager_OnGuildBanRemove;
ShardClient.OnGuildCreate += ShardManager_OnGuildCreate;
ShardClient.OnGuildDelete += ShardManager_OnGuildDelete;
ShardClient.OnGuildUpdate += ShardManager_OnGuildUpdate;
ShardClient.OnGuildEmojisUpdate += ShardManager_OnGuildEmojisUpdate;
ShardClient.OnGuildMemberAdd += ShardManager_OnGuildMemberAdd;
ShardClient.OnGuildMemberUpdate += ShardManager_OnGuildMemberUpdate;
ShardClient.OnGuildMemberRemove += ShardManager_OnGuildMemberRemove;
ShardClient.OnGuildRoleCreate += ShardManager_OnGuildRoleCreate;
ShardClient.OnGuildRoleDelete += ShardManager_OnGuildRoleDelete;
ShardClient.OnGuildRoleUpdate += ShardManager_OnGuildRoleUpdate;
ShardClient.OnGuildIntegrationsUpdate += ShardManager_OnGuildIntegrationsUpdate;
ShardClient.OnGuildMembersChunk += ShardManager_OnGuildMembersChunk;
ShardClient.OnMessageCreate += ShardManager_OnMessageCreate;
ShardClient.OnMessageDelete += ShardManager_OnMessageDelete;
ShardClient.OnMessageUpdate += ShardManager_OnMessageUpdate;
ShardClient.OnTypingStart += ShardManager_OnTypingStart;
ShardClient.OnPresenceUpdate += ShardManager_OnPresenceUpdate;
ShardClient.OnReady += ShardManager_OnReady;
ShardClient.OnResumed += ShardManager_OnResumed;
ShardClient.OnUserUpdate += ShardManager_OnUserUpdate;
ShardClient.OnSocketOpen += ShardManager_OnSocketOpen;
ShardClient.OnSocketClose += ShardManager_OnSocketClose;
ShardClient.OnSocketError += ShardManager_OnSocketError;
ShardClient.OnSocketMessage += ShardManager_OnSocketMessage;
}
private void ClearContainers()
{
servers.Clear();
users.Clear();
roles.Clear();
channels.Clear();
messages.Clear();
members.Clear();
}
/// <summary>
/// Connects to Discord using the token assigned to Token
/// </summary>
public void Connect()
{
ClearContainers();
RestResponse<GatewayInfo> gateway = RestClient.Request<GatewayInfo>(
RestRequestMethod.GET,
IsBot ? RestEndpoints.GatewayBot : RestEndpoints.Gateway
);
if (gateway.ErrorCode != RestErrorCode.Ok)
throw new Exception($"{gateway.ErrorCode}: {gateway.ErrorMessage}");
Gateway = gateway.Response.Url;
if (Gateway.Contains("?"))
Gateway = Gateway.Substring(0, Gateway.IndexOf('?'));
if (!Gateway.EndsWith("/"))
Gateway += "/";
int shards = gateway.Response.Shards ?? 1;
for (int i = 0; i < shards; i++)
ShardClient.Create(i);
}
/// <summary>
/// Connects to Discord with the provided email, password and, optionally, mfa token
/// </summary>
/// <param name="email">Discord account email</param>
/// <param name="password">Discord account password</param>
/// <param name="code">Multi factor authentication token</param>
public void Connect(string email, string password, string code = null)
{
TokenType = DiscordTokenType.User;
RestResponse<LoginResponse> login = RestClient.Request<LoginResponse>(
RestRequestMethod.POST,
RestEndpoints.AuthLogin,
new LoginRequest
{
Email = email,
Password = password,
}
);
if (login.ErrorCode != RestErrorCode.Ok)
throw new Exception($"{login.ErrorCode}: {login.ErrorMessage}");
if (login.Response.UsernameError?.Length > 0)
throw new Exception(login.Response.UsernameError.FirstOrDefault());
if (login.Response.PasswordError?.Length > 0)
throw new Exception(login.Response.PasswordError.FirstOrDefault());
Token = login.Response.Token;
if (string.IsNullOrEmpty(Token))
{
if (login.Response.MFA == true && !string.IsNullOrEmpty(login.Response.Ticket))
{
RestResponse<LoginResponse> totp = RestClient.Request<LoginResponse>(
RestRequestMethod.POST,
RestEndpoints.AuthMfaTotp,
new LoginMultiFactorAuth
{
Code = code,
Ticket = login.Response.Ticket,
}
);
if (totp.ErrorCode != RestErrorCode.Ok)
throw new Exception($"{totp.ErrorCode}: {totp.ErrorMessage}");
Token = totp.Response.Token;
}
else
throw new Exception("Token was null but MFA is false and/or ticket is empty?");
}
if (string.IsNullOrEmpty(Token))
throw new Exception("Authentication failed!");
Connect();
}
/// <summary>
/// Connects to Discord using a provided token and token type
/// </summary>
/// <param name="token"></param>
/// <param name="type"></param>
public void Connect(string token, DiscordTokenType type = DiscordTokenType.Bot)
{
TokenType = type;
Token = token;
Connect();
}
/// <summary>
/// Disconnects from Discord, use Dispose instead if you're not restarting the connection
/// </summary>
public void Disconnect()
{
ShardClient.Disconnect();
ClearContainers();
Token = string.Empty;
Gateway = string.Empty;
}
private void ShardManager_OnChannelCreate(GatewayShard shard, Channel channel)
{
DiscordServer server = servers.Find(x => x.Id == channel.GuildId);
DiscordChannel chan = new DiscordChannel(this, channel, server);
channels.Add(chan);
OnChannelCreate?.Invoke(chan);
}
private void ShardManager_OnChannelUpdate(GatewayShard shard, Channel channel)
{
DiscordChannel chan = channels.Find(x => x.Id == channel.Id);
chan.Name = channel.Name;
/*existing.Type = channel.Type;
existing.LastMessageId = channel.LastMessageId;
if (existing.Type == ChannelType.Private)
existing.Recipients = channel.Recipients;
else
{
existing.GuildId = channel.GuildId;
existing.Topic = channel.Topic;
existing.PermissionOverwrites = channel.PermissionOverwrites;
existing.Position = channel.Position;
}
if (existing.Type == ChannelType.Voice)
{
existing.Bitrate = channel.Bitrate;
existing.UserLimit = channel.UserLimit;
}*/
OnChannelUpdate?.Invoke(chan);
}
private void ShardManager_OnChannelDelete(GatewayShard shard, Channel channel)
{
DiscordChannel chan = channels.Find(x => x.Id == channel.Id);
chan.Name = channel.Name;
/*chan.Type = channel.Type;
chan.LastMessageId = channel.LastMessageId;
if (chan.Type == ChannelType.Private)
chan.Recipients = channel.Recipients;
else
{
chan.GuildId = channel.GuildId;
chan.Topic = channel.Topic;
chan.PermissionOverwrites = channel.PermissionOverwrites;
chan.Position = channel.Position;
}
if (chan.Type == ChannelType.Voice)
{
chan.Bitrate = channel.Bitrate;
chan.UserLimit = channel.UserLimit;
}*/
channels.Remove(chan);
OnChannelDelete?.Invoke(chan);
}
private void ShardManager_OnGuildBanAdd(GatewayShard shard, User sUser)
{
/*$"Id: {user.Id}",
$"Guild Id: {user.GuildId}",
$"Username: {user.Username}",
$"Tag: {user.Tag:0000}",
$"Avatar Hash: {user.AvatarHash}",
$"Is bot?: {user.IsBot}",
$"Has MFA?: {user.HasMFA}",
$"Is verified?: {user.IsVerified}",
$"E-mail: {user.EMail}"*/
DiscordUser user = users.Find(x => x.Id == sUser.Id);
DiscordServer server = servers.Find(x => x.Id == sUser.GuildId);
OnBanAdd?.Invoke(user, server);
}
private void ShardManager_OnGuildBanRemove(GatewayShard shard, User sUser)
{
/*$"Id: {user.Id}",
$"Guild Id: {user.GuildId}",
$"Username: {user.Username}",
$"Tag: {user.Tag:0000}",
$"Avatar Hash: {user.AvatarHash}",
$"Is bot?: {user.IsBot}",
$"Has MFA?: {user.HasMFA}",
$"Is verified?: {user.IsVerified}",
$"E-mail: {user.EMail}"*/
DiscordUser user = users.Find(x => x.Id == sUser.Id);
DiscordServer server = servers.Find(x => x.Id == sUser.GuildId);
OnBanRemove?.Invoke(user, server);
}
private void ShardManager_OnGuildCreate(GatewayShard shard, Guild guild)
{
DiscordServer server = servers.Where(x => x.Id == guild.Id).FirstOrDefault();
if (server == default(DiscordServer))
{
server = new DiscordServer(this, guild);
servers.Add(server);
}
else
{
// should this explode instead if the guild isn't unavailable?
server = servers.Find(x => x.Id == guild.Id);
server.Name = guild.Name;
}
if (guild.Channels != null)
for (int i = 0; i < guild.Channels.Length; i++)
{
Channel channel = guild.Channels[i];
channel.GuildId = server.Id;
if (channels.Where(x => x.Id == channel.Id).Count() > 0)
ShardManager_OnChannelUpdate(shard, channel);
else
ShardManager_OnChannelCreate(shard, channel);
}
if (guild.Roles != null)
foreach (Role role in guild.Roles) {
GuildRole gRole = new GuildRole
{
Guild = server.Id,
Role = role,
RoleId = role.Id
};
if (roles.Where(x => x.Id == role.Id).Count() > 0)
ShardManager_OnGuildRoleUpdate(shard, gRole);
else
ShardManager_OnGuildRoleCreate(shard, gRole);
}
shard.Send(GatewayOPCode.RequestGuildMembers, new GatewayRequestGuildMembers {
Guild = server.Id,
Query = string.Empty,
Limit = 0,
});
OnServerCreate?.Invoke(server);
}
private void ShardManager_OnGuildDelete(GatewayShard shard, Guild guild)
{
DiscordServer server = servers.Find(x => x.Id == guild.Id);
server.Name = guild.Name;
/*server.OwnerId = guild.OwnerId;
server.VoiceRegionId = guild.VoiceRegionId;
server.AfkChannelId = guild.AfkChannelId;
server.AfkTimeout = guild.AfkTimeout;
server.EmbedEnabled = guild.EmbedEnabled;
server.EmbedChannelId = guild.EmbedChannelId;
server.VerificationLevel = guild.VerificationLevel;
server.MessageNotificationLevel = guild.MessageNotificationLevel;
server.Roles = guild.Roles;
server.Emojis = guild.Emojis;
server.Features = guild.Features;
server.MultiFactorAuthLevel = guild.MultiFactorAuthLevel;*/
OnServerDelete?.Invoke(server);
messages.Where(x => x.Channel.Server == server).ToList().ForEach(x => messages.Remove(x));
channels.Where(x => x.Server == server).ToList().ForEach(x => channels.Remove(x));
members.Where(x => x.Server == server).ToList().ForEach(x => members.Remove(x));
roles.Where(x => x.Server == server).ToList().ForEach(x => roles.Remove(x));
servers.Remove(server);
}
private void ShardManager_OnGuildUpdate(GatewayShard shard, Guild guild)
{
DiscordServer server = servers.Find(x => x.Id == guild.Id);
server.Name = guild.Name;
/*server.OwnerId = guild.OwnerId;
server.VoiceRegionId = guild.VoiceRegionId;
server.AfkChannelId = guild.AfkChannelId;
server.AfkTimeout = guild.AfkTimeout;
server.EmbedEnabled = guild.EmbedEnabled;
server.EmbedChannelId = guild.EmbedChannelId;
server.VerificationLevel = guild.VerificationLevel;
server.MessageNotificationLevel = guild.MessageNotificationLevel;
server.Roles = guild.Roles;
server.Emojis = guild.Emojis;
server.Features = guild.Features;
server.MultiFactorAuthLevel = guild.MultiFactorAuthLevel;*/
OnServerUpdate?.Invoke(server);
}
private void ShardManager_OnGuildEmojisUpdate(GatewayShard shard, Guild guild)
{
DiscordServer server = servers.Find(x => x.Id == guild.Id);
//servers.Emojis = guild.Emojis;
OnEmojisUpdate?.Invoke(server);
}
private void ShardManager_OnGuildMemberAdd(GatewayShard shard, GuildMember sMember)
{
DiscordServer server = servers.Find(x => x.Id == sMember.GuildId);
DiscordUser user = users.Where(x => x.Id == sMember.User.Id).FirstOrDefault();
if (user == default(DiscordUser))
{
user = new DiscordUser(this, sMember.User);
users.Add(user);
}
DiscordMember member = new DiscordMember(this, sMember, user, server);
members.Add(member);
OnMemberAdd?.Invoke(member);
}
private void ShardManager_OnGuildMemberUpdate(GatewayShard shard, GuildMember sMember)
{
DiscordMember member = members.Find(x => x.User.Id == sMember.User.Id && x.Server.Id == sMember.GuildId);
/*existing.IsDeafened = member.IsDeafened;
existing.IsMuted = member.IsMuted;
existing.JoinedAt = member.JoinedAt;
existing.Roles = member.Roles;*/
member.Nickname = sMember.Nickname;
OnMemberUpdate?.Invoke(member);
}
private void ShardManager_OnGuildMemberRemove(GatewayShard shard, GuildMember sMember)
{
DiscordMember member = members.Find(x => x.User.Id == sMember.User.Id && x.Server.Id == sMember.GuildId);
members.Remove(member);
OnMemberRemove?.Invoke(member);
}
private void ShardManager_OnGuildRoleCreate(GatewayShard shard, GuildRole sRole)
{
/*$"Guild Id: {role.Guild}",
$"Id: {role.Role.Value.Id}",
$"Name: {role.Role.Value.Name}",
$"Colour: {role.Role.Value.Colour}",
$"Is Hoisted?: {role.Role.Value.IsHoisted}",
$"Position: {role.Role.Value.Position}",
$"Is Managed?: {role.Role.Value.IsManaged}",
$"Is Mentionable?: {role.Role.Value.IsMentionable}",
$"Permissions: {role.Role.Value.Permissions}"*/
DiscordServer server = servers.Find(x => x.Id == sRole.Guild);
DiscordRole role = new DiscordRole(this, sRole.Role.Value, server);
roles.Add(role);
OnRoleCreate?.Invoke(role);
}
private void ShardManager_OnGuildRoleDelete(GatewayShard shard, GuildRole sRole)
{
DiscordRole role = roles.Find(x => x.Id == sRole.RoleId && x.Server.Id == sRole.Guild);
roles.Remove(role);
OnRoleDelete?.Invoke(role);
}
private void ShardManager_OnGuildRoleUpdate(GatewayShard shard, GuildRole sRole)
{
Role dRole = sRole.Role.Value;
DiscordRole role = roles.Find(x => x.Id == dRole.Id && x.Server.Id == sRole.Guild);
role.Name = dRole.Name;
OnRoleUpdate?.Invoke(role);
}
private void ShardManager_OnGuildIntegrationsUpdate(GatewayShard shard, GuildIntegration integration)
{
/*$"Id: {integration.Id}",
$"Name: {integration.Name}",
$"Type: {integration.Type}",
$"Is Enabled?: {integration.IsEnabled}",
$"Is Syncing?: {integration.IsSyncing}",
$"Role Id: {integration.RoleId}",
$"Expire Behaviour: {integration.ExpireBehaviour}",
$"Expire Grace Period: {integration.ExpireGracePeriod}",
$"User: {integration.User.Id}",
$"Account: {integration.Account.Id}, {integration.Account.Name}",
$"Last Sync: {integration.LastSync}"*/
}
private void ShardManager_OnGuildMembersChunk(GatewayShard shard, GuildMembersChunk membersChunk)
{
/*$"Guild Id: {membersChunk.Guild}",
$"Members {string.Join(", ", membersChunk.Members.Select(x => x.User.Id).ToArray())}"*/
for (int i = 0; i < membersChunk.Members.Length; i++)
{
GuildMember member = membersChunk.Members[i];
member.GuildId = membersChunk.Guild;
ShardManager_OnGuildMemberAdd(shard, member);
}
}
private void ShardManager_OnMessageCreate(GatewayShard shard, Message message)
{
DiscordChannel channel = channels.Find(x => x.Id == message.ChannelId);
DiscordMember member = members.Find(x => x.User.Id == message.User.Id);
DiscordMessage msg = new DiscordMessage(this, message, member, channel);
messages.Add(msg);
OnMessageCreate?.Invoke(msg);
}
private void ShardManager_OnMessageDelete(GatewayShard shard, Message message)
{
DiscordMessage msg = messages.Find(x => x.Id == message.Id);
messages.Remove(msg);
OnMessageDelete?.Invoke(msg);
}
private void ShardManager_OnMessageUpdate(GatewayShard shard, Message message)
{
DiscordMessage msg = messages.Find(x => x.Id == message.Id);
msg.Edited = DateTime.Now;
if (!string.IsNullOrEmpty(message.Content))
msg.Text = message.Content;
/*existing.IsTTS = message.IsTTS == true;
existing.IsPinned = message.IsPinned == true;*/
OnMessageUpdate?.Invoke(msg);
}
private void ShardManager_OnTypingStart(GatewayShard shard, TypingStart typing)
{
DiscordChannel channel = channels.Find(x => x.Id == typing.Channel);
DiscordMember member = members.Find(x => x.User.Id == typing.User && x.Server.Id == channel.Server.Id);
OnTypingStart?.Invoke(member, channel);
}
private void ShardManager_OnPresenceUpdate(GatewayShard shard, Presence presence)
{
/*$"User Id: {presence.User.Id}",
$"Roles: " + (presence.Roles != null ? string.Join(", ", presence.Roles) : string.Empty),
"Game: " + (presence.Game.HasValue ? presence.Game.Value.Name : string.Empty),
$"Guild Id: {presence.Guild}",
$"Status: {presence.Status}"*/
DiscordMember member = members.Find(x => x.User.Id == presence.User.Id && x.Server.Id == presence.Guild);
// update user presence
OnPresenceUpdate?.Invoke(member);
}
private void ShardManager_OnReady(GatewayShard shard, GatewayReady ready)
{
foreach (Guild guild in ready.UnavailableGuilds)
// should this call an event handler?
ShardManager_OnGuildCreate(shard, guild);
/* not ready for these yet
foreach (Channel channel in ready.PrivateChannels)
ShardManager_OnChannelCreate(shard, channel);*/
// keep track of self
/*$"Version: {ready.Version}",
$"User: {ready.User.Id}, {ready.User.Username}, {ready.User.EMail}",
$"Private Channels: {string.Join(", ", ready.PrivateChannels.Select(x => x.Id).ToArray())}",
$"Unavailable Guilds: {string.Join(", ", ready.UnavailableGuilds.Select(x => x.Id).ToArray())}",
$"Session: {ready.Session}"*/
DiscordUser user = new DiscordUser(this, ready.User);
users.Add(user);
OnReady?.Invoke(user);
}
private void ShardManager_OnResumed(GatewayShard shard)
{
OnReady?.Invoke(Me);
}
private void ShardManager_OnUserUpdate(GatewayShard shard, User sUser)
{
/*$"Id: {user.Id}",
$"Guild Id: {user.GuildId}",
$"Username: {user.Username}",
$"Tag: {user.Tag:0000}",
$"Avatar Hash: {user.AvatarHash}",
$"Is bot?: {user.IsBot}",
$"Has MFA?: {user.HasMFA}",
$"Is verified?: {user.IsVerified}",
$"E-mail: {user.EMail}"*/
DiscordUser user = users.Where(x => x.Id == sUser.Id).FirstOrDefault();
if (user == default(DiscordUser))
{
user = new DiscordUser(this, sUser);
users.Add(user);
}
OnUserUpdate?.Invoke(user);
}
private void ShardManager_OnSocketOpen(GatewayShard shard)
{
//MultiLineWrite($"Connection opened on shard {shard.Id}");
}
private void ShardManager_OnSocketClose(GatewayShard shard, bool wasClean, ushort code, string reason)
{
//MultiLineWrite($"Connection closed on shard {shard.Id} ({wasClean}/{code}/{reason})");
}
private void ShardManager_OnSocketError(GatewayShard shard, Exception ex)
{
//MultiLineWrite($"Socket error on shard {shard.Id}", ex.Message);
}
private void ShardManager_OnSocketMessage(GatewayShard shard, string text)
{
if (!Directory.Exists("Json"))
Directory.CreateDirectory("Json");
File.WriteAllText(
$"Json/{DateTime.Now:yyyy-MM-dd HH-mm-ss.fffffff}.json",
JsonConvert.SerializeObject(
JsonConvert.DeserializeObject(text),
Formatting.Indented
)
);
}
#region IDisposable
private bool IsDisposed = false;
private void Dispose(bool disposing)
{
if (!IsDisposed) {
IsDisposed = true;
Disconnect();
}
}
~Discord()
{
Dispose(false);
}
/// <summary>
/// Disconnects and releases all unmanaged objects
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
#endregion
}
}

56
Maki/DiscordChannel.cs Normal file
View file

@ -0,0 +1,56 @@
using Maki.Rest;
using Maki.Structures.Channels;
using Maki.Structures.Messages;
using Maki.Structures.Rest;
namespace Maki
{
public class DiscordChannel
{
public readonly ulong Id;
public readonly DiscordServer Server;
private readonly Discord client;
public string Name { get; internal set; }
internal DiscordChannel(Discord discord, Channel channel, DiscordServer server)
{
client = discord;
Id = channel.Id;
Name = channel.Name;
Server = server;
}
public DiscordMessage Send(string text)
{
RestResponse<Message> msg = client.RestClient.Request<Message>(
RestRequestMethod.POST,
RestEndpoints.ChannelMessages(Id),
new MessageCreate {
Text = text,
}
);
DiscordMessage message = new DiscordMessage(client, msg.Response, client.members.Find(x => x.User.Id == msg.Response.User.Id), this);
client.messages.Add(message);
return message;
}
public DiscordMessage Send(DiscordEmbed embed)
{
RestResponse<Message> msg = client.RestClient.Request<Message>(
RestRequestMethod.POST,
RestEndpoints.ChannelMessages(Id),
new MessageCreate
{
Text = string.Empty,
Embed = embed.ToStruct(),
}
);
DiscordMessage message = new DiscordMessage(client, msg.Response, client.members.Find(x => x.User.Id == msg.Response.User.Id), this);
client.messages.Add(message);
return message;
}
}
}

55
Maki/DiscordColour.cs Normal file
View file

@ -0,0 +1,55 @@
using System;
using System.Globalization;
namespace Maki
{
public class DiscordColour
{
public DiscordColour(uint raw)
{
Raw = raw;
}
public DiscordColour(byte red, byte green, byte blue)
{
Raw = (uint)((red << 16) + (green << 8) + blue);
}
public DiscordColour(string hex)
{
hex = hex.ToUpper();
if (hex.Length == 3)
hex = new string(hex[0], 2) + new string(hex[1], 2) + new string(hex[2], 2);
if (hex.Length != 6)
throw new FormatException("Invalid hex colour format!");
Red = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber);
Green = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber);
Blue = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber);
}
public uint Raw = uint.MinValue;
public byte Red
{
get => (byte)(Raw >> 16 & 0xFF);
set => Raw = (uint)((value << 16) + (Green << 8) + Blue);
}
public byte Green
{
get => (byte)(Raw >> 8 & 0xFF);
set => Raw = (uint)((Red << 16) + (value << 8) + Blue);
}
public byte Blue
{
get => (byte)(Raw & 0xFF);
set => Raw = (uint)((Red << 16) + (Green << 8) + value);
}
public string Hex => $"{Red:X2}{Green:X2}{Blue:X2}";
}
}

60
Maki/DiscordEmbed.cs Normal file
View file

@ -0,0 +1,60 @@
using Maki.Structures.Embeds;
using System;
using System.Collections.Generic;
namespace Maki
{
public class DiscordEmbed
{
public string Title;
public string Description;
public string Url;
public DateTime DateTime = DateTime.MinValue;
public DiscordColour Colour;
public DiscordEmbedAuthor Author;
public DiscordEmbedField[] Fields;
public DiscordEmbedFooter Footer;
public DiscordEmbedImage Image;
public DiscordEmbedThumbnail Thumbnail;
internal Embed ToStruct()
{
Embed embed = new Embed() {
Title = Title,
Description = Description,
Url = Url,
};
if (DateTime != null && DateTime != DateTime.MinValue)
embed.Timestamp = DateTime;
if (Colour != null)
embed.Colour = Colour.Raw;
if (Author != null)
embed.Author = Author.ToStruct();
if (Fields != null && Fields.Length > 0)
{
List<EmbedField> fields = new List<EmbedField>();
foreach (DiscordEmbedField field in Fields)
if (!string.IsNullOrEmpty(field.Name) && !string.IsNullOrEmpty(field.Value))
fields.Add(field.ToStruct());
embed.Fields = fields.ToArray();
}
if (Footer != null)
embed.Footer = Footer.ToStruct();
if (Image != null)
embed.Image = Image.ToStruct();
if (Thumbnail != null)
embed.Thumbnail = Thumbnail.ToStruct();
return embed;
}
}
}

View file

@ -0,0 +1,20 @@
using Maki.Structures.Embeds;
namespace Maki
{
public class DiscordEmbedAuthor
{
public string Name;
public string Url;
public string Icon;
internal EmbedAuthor ToStruct()
{
return new EmbedAuthor() {
Name = Name,
Url = Url,
IconUrl = Icon,
};
}
}
}

21
Maki/DiscordEmbedField.cs Normal file
View file

@ -0,0 +1,21 @@
using Maki.Structures.Embeds;
namespace Maki
{
public class DiscordEmbedField
{
public string Name;
public string Value;
public bool Inline = false;
internal EmbedField ToStruct()
{
return new EmbedField()
{
Name = Name,
Value = Value,
Inline = Inline
};
}
}
}

View file

@ -0,0 +1,19 @@
using Maki.Structures.Embeds;
namespace Maki
{
public class DiscordEmbedFooter
{
public string Text;
public string Icon;
internal EmbedFooter ToStruct()
{
return new EmbedFooter()
{
Text = Text,
IconUrl = Icon,
};
}
}
}

24
Maki/DiscordEmbedImage.cs Normal file
View file

@ -0,0 +1,24 @@
using Maki.Structures.Embeds;
namespace Maki
{
public class DiscordEmbedImage
{
public string Url;
internal EmbedImage ToStruct()
{
return new EmbedImage()
{
Url = Url,
};
}
}
/// <summary>
/// An alias of DiscordEmbedImage, for aesthetic reasons
/// </summary>
public class DiscordEmbedThumbnail : DiscordEmbedImage
{
}
}

49
Maki/DiscordMember.cs Normal file
View file

@ -0,0 +1,49 @@
using Maki.Rest;
using Maki.Structures.Guilds;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Maki
{
public class DiscordMember
{
public readonly DiscordUser User;
public readonly DiscordServer Server;
private readonly Discord client;
public DiscordRole[] Roles => client.roles.Where(x => HasRole(x.Id)).ToArray();
public DateTime Joined { get; internal set; }
public string Nickname { get; internal set; }
public string Name => string.IsNullOrEmpty(Nickname) ? User.Username : Nickname;
public string NameWithTag => $"{Name}#{User.Tag:0000}";
public override string ToString() => User.ToString();
private List<ulong> roles = new List<ulong>();
internal DiscordMember(Discord discord, GuildMember member, DiscordUser user, DiscordServer server)
{
client = discord;
User = user;
Server = server;
Nickname = member.Nickname;
Joined = member.JoinedAt ?? DateTime.MinValue;
roles.AddRange(member.Roles);
}
public bool HasRole(ulong id)
{
return roles.Contains(id);
}
public void AddRoles(params DiscordRole[] roles)
{
foreach (DiscordRole role in roles)
{
client.RestClient.Request<object>(RestRequestMethod.PUT, RestEndpoints.GuildMemberRole(Server.Id, User.Id, role.Id));
this.roles.Add(role.Id);
}
}
}
}

32
Maki/DiscordMessage.cs Normal file
View file

@ -0,0 +1,32 @@
using Maki.Structures.Messages;
using System;
namespace Maki
{
public class DiscordMessage
{
public readonly ulong Id;
public readonly DateTime Sent;
public readonly DiscordMember Sender;
public readonly DiscordChannel Channel;
private readonly Discord client;
public string Text { get; internal set; }
public DateTime Edited { get; internal set; }
internal DiscordMessage(Discord discord, Message msg, DiscordMember member, DiscordChannel channel)
{
client = discord;
Id = msg.Id;
Sent = msg.Sent;
Text = msg.Content;
Sender = member;
Channel = channel;
}
public DiscordMessage Edit(string text)
{
return this;
}
}
}

176
Maki/DiscordPermission.cs Normal file
View file

@ -0,0 +1,176 @@
using System;
namespace Maki
{
/// <summary>
/// Discord Permission Flags
/// </summary>
[Flags]
public enum DiscordPermission
{
/// <summary>
/// Allows creation of instant invites
/// </summary>
CreateInstantInvite = 1,
/// <summary>
/// Allows kicking members
/// </summary>
KickMembers = 1 << 1,
/// <summary>
/// Allows banning members
/// </summary>
BanMembers = 1 << 2,
/// <summary>
/// Allows all permissions and bypasses channel permission overwrites
/// </summary>
Administrator = 1 << 3,
/// <summary>
/// Allows management and editing of channels
/// </summary>
ManageChannels = 1 << 4,
/// <summary>
/// Allows management and editing of the guild
/// </summary>
ManageGuild = 1 << 5,
/// <summary>
/// Allows for the addition of reactions to messages
/// </summary>
AddReactions = 1 << 6,
/// <summary>
/// Allows viewing the audit log
/// </summary>
ViewAuditLog = 1 << 7,
/// <summary>
/// Allows reading messages in a channel. The channel will not appear for users without this permission
/// </summary>
ReadMessages = 1 << 10,
/// <summary>
/// Allows for sending messages in a channel.
/// </summary>
SendMessages = 1 << 11,
/// <summary>
/// Allows for sending of /tts messages
/// </summary>
SendTTSMessages = 1 << 12,
/// <summary>
/// Allows for deletion of other users messages
/// </summary>
ManageMessages = 1 << 13,
/// <summary>
/// Links sent by this user will be auto-embedded
/// </summary>
EmbedLinks = 1 << 14,
/// <summary>
/// Allows for uploading images and files
/// </summary>
AttachFiles = 1 << 15,
/// <summary>
/// Allows for reading of message history
/// </summary>
ReadMessageHistory = 1 << 16,
/// <summary>
/// Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel
/// </summary>
MentionEveryone = 1 << 17,
/// <summary>
/// Allows the usage of custom emojis from other servers
/// </summary>
ExternalEmojis = 1 << 18,
/// <summary>
/// Allows for joining of a voice channel
/// </summary>
VoiceConnect = 1 << 20,
/// <summary>
/// Allows for speaking in a voice channel
/// </summary>
VoiceSpeak = 1 << 21,
/// <summary>
/// Allows for muting members in a voice channel
/// </summary>
VoiceMuteMembers = 1 << 22,
/// <summary>
/// Allows for deafening of members in a voice channel
/// </summary>
VoiceDeafenMembers = 1 << 23,
/// <summary>
/// Allows for moving of members between voice channels
/// </summary>
VoiceMoveMembers = 1 << 24,
/// <summary>
/// Allows for using voice-activity-detection in a voice channel
/// </summary>
VoiceUseVAD = 1 << 25,
/// <summary>
/// Allows for modification of own nickname
/// </summary>
ChangeNickname = 1 << 26,
/// <summary>
/// Allows for modification of other users nicknames
/// </summary>
ManageNicknames = 1 << 27,
/// <summary>
/// Allows management and editing of roles
/// </summary>
ManageRoles = 1 << 28,
/// <summary>
/// Allows management and editing of webhooks
/// </summary>
ManageWebhooks = 1 << 29,
/// <summary>
/// Allows management and editing of emojis
/// </summary>
ManageEmojis = 1 << 30,
/// <summary>
/// Blank permissions
/// </summary>
None = 0,
/// <summary>
/// All Guild related permissions
/// </summary>
AllGuild = CreateInstantInvite | KickMembers | BanMembers | Administrator | ManageChannels | ManageGuild | AddReactions | ViewAuditLog | ChangeNickname | ManageNicknames | ManageRoles | ManageWebhooks | ManageEmojis,
/// <summary>
/// All text channel permissions
/// </summary>
AllText = CreateInstantInvite | ManageChannels | ViewAuditLog | ReadMessages | SendMessages | SendTTSMessages | ManageMessages | EmbedLinks | AttachFiles | ReadMessageHistory | MentionEveryone | ExternalEmojis | ManageRoles | ManageWebhooks | ManageEmojis,
/// <summary>
/// All voice channel permissions
/// </summary>
AllVoice = CreateInstantInvite | ManageChannels | ViewAuditLog | VoiceConnect | VoiceSpeak | VoiceMuteMembers | VoiceDeafenMembers | VoiceMoveMembers | VoiceUseVAD | ManageRoles,
/// <summary>
/// All permissions
/// </summary>
All = CreateInstantInvite | KickMembers | BanMembers | Administrator | ManageChannels | ManageGuild | AddReactions | ViewAuditLog | ReadMessages | SendMessages | SendTTSMessages | ManageMessages | EmbedLinks | AttachFiles | ReadMessageHistory | MentionEveryone | ExternalEmojis | VoiceConnect | VoiceSpeak | VoiceMuteMembers | VoiceDeafenMembers | VoiceMoveMembers | VoiceUseVAD | ChangeNickname | ManageNicknames | ManageRoles | ManageWebhooks | ManageEmojis,
}
}

61
Maki/DiscordRole.cs Normal file
View file

@ -0,0 +1,61 @@
using Maki.Rest;
using Maki.Structures.Rest;
using Maki.Structures.Roles;
using System;
namespace Maki
{
public class DiscordRole
{
public readonly ulong Id;
public readonly DiscordServer Server;
public readonly DiscordColour Colour;
private readonly Discord client;
public string Name { get; internal set; }
public DiscordPermission Perms { get; internal set; }
public bool IsHoisted { get; internal set; }
public bool IsMentionable { get; internal set; }
internal DiscordRole(Discord discord, Role role, DiscordServer server)
{
client = discord;
Id = role.Id;
Name = role.Name;
Perms = (DiscordPermission)role.Permissions;
IsHoisted = role.IsHoisted == true;
IsMentionable = role.IsMentionable == true;
Server = server;
Colour = new DiscordColour(role.Colour ?? uint.MinValue);
}
public void Delete()
{
RestResponse<object> resp = client.RestClient.Request<object>(RestRequestMethod.DELETE, RestEndpoints.GuildRole(Server.Id, Id));
if (resp.Status != 204)
throw new Exception("Failed to delete role!");
}
public void Edit(string name = null, DiscordPermission? perms = null, DiscordColour colour = null, bool? hoist = null, bool? mentionable = null)
{
RestResponse<Role> role = client.RestClient.Request<Role>(RestRequestMethod.PATCH, RestEndpoints.GuildRole(Server.Id, Id), new RoleCreate
{
Name = name ?? Name,
Perms = perms ?? Perms,
Colour = colour == null ? 0 : colour.Raw,
Hoist = hoist ?? IsHoisted,
Mentionable = mentionable ?? IsMentionable
});
if (role.ErrorCode != RestErrorCode.Ok)
throw new Exception($"{role.ErrorCode}: {role.ErrorMessage}");
Name = role.Response.Name;
Perms = (DiscordPermission)role.Response.Permissions;
Colour.Raw = role.Response.Colour ?? 0;
IsHoisted = role.Response.IsHoisted == true;
IsMentionable = role.Response.IsMentionable == true;
}
}
}

49
Maki/DiscordServer.cs Normal file
View file

@ -0,0 +1,49 @@
using Maki.Rest;
using Maki.Structures.Guilds;
using Maki.Structures.Rest;
using Maki.Structures.Roles;
using System;
using System.Linq;
namespace Maki
{
public class DiscordServer
{
public readonly ulong Id;
private readonly Discord client;
public string Name { get; internal set; }
public ulong OwnerId { get; internal set; }
public DiscordMember Owner => Members.Where(x => x.User.Id == OwnerId).FirstOrDefault();
public DiscordMember[] Members => client.members.Where(x => x.Server == this).ToArray();
public DiscordRole[] Roles => client.roles.Where(x => x.Server == this).ToArray();
internal DiscordServer(Discord discord, Guild guild)
{
client = discord;
Id = guild.Id;
Name = guild.Name;
OwnerId = guild.OwnerId ?? ulong.MinValue;
}
public DiscordRole CreateRole(string name = null, DiscordPermission perms = DiscordPermission.None, DiscordColour colour = null, bool hoist = false, bool mentionable = false)
{
RestResponse<Role> roleResp = client.RestClient.Request<Role>(RestRequestMethod.POST, RestEndpoints.GuildRoles(Id), new RoleCreate
{
Name = name,
Perms = perms,
Colour = colour == null ? 0 : colour.Raw,
Hoist = hoist,
Mentionable = mentionable
});
if (roleResp.ErrorCode != RestErrorCode.Ok)
throw new Exception($"{roleResp.ErrorCode}: {roleResp.ErrorMessage}");
DiscordRole role = new DiscordRole(client, roleResp.Response, this);
client.roles.Add(role);
return role;
}
}
}

18
Maki/DiscordTokenType.cs Normal file
View file

@ -0,0 +1,18 @@
namespace Maki
{
/// <summary>
/// Discord token type
/// </summary>
public enum DiscordTokenType
{
/// <summary>
/// Regular user
/// </summary>
User,
/// <summary>
/// Manage bot user
/// </summary>
Bot
}
}

48
Maki/DiscordUser.cs Normal file
View file

@ -0,0 +1,48 @@
using Maki.Rest;
using Maki.Structures.Presences;
using Maki.Structures.Users;
using System;
namespace Maki
{
public class DiscordUser
{
public readonly ulong Id;
public readonly bool IsBot;
public readonly DateTime Created;
private readonly Discord client;
public string Username { get; internal set; }
public ushort Tag { get; internal set; }
public bool HasMFA { get; internal set; }
public bool IsVerified { get; internal set; }
public string EMail { get; internal set; }
public string NameWithTag => $"{Username}#{Tag:0000}";
public string At => $"<@{Id}>";
public override string ToString() => At;
private string avatarHash;
public string Avatar(string ext = @"jpg", int size = 128) => RestEndpoints.CDN_URL + $@"/avatars/{Id}/{avatarHash}.{ext}?size={size}";
internal DiscordUser(Discord discord, User user)
{
client = discord;
Id = user.Id;
Created = Utility.FromDiscordTimeMilliseconds((long) Id >> 22);
Username = user.Username;
Tag = user.Tag;
IsBot = user.IsBot;
HasMFA = user.HasMFA;
IsVerified = user.IsVerified;
EMail = user.EMail;
avatarHash = user.AvatarHash;
}
internal void SetPresence(Presence presence)
{
//
}
}
}

View file

@ -0,0 +1,63 @@
namespace Maki.Gateway
{
/// <summary>
/// Extended WebSocket close codes used by Discord's Gateway
/// </summary>
enum GatewayCloseCode
{
/// <summary>
/// We're not sure what went wrong. Try reconnecting?
/// </summary>
Unknown = 4000,
/// <summary>
/// You sent an invalid Gateway OP Code. Don't do that!
/// </summary>
UnknownOPCode = 4001,
/// <summary>
/// You sent an invalid payload to us. Don't do that!
/// </summary>
DecodeError = 4002,
/// <summary>
/// You sent us a payload prior to identifying.
/// </summary>
NotAuthenticated = 4003,
/// <summary>
/// The account token sent with your identify payload is incorrect.
/// </summary>
AuthenticationFailed = 4004,
/// <summary>
/// You sent more than one identify payload. Don't do that!
/// </summary>
AlreadyAuthenticated = 4005,
/// <summary>
/// The sequence sent when resuming the session was invalid. Reconnect and start a new session.
/// </summary>
InvalidSequence = 4007,
/// <summary>
/// Woah nelly! You're sending payloads to us too quickly. Slow it down!
/// </summary>
RateLimited = 4008,
/// <summary>
/// Your session timed out. Reconnect and start a new one.
/// </summary>
SessionTimeout = 4009,
/// <summary>
/// You sent us an invalid shard when identifying.
/// </summary>
InvalidShard = 4010,
/// <summary>
/// The session would have handled too many guilds - you are required to shard your connection in order to connect.
/// </summary>
ShardingRequired = 4011,
}
}

View file

@ -0,0 +1,192 @@
namespace Maki.Gateway
{
/// <summary>
/// Gateway Dispatch Events
/// </summary>
enum GatewayEvent
{
#region Voice Events
CALL_CREATE,
CALL_DELETE,
CALL_UPDATE,
/// <summary>
/// Sent when a guild's voice server is updated. This is sent when initially connecting to voice, and when the current voice instance fails over to a new server.
/// </summary>
VOICE_SERVER_UPDATE,
/// <summary>
/// Sent when someone joins/leaves/moves voice channels. Inner payload is a voice state object.
/// </summary>
VOICE_STATE_UPDATE,
#endregion
#region Channel Events
/// <summary>
/// Sent when a new channel is created, relevant to the current user. The inner payload is a DM or Guild channel object.
/// </summary>
CHANNEL_CREATE,
/// <summary>
/// Sent when a channel relevant to the current user is deleted. The inner payload is a DM or Guild channel object.
/// </summary>
CHANNEL_DELETE,
CHANNEL_PINS_ACK,
CHANNEL_PINS_UPDATE,
CHANNEL_RECIPIENT_ADD,
CHANNEL_RECIPIENT_REMOVE,
/// <summary>
/// Sent when a channel is updated. The inner payload is a guild channel object.
/// </summary>
CHANNEL_UPDATE,
#endregion
#region Guild Events
/// <summary>
/// Sent when a user is banned from a guild. The inner payload is a user object, with an extra guild_id key.
/// </summary>
GUILD_BAN_ADD,
/// <summary>
/// Sent when a user is unbanned from a guild. The inner payload is a user object, with an extra guild_id key.
/// </summary>
GUILD_BAN_REMOVE,
/// <summary>
/// This event can be sent in three different scenarios:
/// 1. When a user is initially connecting, to lazily load and backfill information for all unavailable guilds sent in the ready event.
/// 2. When a Guild becomes available again to the client.
/// 3. When the current user joins a new Guild.
/// The inner payload is a guild object, with all the extra fields specified.
/// </summary>
GUILD_CREATE,
/// <summary>
/// Sent when a guild becomes unavailable during a guild outage, or when the user leaves or is removed from a guild. See GUILD_CREATE for more information about how to handle this event.
/// </summary>
GUILD_DELETE,
/// <summary>
/// Sent when a guild's emojis have been updated.
/// </summary>
GUILD_EMOJIS_UPDATE,
/// <summary>
/// Sent when a guild integration is updated.
/// </summary>
GUILD_INTEGRATIONS_UPDATE,
/// <summary>
/// Sent when a new user joins a guild. The inner payload is a guild member objec, with an extra guild_id key.
/// </summary>
GUILD_MEMBER_ADD,
/// <summary>
/// Sent when a guild member is updated.
/// </summary>
GUILD_MEMBER_UPDATE,
/// <summary>
/// Sent when a user is removed from a guild (leave/kick/ban).
/// </summary>
GUILD_MEMBER_REMOVE,
/// <summary>
/// Sent in response to Gateway Request Guild Members.
/// </summary>
GUILD_MEMBERS_CHUNK,
/// <summary>
/// Sent when a guild role is created.
/// </summary>
GUILD_ROLE_CREATE,
/// <summary>
/// Sent when a guild role is updated.
/// </summary>
GUILD_ROLE_UPDATE,
/// <summary>
/// Sent when a guild role is deleted.
/// </summary>
GUILD_ROLE_DELETE,
/// <summary>
/// Sent when a guild is updated. The inner payload is a guild object.
/// </summary>
GUILD_UPDATE,
#endregion
#region Message Events
MESSAGE_ACK,
/// <summary>
/// Sent when a message is created. The inner payload is a message object.
/// </summary>
MESSAGE_CREATE,
/// <summary>
/// Sent when a message is deleted.
/// </summary>
MESSAGE_DELETE,
/// <summary>
/// Sent when multiple messages are deleted at once.
/// </summary>
MESSAGE_DELETE_BULK,
MESSAGE_REACTION_ADD,
MESSAGE_REACTION_REMOVE,
MESSAGE_REACTIONS_REMOVE_ALL,
/// <summary>
/// Sent when a message is updated. The inner payload is a message object.
/// </summary>
MESSAGE_UPDATE,
#endregion
#region Presence Events
/// <summary>
/// Sent when a user starts typing in a channel.
/// </summary>
TYPING_START,
/// <summary>
/// A user's presence is their current state on a guild. This event is sent when a user's presence is updated for a guild.
/// </summary>
PRESENCE_UPDATE,
PRESENCES_REPLACE,
#endregion
#region Relation Events
FRIEND_SUGGESTION_DELETE,
RELATIONSHIP_ADD,
RELATIONSHIP_REMOVE,
#endregion
#region State Events
/// <summary>
/// The ready event is dispatched when a client has completed the initial handshake with the gateway (for new sessions). The ready event can be the largest and most complex event the gateway will send, as it contains all the state required for a client to begin interacting with the rest of the platform.
/// </summary>
READY,
/// <summary>
/// The resumed event is dispatched when a client has sent a resume payload to the gateway (for resuming existing sessions).
/// </summary>
RESUMED,
#endregion
#region User Events
/// <summary>
/// Sent when properties about the user change. Inner payload is a user object.
/// </summary>
USER_UPDATE,
USER_NOTE_UPDATE,
/// <summary>
/// Sent when the current user updates their settings. Inner payload is a user settings object.
/// </summary>
USER_SETTINGS_UPDATE,
#endregion
}
}

View file

@ -0,0 +1,62 @@
using System;
using System.Threading;
namespace Maki.Gateway
{
/// <summary>
/// Handles Heartbeat (pings) in the background for a Shard
/// </summary>
class GatewayHeartbeatManager
{
/// <summary>
/// Subject shard
/// </summary>
private readonly GatewayShard shard;
/// <summary>
/// Interval timer for repeating the request
/// </summary>
private Timer timer = null;
/// <summary>
/// Constructor
/// </summary>
/// <param name="s">Parent GatewayShard instance</param>
public GatewayHeartbeatManager(GatewayShard s)
{
shard = s;
}
/// <summary>
/// Starts sending heartbeats
/// </summary>
public void Start()
{
if (timer != null)
Stop();
timer = new Timer(Handler, null, TimeSpan.Zero, shard.HeartbeatInterval);
}
/// <summary>
/// Heartbeat sender
/// </summary>
/// <param name="state"></param>
private void Handler(object state)
{
shard.Send(GatewayOPCode.Heartbeat, shard.LastSequence);
}
/// <summary>
/// Stops sending heartbeats
/// </summary>
public void Stop()
{
if (timer == null)
return;
timer.Dispose();
timer = null;
}
}
}

View file

@ -0,0 +1,68 @@
namespace Maki.Gateway
{
/// <summary>
/// Gateway OPcodes
/// </summary>
enum GatewayOPCode
{
/// <summary>
/// dispatches an event
/// </summary>
Dispatch = 0,
/// <summary>
/// used for ping checking
/// </summary>
Heartbeat = 1,
/// <summary>
/// used for client handshake
/// </summary>
Identify = 2,
/// <summary>
/// used to update the client status
/// </summary>
StatusUpdate = 3,
/// <summary>
/// used to join/move/leave voice channels
/// </summary>
VoiceStateUpdate = 4,
/// <summary>
/// used for voice ping checking
/// </summary>
VoiceServerPing = 5,
/// <summary>
/// used to resume a closed connection
/// </summary>
Resume = 6,
/// <summary>
/// used to tell client to reconnect to the gateway
/// </summary>
Reconnect = 7,
/// <summary>
/// used to request guild members
/// </summary>
RequestGuildMembers = 8,
/// <summary>
/// used to notify client they have an invalid session id
/// </summary>
InvalidSession = 9,
/// <summary>
/// sent immediately after connecting, contains heartbeat and server debug information
/// </summary>
Hello = 10,
/// <summary>
/// sent immediately following a client heartbeat that was received
/// </summary>
HeartbeatACK = 11
}
}

View file

@ -0,0 +1,507 @@
using Maki.Structures.Channels;
using Maki.Structures.Gateway;
using Maki.Structures.Guilds;
using Maki.Structures.Messages;
using Maki.Structures.Presences;
using Maki.Structures.Users;
using Newtonsoft.Json;
using System;
using WebSocketSharp;
namespace Maki.Gateway
{
/// <summary>
/// Gateway connection shard
/// </summary>
class GatewayShard : IDisposable
{
/// <summary>
/// Session key for continuing a resuming after disconnecting
/// </summary>
private string session;
/// <summary>
/// Websocket container
/// </summary>
private WebSocket webSocket;
/// <summary>
/// Active gateway version
/// </summary>
private int gatewayVersion = Discord.GATEWAY_VERSION;
/// <summary>
/// Interval at which heartbeats are sent
/// </summary>
public TimeSpan HeartbeatInterval { get; private set; }
/// <summary>
/// Last sequence received from the gateway, primarily used for resuming connections and getting the data that was missed
/// </summary>
public int? LastSequence { get; private set; } = null;
/// <summary>
/// Identifier for this Shard
/// </summary>
public readonly int Id;
/// <summary>
/// Parent DiscordClient instance
/// </summary>
private readonly Discord client;
/// <summary>
/// Heartbeat handler
/// </summary>
private readonly GatewayHeartbeatManager heartbeatHandler;
#region Events
public event Action<GatewayShard> OnCallCreate;
public event Action<GatewayShard> OnCallDelete;
public event Action<GatewayShard> OnCallUpdate;
public event Action<GatewayShard> OnVoiceServerUpdate;
public event Action<GatewayShard> OnVoiceStateUpdate;
public event Action<GatewayShard, Channel> OnChannelCreate;
public event Action<GatewayShard, Channel> OnChannelDelete;
public event Action<GatewayShard> OnChannelPinsAck;
public event Action<GatewayShard> OnChannelPinsUpdate;
public event Action<GatewayShard> OnChannelRecipientAdd;
public event Action<GatewayShard> OnChannelRecipientRemove;
public event Action<GatewayShard, Channel> OnChannelUpdate;
public event Action<GatewayShard, User> OnGuildBanAdd;
public event Action<GatewayShard, User> OnGuildBanRemove;
public event Action<GatewayShard, Guild> OnGuildCreate;
public event Action<GatewayShard, Guild> OnGuildDelete;
public event Action<GatewayShard, Guild> OnGuildEmojisUpdate;
public event Action<GatewayShard, GuildIntegration> OnGuildIntegrationsUpdate;
public event Action<GatewayShard, GuildMember> OnGuildMemberAdd;
public event Action<GatewayShard, GuildMember> OnGuildMemberUpdate;
public event Action<GatewayShard, GuildMember> OnGuildMemberRemove;
public event Action<GatewayShard, GuildMembersChunk> OnGuildMembersChunk;
public event Action<GatewayShard, GuildRole> OnGuildRoleCreate;
public event Action<GatewayShard, GuildRole> OnGuildRoleUpdate;
public event Action<GatewayShard, GuildRole> OnGuildRoleDelete;
public event Action<GatewayShard, Guild> OnGuildUpdate;
public event Action<GatewayShard> OnMessageAck;
public event Action<GatewayShard, Message> OnMessageCreate;
public event Action<GatewayShard, Message> OnMessageDelete;
public event Action<GatewayShard> OnMessageDeleteBulk;
public event Action<GatewayShard> OnMessageReactionAdd;
public event Action<GatewayShard> OnMessageReactionRemove;
public event Action<GatewayShard> OnMessageReactionsRemoveAll;
public event Action<GatewayShard, Message> OnMessageUpdate;
public event Action<GatewayShard, TypingStart> OnTypingStart;
public event Action<GatewayShard, Presence> OnPresenceUpdate;
public event Action<GatewayShard> OnPresencesReplace;
public event Action<GatewayShard> OnFriendSuggestionDelete;
public event Action<GatewayShard> OnRelationshipAdd;
public event Action<GatewayShard> OnRelationshipRemove;
public event Action<GatewayShard, GatewayReady> OnReady;
public event Action<GatewayShard> OnResumed;
public event Action<GatewayShard, User> OnUserUpdate;
public event Action<GatewayShard> OnUserNoteUpdate;
public event Action<GatewayShard> OnUserSettingsUpdate;
public event Action<GatewayShard> OnSocketOpen;
public event Action<GatewayShard, bool, ushort, string> OnSocketClose;
public event Action<GatewayShard, Exception> OnSocketError;
public event Action<GatewayShard, string> OnSocketMessage;
#endregion
/// <summary>
/// Constructor
/// </summary>
/// <param name="id">Shard Id</param>
/// <param name="c">Parent DiscordClient instance</param>
public GatewayShard(int id, Discord c)
{
Id = id;
client = c;
heartbeatHandler = new GatewayHeartbeatManager(this);
Connect();
}
/// <summary>
/// Event handler for WebSocketSharp's OnOpen event, forwards to our OnSocketOpen event
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="e">Event arguments</param>
private void WebSocket_OnOpen(object sender, EventArgs e) => OnSocketOpen?.Invoke(this);
/// <summary>
/// Event handler for WebSocketSharp's OnError event, forwards to our OnSocketError event
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="e">Event arguments</param>
private void WebSocket_OnError(object sender, ErrorEventArgs e) => OnSocketError?.Invoke(this, e.Exception);
/// <summary>
/// Event handler for WebSocketSharp's OnClose event, stops heartbeats, forwards to our OnSocketClose event and reconnects if the close wasn't clean
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="e">Event arguments</param>
private void WebSocket_OnClose(object sender, CloseEventArgs e)
{
heartbeatHandler.Stop();
OnSocketClose?.Invoke(this, e.WasClean, e.Code, e.Reason);
if (!e.WasClean)
Connect();
}
/// <summary>
/// Event handler for WebSocketSharp's OnOpen event, handles gateway instructions and forwards to our events
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="e">Event arguments</param>
private void WebSocket_OnMessage(object sender, MessageEventArgs e)
{
// ignore non-text messages
if (!e.IsText)
return;
//Console.WriteLine(e.Data.Replace(client.Token, new string('*', client.Token.Length)));
OnSocketMessage?.Invoke(this, e.Data);
GatewayPayload payload = JsonConvert.DeserializeObject<GatewayPayload>(e.Data);
switch (payload.OPCode) {
case GatewayOPCode.Dispatch:
LastSequence = payload.Sequence;
Enum.TryParse(payload.Name, out GatewayEvent evt);
switch (evt) {
#region Call
case GatewayEvent.CALL_CREATE:
OnCallCreate?.Invoke(this);
break;
case GatewayEvent.CALL_DELETE:
OnCallDelete?.Invoke(this);
break;
case GatewayEvent.CALL_UPDATE:
OnCallUpdate?.Invoke(this);
break;
case GatewayEvent.VOICE_SERVER_UPDATE:
OnVoiceServerUpdate?.Invoke(this);
break;
case GatewayEvent.VOICE_STATE_UPDATE:
OnVoiceStateUpdate?.Invoke(this);
break;
#endregion
#region Channel
case GatewayEvent.CHANNEL_CREATE:
OnChannelCreate?.Invoke(this, payload.DataAs<Channel>());
break;
case GatewayEvent.CHANNEL_DELETE:
OnChannelDelete?.Invoke(this, payload.DataAs<Channel>());
break;
case GatewayEvent.CHANNEL_PINS_ACK:
OnChannelPinsAck?.Invoke(this);
break;
case GatewayEvent.CHANNEL_PINS_UPDATE:
OnChannelPinsUpdate?.Invoke(this);
break;
case GatewayEvent.CHANNEL_RECIPIENT_ADD:
OnChannelRecipientAdd?.Invoke(this);
break;
case GatewayEvent.CHANNEL_RECIPIENT_REMOVE:
OnChannelRecipientRemove?.Invoke(this);
break;
case GatewayEvent.CHANNEL_UPDATE:
OnChannelUpdate?.Invoke(this, payload.DataAs<Channel>());
break;
#endregion
#region Guild
case GatewayEvent.GUILD_BAN_ADD:
OnGuildBanAdd?.Invoke(this, payload.DataAs<User>());
break;
case GatewayEvent.GUILD_BAN_REMOVE:
OnGuildBanRemove?.Invoke(this, payload.DataAs<User>());
break;
case GatewayEvent.GUILD_CREATE:
OnGuildCreate?.Invoke(this, payload.DataAs<Guild>());
break;
case GatewayEvent.GUILD_DELETE:
OnGuildDelete?.Invoke(this, payload.DataAs<Guild>());
break;
case GatewayEvent.GUILD_EMOJIS_UPDATE:
OnGuildEmojisUpdate?.Invoke(this, payload.DataAs<Guild>());
break;
case GatewayEvent.GUILD_INTEGRATIONS_UPDATE:
OnGuildIntegrationsUpdate?.Invoke(this, payload.DataAs<GuildIntegration>());
break;
case GatewayEvent.GUILD_MEMBER_ADD:
OnGuildMemberAdd?.Invoke(this, payload.DataAs<GuildMember>());
break;
case GatewayEvent.GUILD_MEMBER_UPDATE:
OnGuildMemberUpdate?.Invoke(this, payload.DataAs<GuildMember>());
break;
case GatewayEvent.GUILD_MEMBER_REMOVE:
OnGuildMemberRemove?.Invoke(this, payload.DataAs<GuildMember>());
break;
case GatewayEvent.GUILD_MEMBERS_CHUNK:
OnGuildMembersChunk?.Invoke(this, payload.DataAs<GuildMembersChunk>());
break;
case GatewayEvent.GUILD_ROLE_CREATE:
OnGuildRoleCreate?.Invoke(this, payload.DataAs<GuildRole>());
break;
case GatewayEvent.GUILD_ROLE_UPDATE:
OnGuildRoleUpdate?.Invoke(this, payload.DataAs<GuildRole>());
break;
case GatewayEvent.GUILD_ROLE_DELETE:
OnGuildRoleDelete?.Invoke(this, payload.DataAs<GuildRole>());
break;
case GatewayEvent.GUILD_UPDATE:
OnGuildUpdate?.Invoke(this, payload.DataAs<Guild>());
break;
#endregion
#region Message
case GatewayEvent.MESSAGE_ACK:
OnMessageAck?.Invoke(this);
break;
case GatewayEvent.MESSAGE_CREATE:
OnMessageCreate?.Invoke(this, payload.DataAs<Message>());
break;
case GatewayEvent.MESSAGE_DELETE:
OnMessageDelete?.Invoke(this, payload.DataAs<Message>());
break;
case GatewayEvent.MESSAGE_DELETE_BULK:
OnMessageDeleteBulk?.Invoke(this);
break;
case GatewayEvent.MESSAGE_REACTION_ADD:
OnMessageReactionAdd?.Invoke(this);
break;
case GatewayEvent.MESSAGE_REACTION_REMOVE:
OnMessageReactionRemove?.Invoke(this);
break;
case GatewayEvent.MESSAGE_REACTIONS_REMOVE_ALL:
OnMessageReactionsRemoveAll?.Invoke(this);
break;
case GatewayEvent.MESSAGE_UPDATE:
OnMessageUpdate?.Invoke(this, payload.DataAs<Message>());
break;
#endregion
#region Presence
case GatewayEvent.TYPING_START:
OnTypingStart?.Invoke(this, payload.DataAs<TypingStart>());
break;
case GatewayEvent.PRESENCE_UPDATE:
OnPresenceUpdate?.Invoke(this, payload.DataAs<Presence>());
break;
case GatewayEvent.PRESENCES_REPLACE:
OnPresencesReplace?.Invoke(this);
break;
#endregion
#region Relations
case GatewayEvent.FRIEND_SUGGESTION_DELETE:
OnFriendSuggestionDelete?.Invoke(this);
break;
case GatewayEvent.RELATIONSHIP_ADD:
OnRelationshipAdd?.Invoke(this);
break;
case GatewayEvent.RELATIONSHIP_REMOVE:
OnRelationshipRemove?.Invoke(this);
break;
#endregion
#region State
case GatewayEvent.READY:
GatewayReady ready = payload.DataAs<GatewayReady>();
gatewayVersion = ready.Version;
session = ready.Session;
OnReady?.Invoke(this, ready);
break;
case GatewayEvent.RESUMED:
OnResumed?.Invoke(this);
break;
#endregion
#region User
case GatewayEvent.USER_UPDATE:
OnUserUpdate?.Invoke(this, payload.DataAs<User>());
break;
case GatewayEvent.USER_NOTE_UPDATE:
OnUserNoteUpdate?.Invoke(this);
break;
case GatewayEvent.USER_SETTINGS_UPDATE:
OnUserSettingsUpdate?.Invoke(this);
break;
#endregion
default:
Console.WriteLine($"Unknown payload type: {payload.Name}");
break;
}
break;
case GatewayOPCode.Hello:
GatewayHello hello = payload.DataAs<GatewayHello>();
HeartbeatInterval = TimeSpan.FromMilliseconds(hello.HeartbeatInterval);
heartbeatHandler.Start();
if (string.IsNullOrEmpty(session))
Send(GatewayOPCode.Identify, new GatewayIdentification {
Token = client.Token,
Compress = false,
LargeThreshold = 250,
Shard = new int[2] { Id, client.ShardClient.ShardCount },
Properties = new GatewayIdentificationProperties {
OperatingSystem = @"windows",
Browser = @"Maki",
Device = @"Maki",
Referrer = string.Empty,
ReferringDomain = string.Empty,
}
});
else
Send(GatewayOPCode.Resume, new GatewayResume {
LastSequence = LastSequence ?? default(int),
Token = client.Token,
Session = session,
});
break;
}
}
#region IDisposable
private bool IsDisposed = false;
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
IsDisposed = true;
Disconnect();
}
}
~GatewayShard()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
#endregion
/// <summary>
/// Connects to the gateway
/// </summary>
public void Connect()
{
webSocket = new WebSocket($"{client.Gateway}?v={gatewayVersion}&encoding=json");
// make wss not log anything on its own
webSocket.Log.Output = (LogData logData, string path) => { };
webSocket.OnOpen += WebSocket_OnOpen;
webSocket.OnClose += WebSocket_OnClose;
webSocket.OnError += WebSocket_OnError;
webSocket.OnMessage += WebSocket_OnMessage;
webSocket.Connect();
}
/// <summary>
/// Stops heartbeats and closes gateway connection for this shard
/// </summary>
public void Disconnect()
{
heartbeatHandler.Stop();
if (webSocket.ReadyState != WebSocketState.Closed)
webSocket?.Close(CloseStatusCode.Normal);
}
/// <summary>
/// Serialises and sends a json object
/// </summary>
/// <typeparam name="T">Type to serialise as</typeparam>
/// <param name="data">Data to serialise and send</param>
public void Send<T>(T data)
{
/*Console.WriteLine(
JsonConvert.SerializeObject(
data,
typeof(T),
new JsonSerializerSettings()
).Replace(client.Token, new string('*', client.Token.Length)));*/
webSocket.Send(
JsonConvert.SerializeObject(
data,
typeof(T),
new JsonSerializerSettings()
)
);
}
/// <summary>
/// Serialises and sends a Gateway Payload
/// </summary>
/// <param name="opcode">Opcode to use</param>
/// <param name="data">Data to send</param>
public void Send(GatewayOPCode opcode, object data)
{
Send(new GatewayPayload
{
OPCode = opcode,
Data = data
});
}
}
}

View file

@ -0,0 +1,299 @@
using Maki.Structures.Channels;
using Maki.Structures.Gateway;
using Maki.Structures.Guilds;
using Maki.Structures.Messages;
using Maki.Structures.Presences;
using Maki.Structures.Users;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Maki.Gateway
{
/// <summary>
/// Gateway connection shard manager
/// </summary>
class GatewayShardClient : IDisposable
{
/// <summary>
/// Managed (active) shards
/// </summary>
private readonly Dictionary<int, GatewayShard> shards = new Dictionary<int, GatewayShard>();
/// <summary>
/// Parent DiscordClient instance
/// </summary>
private Discord client;
/// <summary>
/// Number of active managed shards
/// </summary>
public int ShardCount => shards.Count;
/// <summary>
/// Constructor
/// </summary>
/// <param name="c">Parent DiscordClient instance</param>
public GatewayShardClient(Discord c)
{
client = c;
}
/// <summary>
/// Creates a new Gateway Shard
/// </summary>
/// <param name="id">Id of the shard</param>
/// <returns>New Gateway Shard</returns>
public GatewayShard Create(int id)
{
GatewayShard shard = new GatewayShard(id, client);
ApplyEvents(shard);
shards.Add(id, shard);
return shard;
}
/// <summary>
/// Destroys a Gateway Shard using its id
/// </summary>
/// <param name="id">Id of the shard to destroy</param>
public void Destroy(int id) => Destroy(shards[id]);
/// <summary>
/// Destroys a Gateway Shard
/// </summary>
/// <param name="shard">Shard to destroy</param>
public void Destroy(GatewayShard shard)
{
shards.Remove(shard.Id);
// Disconnect and Dispose are called separately so OnSocketClose is called properly
shard.Disconnect();
RemoveEvents(shard);
shard.Dispose();
}
/// <summary>
/// Destroys all shards
/// </summary>
public void Disconnect() => shards.Keys.ToList().ForEach(x => Destroy(x));
/// <summary>
/// Links all gateway events handlers to the shard
/// </summary>
/// <param name="shard">Shard to link events to</param>
private void ApplyEvents(GatewayShard shard)
{
shard.OnCallCreate += OnCallCreate;
shard.OnCallDelete += OnCallDelete;
shard.OnCallUpdate += OnCallUpdate;
shard.OnVoiceServerUpdate += OnVoiceServerUpdate;
shard.OnVoiceStateUpdate += OnVoiceStateUpdate;
shard.OnChannelCreate += OnChannelCreate;
shard.OnChannelDelete += OnChannelDelete;
shard.OnChannelPinsAck += OnChannelPinsAck;
shard.OnChannelPinsUpdate += OnChannelPinsUpdate;
shard.OnChannelRecipientAdd += OnChannelRecipientAdd;
shard.OnChannelRecipientRemove += OnChannelRecipientRemove;
shard.OnChannelUpdate += OnChannelUpdate;
shard.OnGuildBanAdd += OnGuildBanAdd;
shard.OnGuildBanRemove += OnGuildBanRemove;
shard.OnGuildCreate += OnGuildCreate;
shard.OnGuildDelete += OnGuildDelete;
shard.OnGuildEmojisUpdate += OnGuildEmojisUpdate;
shard.OnGuildIntegrationsUpdate += OnGuildIntegrationsUpdate;
shard.OnGuildMemberAdd += OnGuildMemberAdd;
shard.OnGuildMemberUpdate += OnGuildMemberUpdate;
shard.OnGuildMemberRemove += OnGuildMemberRemove;
shard.OnGuildMembersChunk += OnGuildMembersChunk;
shard.OnGuildRoleCreate += OnGuildRoleCreate;
shard.OnGuildRoleUpdate += OnGuildRoleUpdate;
shard.OnGuildRoleDelete += OnGuildRoleDelete;
shard.OnGuildUpdate += OnGuildUpdate;
shard.OnMessageAck += OnMessageAck;
shard.OnMessageCreate += OnMessageCreate;
shard.OnMessageDelete += OnMessageDelete;
shard.OnMessageDeleteBulk += OnMessageDeleteBulk;
shard.OnMessageReactionAdd += OnMessageReactionAdd;
shard.OnMessageReactionRemove += OnMessageReactionRemove;
shard.OnMessageReactionsRemoveAll += OnMessageReactionsRemoveAll;
shard.OnMessageUpdate += OnMessageUpdate;
shard.OnTypingStart += OnTypingStart;
shard.OnPresenceUpdate += OnPresenceUpdate;
shard.OnPresencesReplace += OnPresencesReplace;
shard.OnFriendSuggestionDelete += OnFriendSuggestionDelete;
shard.OnRelationshipAdd += OnRelationshipAdd;
shard.OnRelationshipRemove += OnRelationshipRemove;
shard.OnReady += OnReady;
shard.OnResumed += OnResumed;
shard.OnUserUpdate += OnUserUpdate;
shard.OnUserNoteUpdate += OnUserNoteUpdate;
shard.OnUserSettingsUpdate += OnUserSettingsUpdate;
shard.OnSocketOpen += OnSocketOpen;
shard.OnSocketClose += OnSocketClose;
shard.OnSocketError += OnSocketError;
shard.OnSocketMessage += OnSocketMessage;
}
/// <summary>
/// Unlinks all gateway events handlers from the shard
/// </summary>
/// <param name="shard">Shard to unlink events from</param>
public void RemoveEvents(GatewayShard shard)
{
shard.OnCallCreate -= OnCallCreate;
shard.OnCallDelete -= OnCallDelete;
shard.OnCallUpdate -= OnCallUpdate;
shard.OnVoiceServerUpdate -= OnVoiceServerUpdate;
shard.OnVoiceStateUpdate -= OnVoiceStateUpdate;
shard.OnChannelCreate -= OnChannelCreate;
shard.OnChannelDelete -= OnChannelDelete;
shard.OnChannelPinsAck -= OnChannelPinsAck;
shard.OnChannelPinsUpdate -= OnChannelPinsUpdate;
shard.OnChannelRecipientAdd -= OnChannelRecipientAdd;
shard.OnChannelRecipientRemove -= OnChannelRecipientRemove;
shard.OnChannelUpdate -= OnChannelUpdate;
shard.OnGuildBanAdd -= OnGuildBanAdd;
shard.OnGuildBanRemove -= OnGuildBanRemove;
shard.OnGuildCreate -= OnGuildCreate;
shard.OnGuildDelete -= OnGuildDelete;
shard.OnGuildEmojisUpdate -= OnGuildEmojisUpdate;
shard.OnGuildIntegrationsUpdate -= OnGuildIntegrationsUpdate;
shard.OnGuildMemberAdd -= OnGuildMemberAdd;
shard.OnGuildMemberUpdate -= OnGuildMemberUpdate;
shard.OnGuildMemberRemove -= OnGuildMemberRemove;
shard.OnGuildMembersChunk -= OnGuildMembersChunk;
shard.OnGuildRoleCreate -= OnGuildRoleCreate;
shard.OnGuildRoleUpdate -= OnGuildRoleUpdate;
shard.OnGuildRoleDelete -= OnGuildRoleDelete;
shard.OnGuildUpdate -= OnGuildUpdate;
shard.OnMessageAck -= OnMessageAck;
shard.OnMessageCreate -= OnMessageCreate;
shard.OnMessageDelete -= OnMessageDelete;
shard.OnMessageDeleteBulk -= OnMessageDeleteBulk;
shard.OnMessageReactionAdd -= OnMessageReactionAdd;
shard.OnMessageReactionRemove -= OnMessageReactionRemove;
shard.OnMessageReactionsRemoveAll -= OnMessageReactionsRemoveAll;
shard.OnMessageUpdate -= OnMessageUpdate;
shard.OnTypingStart -= OnTypingStart;
shard.OnPresenceUpdate -= OnPresenceUpdate;
shard.OnPresencesReplace -= OnPresencesReplace;
shard.OnFriendSuggestionDelete -= OnFriendSuggestionDelete;
shard.OnRelationshipAdd -= OnRelationshipAdd;
shard.OnRelationshipRemove -= OnRelationshipRemove;
shard.OnReady -= OnReady;
shard.OnResumed -= OnResumed;
shard.OnUserUpdate -= OnUserUpdate;
shard.OnUserNoteUpdate -= OnUserNoteUpdate;
shard.OnUserSettingsUpdate -= OnUserSettingsUpdate;
shard.OnSocketOpen -= OnSocketOpen;
shard.OnSocketClose -= OnSocketClose;
shard.OnSocketError -= OnSocketError;
shard.OnSocketMessage -= OnSocketMessage;
}
#region Events
public event Action<GatewayShard> OnCallCreate;
public event Action<GatewayShard> OnCallDelete;
public event Action<GatewayShard> OnCallUpdate;
public event Action<GatewayShard> OnVoiceServerUpdate;
public event Action<GatewayShard> OnVoiceStateUpdate;
public event Action<GatewayShard, Channel> OnChannelCreate;
public event Action<GatewayShard, Channel> OnChannelDelete;
public event Action<GatewayShard> OnChannelPinsAck;
public event Action<GatewayShard> OnChannelPinsUpdate;
public event Action<GatewayShard> OnChannelRecipientAdd;
public event Action<GatewayShard> OnChannelRecipientRemove;
public event Action<GatewayShard, Channel> OnChannelUpdate;
public event Action<GatewayShard, User> OnGuildBanAdd;
public event Action<GatewayShard, User> OnGuildBanRemove;
public event Action<GatewayShard, Guild> OnGuildCreate;
public event Action<GatewayShard, Guild> OnGuildDelete;
public event Action<GatewayShard, Guild> OnGuildEmojisUpdate;
public event Action<GatewayShard, GuildIntegration> OnGuildIntegrationsUpdate;
public event Action<GatewayShard, GuildMember> OnGuildMemberAdd;
public event Action<GatewayShard, GuildMember> OnGuildMemberUpdate;
public event Action<GatewayShard, GuildMember> OnGuildMemberRemove;
public event Action<GatewayShard, GuildMembersChunk> OnGuildMembersChunk;
public event Action<GatewayShard, GuildRole> OnGuildRoleCreate;
public event Action<GatewayShard, GuildRole> OnGuildRoleUpdate;
public event Action<GatewayShard, GuildRole> OnGuildRoleDelete;
public event Action<GatewayShard, Guild> OnGuildUpdate;
public event Action<GatewayShard> OnMessageAck;
public event Action<GatewayShard, Message> OnMessageCreate;
public event Action<GatewayShard, Message> OnMessageDelete;
public event Action<GatewayShard> OnMessageDeleteBulk;
public event Action<GatewayShard> OnMessageReactionAdd;
public event Action<GatewayShard> OnMessageReactionRemove;
public event Action<GatewayShard> OnMessageReactionsRemoveAll;
public event Action<GatewayShard, Message> OnMessageUpdate;
public event Action<GatewayShard, TypingStart> OnTypingStart;
public event Action<GatewayShard, Presence> OnPresenceUpdate;
public event Action<GatewayShard> OnPresencesReplace;
public event Action<GatewayShard> OnFriendSuggestionDelete;
public event Action<GatewayShard> OnRelationshipAdd;
public event Action<GatewayShard> OnRelationshipRemove;
public event Action<GatewayShard, GatewayReady> OnReady;
public event Action<GatewayShard> OnResumed;
public event Action<GatewayShard, User> OnUserUpdate;
public event Action<GatewayShard> OnUserNoteUpdate;
public event Action<GatewayShard> OnUserSettingsUpdate;
public event Action<GatewayShard> OnSocketOpen;
public event Action<GatewayShard, bool, ushort, string> OnSocketClose;
public event Action<GatewayShard, Exception> OnSocketError;
public event Action<GatewayShard, string> OnSocketMessage;
#endregion
#region IDisposable
private bool IsDisposed = false;
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
IsDisposed = true;
Disconnect();
}
}
~GatewayShardClient()
{
Dispose(false);
}
/// <summary>
/// Releases all unmanaged resources used by this object
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
#endregion
}
}

View file

@ -4,16 +4,16 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4DC6C0A6-520E-4A0A-9A47-3515458B44A2}</ProjectGuid>
<OutputType>Exe</OutputType>
<ProjectGuid>{97523AED-B694-42C2-96AC-86A1D65109F7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Maki</RootNamespace>
<AssemblyName>Maki</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
@ -21,18 +21,109 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>0649</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>0649</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Include="App.config" />
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="websocket-sharp, Version=1.0.2.59611, Culture=neutral, PublicKeyToken=5660b08a1845a91e, processorArchitecture=MSIL">
<HintPath>..\packages\WebSocketSharp.1.0.3-rc11\lib\websocket-sharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DiscordChannel.cs" />
<Compile Include="Discord.cs" />
<Compile Include="DiscordColour.cs" />
<Compile Include="DiscordEmbed.cs" />
<Compile Include="DiscordEmbedAuthor.cs" />
<Compile Include="DiscordEmbedField.cs" />
<Compile Include="DiscordEmbedFooter.cs" />
<Compile Include="DiscordEmbedImage.cs" />
<Compile Include="DiscordMember.cs" />
<Compile Include="DiscordMessage.cs" />
<Compile Include="DiscordRole.cs" />
<Compile Include="DiscordServer.cs" />
<Compile Include="DiscordUser.cs" />
<Compile Include="Gateway\GatewayEvent.cs" />
<Compile Include="Rest\RestErrorCode.cs" />
<Compile Include="Rest\RestRequestMethod.cs" />
<Compile Include="Rest\RestClient.cs" />
<Compile Include="Rest\RestEndpoints.cs" />
<Compile Include="Gateway\GatewayHeartbeatManager.cs" />
<Compile Include="Gateway\GatewayOPCode.cs" />
<Compile Include="DiscordPermission.cs" />
<Compile Include="DiscordTokenType.cs" />
<Compile Include="Gateway\GatewayCloseCode.cs" />
<Compile Include="Rest\RestResponse.cs" />
<Compile Include="Structures\Auth\LoginMultiFactorAuth.cs" />
<Compile Include="Structures\Auth\LoginRequest.cs" />
<Compile Include="Structures\Auth\LoginResponse.cs" />
<Compile Include="Structures\Channels\Channel.cs" />
<Compile Include="Structures\Channels\ChannelType.cs" />
<Compile Include="Structures\Embeds\Embed.cs" />
<Compile Include="Structures\Embeds\EmbedAuthor.cs" />
<Compile Include="Structures\Embeds\EmbedField.cs" />
<Compile Include="Structures\Embeds\EmbedFooter.cs" />
<Compile Include="Structures\Embeds\EmbedImage.cs" />
<Compile Include="Structures\Embeds\EmbedProvider.cs" />
<Compile Include="Structures\Embeds\EmbedVideo.cs" />
<Compile Include="Structures\Gateway\GatewayHello.cs" />
<Compile Include="Structures\Gateway\GatewayIdentification.cs" />
<Compile Include="Structures\Gateway\GatewayIdentificationProperties.cs" />
<Compile Include="Structures\Gateway\GatewayReady.cs" />
<Compile Include="Gateway\GatewayShard.cs" />
<Compile Include="Gateway\GatewayShardClient.cs" />
<Compile Include="Structures\Gateway\GatewayInfo.cs" />
<Compile Include="Structures\Gateway\GatewayPayload.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Structures\Gateway\GatewayRequestGuildMembers.cs" />
<Compile Include="Structures\Gateway\GatewayResume.cs" />
<Compile Include="Structures\Gateway\GatewayStatusUpdate.cs" />
<Compile Include="Structures\Gateway\GatewayVoiceStateUpdate.cs" />
<Compile Include="Structures\Guilds\Guild.cs" />
<Compile Include="Structures\Guilds\GuildWidget.cs" />
<Compile Include="Structures\Guilds\GuildIntegration.cs" />
<Compile Include="Structures\Guilds\GuildIntegrationAccount.cs" />
<Compile Include="Structures\Guilds\GuildMember.cs" />
<Compile Include="Structures\Guilds\GuildMembersChunk.cs" />
<Compile Include="Structures\Guilds\GuildRole.cs" />
<Compile Include="Structures\Messages\Attachment.cs" />
<Compile Include="Structures\Messages\Emoji.cs" />
<Compile Include="Structures\Messages\Message.cs" />
<Compile Include="Structures\Messages\MessageReaction.cs" />
<Compile Include="Structures\Presences\Game.cs" />
<Compile Include="Structures\Presences\GameType.cs" />
<Compile Include="Structures\Presences\Presence.cs" />
<Compile Include="Structures\Presences\TypingStart.cs" />
<Compile Include="Structures\Rest\Error.cs" />
<Compile Include="Structures\Rest\MessageCreate.cs" />
<Compile Include="Structures\Rest\RoleCreate.cs" />
<Compile Include="Structures\Roles\Role.cs" />
<Compile Include="Structures\Users\PermissionOverwrite.cs" />
<Compile Include="Structures\Users\User.cs" />
<Compile Include="Utility.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Maki")]
[assembly: AssemblyDescription("Discord Client library")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Maki")]
[assembly: AssemblyCopyright("flash.moe 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("97523aed-b694-42c2-96ac-86a1d65109f7")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

150
Maki/Rest/RestClient.cs Normal file
View file

@ -0,0 +1,150 @@
using Maki.Structures.Rest;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
namespace Maki.Rest
{
/// <summary>
/// Handles requests to Discord's REST API
/// </summary>
internal class RestClient
{
/// <summary>
/// User agent that is send alongside requests
/// </summary>
private const string USER_AGENT = @"DiscordBot (https://github.com/flashwave/nicobot)";
/// <summary>
/// Container for parent DiscordClient instance
/// </summary>
private readonly Discord client;
/// <summary>
/// Request types that should be handled as data requests
/// </summary>
private readonly RestRequestMethod[] dataRequestTypes = new RestRequestMethod[] {
RestRequestMethod.POST,
RestRequestMethod.PUT,
RestRequestMethod.PATCH,
};
/// <summary>
/// Constructor
/// </summary>
/// <param name="c">Parent DiscordClient instance</param>
public RestClient(Discord c)
{
client = c;
}
/// <summary>
/// Creates a base HttpWebRequest
/// </summary>
/// <param name="method">Request method</param>
/// <param name="url">Endpoint url</param>
/// <returns>Prepared HttpWebRequest</returns>
private HttpWebRequest BaseRequest(RestRequestMethod method, string url)
{
Uri uri = new Uri(RestEndpoints.BASE_URL + RestEndpoints.BASE_PATH + url);
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
request.Method = method.ToString();
request.UserAgent = USER_AGENT;
if (!string.IsNullOrEmpty(client.Token))
request.Headers[HttpRequestHeader.Authorization] = (client.IsBot ? "Bot " : string.Empty) + client.Token;
return request;
}
/// <summary>
/// Gets a response string from HttpWebRequest
/// </summary>
/// <param name="request">HttpWebRequest instance</param>
/// <returns>string output</returns>
private RestResponse<T> HandleResponse<T>(HttpWebRequest request)
{
HttpWebResponse webResponse;
RestResponse<T> response = new RestResponse<T>();
try
{
webResponse = request.GetResponse() as HttpWebResponse;
}
catch (WebException ex)
{
webResponse = ex.Response as HttpWebResponse;
}
Enum.TryParse(webResponse.Method, out response.Method);
response.Uri = webResponse.ResponseUri;
response.Status = (ushort)webResponse.StatusCode;
using (Stream httpResponseStream = webResponse.GetResponseStream())
using (StreamReader streamReader = new StreamReader(httpResponseStream))
response.RawResponse = streamReader.ReadToEnd();
webResponse.Close();
Error error = default(Error);
try
{
error = JsonConvert.DeserializeObject<Error>(response.RawResponse);
}
catch
{
}
if (error.Equals(default(Error)))
{
response.ErrorCode = RestErrorCode.Ok;
response.ErrorMessage = string.Empty;
}
else
{
response.ErrorCode = (RestErrorCode)error.Code;
response.ErrorMessage = error.Message;
}
Console.WriteLine(response.RawResponse);
if (response.Status == 200)
response.Response = JsonConvert.DeserializeObject<T>(response.RawResponse);
return response;
}
/// <summary>
/// Make a json request
/// </summary>
/// <typeparam name="T">Type to use when deserialising the JSON object</typeparam>
/// <param name="method">Request method</param>
/// <param name="url">Endpoint url</param>
/// <param name="data">Request data</param>
/// <returns>Deserialised JSON object</returns>
public RestResponse<T> Request<T>(RestRequestMethod method, string url, object data = null)
{
HttpWebRequest request = BaseRequest(method, url);
request.ContentType = "application/json;charset=utf-8";
request.ContentLength = 0;
if (dataRequestTypes.Contains(method) && data != null)
{
byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
request.ContentLength = bytes.LongLength;
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(bytes, 0, bytes.Length);
}
return HandleResponse<T>(request);
}
}
}

470
Maki/Rest/RestEndpoints.cs Normal file
View file

@ -0,0 +1,470 @@
namespace Maki.Rest
{
/// <summary>
/// REST API Endpoint Formatter
/// </summary>
static class RestEndpoints
{
/// <summary>
/// Base URL of Discord
/// </summary>
public const string BASE_URL = @"https://discordapp.com";
/// <summary>
/// Path to the REST API
/// </summary>
public static string BASE_PATH => @"/api/v" + Discord.GATEWAY_VERSION;
/// <summary>
/// Url of Discord's CDN
/// </summary>
public const string CDN_URL = @"https://cdn.discordapp.com";
#region Channels
/// <summary>
/// Gets a Channel endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel endpoint</returns>
public static string Channel(ulong chanId) => $@"/channels/{chanId}";
/// <summary>
/// Gets a Channel Bulk Delete endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Bulk Delete endpoint</returns>
public static string ChannelBulkDelete(ulong chanId) => $@"/channels/{chanId}/messages/bulk_delete";
/// <summary>
/// Gets a Channel Call Ring endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Call Ring endpoint</returns>
public static string ChannelCallRing(ulong chanId) => $@"/channels/{chanId}/call/ring";
/// <summary>
/// Gets a Channel Invites endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Call Ring endpoint</returns>
public static string ChannelInvites(ulong chanId) => $@"/channels/{chanId}/invites";
/// <summary>
/// Gets a Channel Message Reaction endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <param name="msgId">Message Id</param>
/// <param name="reaction">Reaction Emoji</param>
/// <returns>Channel Message Reaction endpoint</returns>
public static string ChannelMessageReaction(ulong chanId, ulong msgId, string reaction) => $@"/channels/{chanId}/messages/{msgId}/reactions/{reaction}";
/// <summary>
/// Gets a Channel Message Reaction User endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <param name="msgId">Message Id</param>
/// <param name="reaction">Reaction Emoji</param>
/// <param name="userId">User Id</param>
/// <returns>Channel Message Reaction User endpoint</returns>
public static string ChannelMessageReactionUser(ulong chanId, ulong msgId, string reaction, ulong userId) => $@"/channels/{chanId}/messages/{msgId}/reactions/{reaction}/{userId}";
/// <summary>
/// Gets a Channel Message Reactions endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <param name="msgId">Message Id</param>
/// <returns>Channel Message Reactions endpoint</returns>
public static string ChannelMessageReactions(ulong chanId, ulong msgId) => $@"/channels/{chanId}/messages/{msgId}/reactions";
/// <summary>
/// Gets a Channel Message endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <param name="msgId">Message Id</param>
/// <returns>Channel Message endpoint</returns>
public static string ChannelMessage(ulong chanId, ulong msgId) => $@"/channels/{chanId}/messages/{msgId}";
/// <summary>
/// Gets a Channel Messages endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Messages endpoint</returns>
public static string ChannelMessages(ulong chanId) => $@"/channels/{chanId}/messages";
/// <summary>
/// Gets a Channel Messages Search endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Messages Search endpoint</returns>
public static string ChannelMessagesSearch(ulong chanId) => $@"/channels/{chanId}/messages/search";
/// <summary>
/// Gets a Channel Permission endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <param name="overId">Overwrite Id</param>
/// <returns>Channel Permission endpoint</returns>
public static string ChannelPermission(ulong chanId, ulong overId) => $@"/channels/{chanId}/permissions/{overId}";
/// <summary>
/// Gets a Channel Permissions endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Permissions endpoint</returns>
public static string ChannelPermissions(ulong chanId) => $@"/channels/{chanId}/permissions";
/// <summary>
/// Gets a Channel Pin endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <param name="msgId">Message Id</param>
/// <returns>Channel Pin endpoint</returns>
public static string ChannelPin(ulong chanId, ulong msgId) => $@"/channels/{chanId}/pins/{msgId}";
/// <summary>
/// Gets a Channel Pins endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Pins endpoint</returns>
public static string ChannelPins(ulong chanId) => $@"/channels/{chanId}/pins";
/// <summary>
/// Gets a Channel Recipient endpoint
/// </summary>
/// <param name="groupId">Group (DM Channel) Id</param>
/// <param name="userId">Recipient (User) Id</param>
/// <returns>Channel Recipient endpoint</returns>
public static string ChannelRecipient(ulong groupId, ulong userId) => $@"/channels/{groupId}/recipients/{userId}";
/// <summary>
/// Gets a Channel Typing endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Typing endpoint</returns>
public static string ChannelTyping(ulong chanId) => $@"/channels/{chanId}/typing";
/// <summary>
/// Gets a Channel Webhooks endpoint
/// </summary>
/// <param name="chanId">Channel Id</param>
/// <returns>Channel Webhooks endpoint</returns>
public static string ChannelWebhooks(ulong chanId) => $@"/channels/{chanId}/webhooks";
/// <summary>
/// Channels endpoint
/// </summary>
public static string Channels => @"/channels";
#endregion
#region Gateway
/// <summary>
/// Gateway endpoint
/// </summary>
public static string Gateway => @"/gateway";
/// <summary>
/// Bot Gateway endpoint
/// </summary>
public static string GatewayBot => @"/gateway/bot";
#endregion
#region Guild
/// <summary>
/// Gets a Guild endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild endpoint</returns>
public static string Guild(ulong guildId) => $@"/guilds/{guildId}";
/// <summary>
/// Gets a Guild Ban endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="memberId">Member (banned user) Id</param>
/// <returns>Guild Ban endpoint</returns>
public static string GuildBan(ulong guildId, ulong memberId) => $@"/guilds/{guildId}/bans/{memberId}";
/// <summary>
/// Gets a Guild Bans endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Bans endpoint</returns>
public static string GuildBans(ulong guildId) => $@"/guilds/{guildId}/bans";
/// <summary>
/// Gets a Guild Channels endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Channels endpoint</returns>
public static string GuildChannels(ulong guildId) => $@"/guilds/{guildId}/channels";
/// <summary>
/// Gets a Guild Embed endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Embed endpoint</returns>
public static string GuildEmbed(ulong guildId) => $@"/guilds/{guildId}/embed";
/// <summary>
/// Gets a Guild Emoji endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="emojiId">Emoji Id</param>
/// <returns>Guild Emoji endpoint</returns>
public static string GuildEmoji(ulong guildId, ulong emojiId) => $@"/guilds/{guildId}/emojis/{emojiId}";
/// <summary>
/// Gets a Guild Emojis endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Emojis endpoint</returns>
public static string GuildEmojis(ulong guildId) => $@"/guilds/{guildId}/emojis";
/// <summary>
/// Gets a Guild Integration endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="intId">Integration Id</param>
/// <returns>Guild Integration endpoint</returns>
public static string GuildIntegration(ulong guildId, ulong intId) => $@"/guilds/{guildId}/integrations/{intId}";
/// <summary>
/// Gets a Guild Integration Sync endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="intId">Integration Id</param>
/// <returns>Guild Integration Sync endpoint</returns>
public static string GuildIntegrationSync(ulong guildId, ulong intId) => $@"/guilds/{guildId}/integrations/{intId}/sync";
/// <summary>
/// Gets a Guild Integrations endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Integrations endpoint</returns>
public static string GuildIntegrations(ulong guildId) => $@"/guilds/{guildId}/integrations";
/// <summary>
/// Gets a Guild Invites endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Invites endpoint</returns>
public static string GuildInvites(ulong guildId) => $@"/guilds/{guildId}/invites";
/// <summary>
/// Gets a Guild Member endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="memberId">Member (User) Id</param>
/// <returns>Guild Member endpoint</returns>
public static string GuildMember(ulong guildId, ulong memberId) => $@"/guilds/{guildId}/members/{memberId}";
/// <summary>
/// Gets a Guild Member Nickname endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="memberId">Member (User) Id</param>
/// <returns>Guild Member Nickname endpoint</returns>
public static string GuildMemberNick(ulong guildId, ulong memberId) => $@"/guilds/{guildId}/members/{memberId}/nick";
/// <summary>
/// Gets a Guild Member Role endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="memberId">Member (User) Id</param>
/// <param name="roleId">Role Id</param>
/// <returns>Guild Member Role endpoint</returns>
public static string GuildMemberRole(ulong guildId, ulong memberId, ulong roleId) => $@"/guilds/{guildId}/members/{memberId}/roles/{roleId}";
/// <summary>
/// Gets a Guild Members endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Members endpoint</returns>
public static string GuildMembers(ulong guildId) => $@"/guilds/{guildId}/members";
/// <summary>
/// Gets a Guild Messages Search endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Messages Search endpoint</returns>
public static string GuildMessagesSearch(ulong guildId) => $@"/guilds/{guildId}/messages/search";
/// <summary>
/// Gets a Guild Prune endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Prune endpoint</returns>
public static string GuildPrune(ulong guildId) => $@"/guilds/{guildId}/prune";
/// <summary>
/// Gets a Guild Role endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <param name="roleId">Role Id</param>
/// <returns>Guild Role endpoint</returns>
public static string GuildRole(ulong guildId, ulong roleId) => $@"/guilds/{guildId}/roles/{roleId}";
/// <summary>
/// Gets a Guild Roles endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Roles endpoint</returns>
public static string GuildRoles(ulong guildId) => $@"/guilds/{guildId}/roles";
/// <summary>
///
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns></returns>
public static string GuildVoiceRegions(ulong guildId) => $@"/guilds/{guildId}/regions";
/// <summary>
/// Gets a Guild Webhooks endpoint
/// </summary>
/// <param name="guildId">Guild Id</param>
/// <returns>Guild Webhooks endpoint</returns>
public static string GuildWebhooks(ulong guildId) => $@"/guilds/{guildId}/webhooks";
/// <summary>
/// Guild endpoint
/// </summary>
public static string Guilds => @"/guilds";
#endregion
#region Invite
/// <summary>
/// Gets an Invite endpoint
/// </summary>
/// <param name="inviteId">Invite Id</param>
/// <returns>Invite endpoint</returns>
public static string Invite(ulong inviteId) => $@"/invite/{inviteId}";
#endregion
#region OAuth2
/// <summary>
/// Gets an OAuth2 Application endpoint
/// </summary>
/// <param name="appId">OAuth2 Application Id</param>
/// <returns>OAuth2 Application endpoint</returns>
public static string OAuth2Application(ulong appId) => $@"/oauth2/applications/{appId}";
#endregion
#region Auth
/// <summary>
/// Auth Login endpoint
/// </summary>
public static string AuthLogin => @"/auth/login";
/// <summary>
/// Auth MFA TOTP endpoint
/// </summary>
public static string AuthMfaTotp => @"/auth/mfa/totp";
#endregion
#region User
/// <summary>
/// Gets a User endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <returns>User endpoint</returns>
public static string User(ulong userId) => $@"/users/{userId}";
/// <summary>
/// Gets a User Channels endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <returns>User Channels endpoint</returns>
public static string UserChannels(ulong userId) => $@"/users/{userId}/channels";
/// <summary>
/// Gets a User Guild endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <param name="guildId">Guild Id</param>
/// <returns>User Guild endpoint</returns>
public static string UserGuild(ulong userId, ulong guildId) => $@"/users/{userId}/guilds/{guildId}";
/// <summary>
/// Gets a User Guilds endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <returns>User Guilds endpoint</returns>
public static string UserGuilds(ulong userId) => $@"/users/{userId}/guilds";
/// <summary>
/// Gets a User Note endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <param name="targetId">Target (User) Id</param>
/// <returns>User Note endpoint</returns>
public static string UserNote(ulong userId, ulong targetId) => $@"/users/{userId}/note/{targetId}";
/// <summary>
/// Gets a User Profile endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <returns>User Profile endpoint</returns>
public static string UserProfile(ulong userId) => $@"/users/{userId}/profile";
/// <summary>
/// Gets a User Relationship endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <param name="targetId">Target (User) Id</param>
/// <returns>User Relationship endpoint</returns>
public static string UserRelationship(ulong userId, ulong targetId) => $@"/users/{userId}/relationships/{targetId}";
/// <summary>
/// Gets a User Settings endpoint
/// </summary>
/// <param name="userId">User Id</param>
/// <returns>User Settings endpoint</returns>
public static string UserSettings(ulong userId) => $@"/users/{userId}/settings";
/// <summary>
/// Users endpoint
/// </summary>
public static string Users => @"/users";
#endregion
#region Voice
/// <summary>
/// Voice Regions endpoint
/// </summary>
public static string VoiceRegions => @"/voice/regions";
#endregion
#region Webhooks
/// <summary>
/// Gets a Webhook endpoint
/// </summary>
/// <param name="hookId">Webhook Id</param>
/// <returns>Webhook endpoint</returns>
public static string Webhook(ulong hookId) => $@"/webhooks/{hookId}";
/// <summary>
/// Gets a Webhook endpoint with token
/// </summary>
/// <param name="hookId">Webhook Id</param>
/// <param name="token">Webhook Token</param>
/// <returns>Webhook endpoint with token</returns>
public static string WebhookToken(ulong hookId, string token) => $@"/webhooks/{hookId}/{token}";
/// <summary>
/// Gets a Webhook endpoint with token for GitHub
/// </summary>
/// <param name="hookId">Webhook Id</param>
/// <param name="token">Webhook Token</param>
/// <returns>Webhook endpoint with token for GitHub</returns>
public static string WebhookTokenGitHub(ulong hookId, string token) => $@"/webhooks/{hookId}/{token}/github";
/// <summary>
/// Gets a Webhook endpoint with token for Slack
/// </summary>
/// <param name="hookId">Webhook Id</param>
/// <param name="token">Webhook Token</param>
/// <returns>Webhook endpoint with token for Slack</returns>
public static string WebhookTokenSlack(ulong hookId, string token) => $@"/webhooks/{hookId}/{token}/slack";
#endregion
}
}

230
Maki/Rest/RestErrorCode.cs Normal file
View file

@ -0,0 +1,230 @@
namespace Maki.Rest
{
/// <summary>
/// Discord Rest API error codes
/// </summary>
enum RestErrorCode
{
/// <summary>
/// Specific to this library, used when there is no error
/// </summary>
Ok = 0,
#region 1xxxx
/// <summary>
/// Unknown account
/// </summary>
UnknownAccount = 10001,
/// <summary>
/// Unknown application
/// </summary>
UnknownApplication = 10002,
/// <summary>
/// Unknown channel
/// </summary>
UnknownChannel = 10003,
/// <summary>
/// Unknown guild
/// </summary>
UnknownGuild = 10004,
/// <summary>
/// Unknown integration
/// </summary>
UnknownIntegration = 10005,
/// <summary>
/// Unknown invite
/// </summary>
UnknownInvite = 10006,
/// <summary>
/// Unknown member
/// </summary>
UnknownMember = 10007,
/// <summary>
/// Unknown message
/// </summary>
UnknownMessage = 10008,
/// <summary>
/// Unknown overwrite
/// </summary>
UnknownOverwrite = 10009,
/// <summary>
/// Unknown provider
/// </summary>
UnknownProvider = 10010,
/// <summary>
/// Unknown role
/// </summary>
UnknownRole = 10011,
/// <summary>
/// Unknown token
/// </summary>
UnknownToken = 10012,
/// <summary>
/// Unknown user
/// </summary>
UnknownUser = 10013,
/// <summary>
/// Unknown Emoji
/// </summary>
UnknownEmoji = 10014,
#endregion
#region 2xxxx
/// <summary>
/// Bots cannot use this endpoint
/// </summary>
UserOnlyEndpoint = 20001,
/// <summary>
/// Only bots can use this endpoint
/// </summary>
BotOnlyEndpoint = 20002,
#endregion
#region 3xxxx
/// <summary>
/// Maximum number of guilds reached (100)
/// </summary>
MaximumGuildsReached = 30001,
/// <summary>
/// Maximum number of friends reached (1000)
/// </summary>
MaximumFriendsReached = 30002,
/// <summary>
/// Maximum number of pins reached (50)
/// </summary>
MaximumPinsReached = 30003,
/// <summary>
/// Maximum number of guild roles reached (250)
/// </summary>
MaximumRolesReached = 30005,
/// <summary>
/// Too many reactions
/// </summary>
TooManyReactions = 30010,
#endregion
#region 4xxxx
/// <summary>
/// Unauthorized
/// </summary>
Unauthorised = 40001,
#endregion
#region 5xxxx
/// <summary>
/// Missing access
/// </summary>
MissingAccess = 50001,
/// <summary>
/// Invalid account type
/// </summary>
InvalidAccountType = 50002,
/// <summary>
/// Cannot execute action on a DM channel
/// </summary>
CannotExecuteInDM = 50003,
/// <summary>
/// Embed disabled
/// </summary>
EmbedDisabled = 50004,
/// <summary>
/// Cannot edit a message authored by another user
/// </summary>
CannotEditForeignMessages = 50005,
/// <summary>
/// Cannot send an empty message
/// </summary>
CannotSendEmptyMessage = 50006,
/// <summary>
/// Cannot send messages to this user
/// </summary>
CannotSendToUser = 50007,
/// <summary>
/// Cannot send messages in a voice channel
/// </summary>
CannotSendTextInVoiceChannel = 50008,
/// <summary>
/// Channel verification level is too high
/// </summary>
ChannelVerificationTooHigh = 50009,
/// <summary>
/// OAuth2 application does not have a bot
/// </summary>
AppDoesNotHaveBot = 50010,
/// <summary>
/// OAuth2 application limit reached
/// </summary>
AppLimitReached = 50011,
/// <summary>
/// Invalid OAuth state
/// </summary>
InvalidOAuthState = 50012,
/// <summary>
/// Missing permissions
/// </summary>
MissingPermissions = 50013,
/// <summary>
/// Invalid authentication token
/// </summary>
InvalidAuthenticationToken = 50014,
/// <summary>
/// Note is too long
/// </summary>
NoteTooLong = 50015,
/// <summary>
/// Provided too few or too many messages to delete. Must provide at least 2 and fewer than 100 messages to delete.
/// </summary>
TooFewOrManyMessageDeletes = 50016,
/// <summary>
/// A message can only be pinned to the channel it was sent in
/// </summary>
MessagePinInOriginChannelOnly = 50019,
/// <summary>
/// A message provided was too old to bulk delete
/// </summary>
MessageTooOldForBulkDelete = 50034,
#endregion
#region 9xxxx
/// <summary>
/// Reaction Blocked
/// </summary>
ReactionBlocked = 90001,
#endregion
}
}

View file

@ -0,0 +1,33 @@
namespace Maki.Rest
{
/// <summary>
/// Discord Rest request methods
/// </summary>
enum RestRequestMethod
{
/// <summary>
/// GET, does not send additional data
/// </summary>
GET,
/// <summary>
/// POST, sends additional data
/// </summary>
POST,
/// <summary>
/// PUT, sends additional data
/// </summary>
PUT,
/// <summary>
/// PATCH, sends additional data
/// </summary>
PATCH,
/// <summary>
/// DELETE, does not send additional data
/// </summary>
DELETE
}
}

15
Maki/Rest/RestResponse.cs Normal file
View file

@ -0,0 +1,15 @@
using System;
namespace Maki.Rest
{
internal class RestResponse<T>
{
public RestRequestMethod Method;
public Uri Uri;
public ushort Status;
public RestErrorCode ErrorCode;
public string ErrorMessage;
public string RawResponse;
public T Response;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Auth
{
/// <summary>
/// Multi Factor Auth login request
/// </summary>
internal struct LoginMultiFactorAuth
{
/// <summary>
/// Multi factor auth code
/// </summary>
[JsonProperty("code")]
public string Code;
/// <summary>
/// Multi factor auth ticket
/// </summary>
[JsonProperty("ticket")]
public string Ticket;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Auth
{
/// <summary>
/// Login request
/// </summary>
internal struct LoginRequest
{
/// <summary>
/// E-mail address
/// </summary>
[JsonProperty("email")]
public string Email;
/// <summary>
/// Password
/// </summary>
[JsonProperty("password")]
public string Password;
}
}

View file

@ -0,0 +1,40 @@
using Newtonsoft.Json;
namespace Maki.Structures.Auth
{
/// <summary>
/// Login response
/// </summary>
internal struct LoginResponse
{
/// <summary>
/// Indicates whether we require multi factor, is usually either null or true
/// </summary>
[JsonProperty("mfa")]
public bool? MFA;
/// <summary>
/// Token
/// </summary>
[JsonProperty("token")]
public string Token;
/// <summary>
/// MFA Ticket, to be bundled with the mfa request
/// </summary>
[JsonProperty("ticket")]
public string Ticket;
/// <summary>
/// Username errors
/// </summary>
[JsonProperty("username")]
public string[] UsernameError;
/// <summary>
/// Password errors
/// </summary>
[JsonProperty("password")]
public string[] PasswordError;
}
}

View file

@ -0,0 +1,71 @@
using Maki.Structures.Users;
using Newtonsoft.Json;
namespace Maki.Structures.Channels
{
/// <summary>
/// Discord API Channel structure
/// </summary>
internal struct Channel
{
/// <summary>
/// the id of this private message
/// </summary>
[JsonProperty("id")]
public ulong Id;
[JsonProperty("type")]
public ChannelType Type;
/// <summary>
/// the id of the last message sent in this DM
/// </summary>
[JsonProperty("last_message_id")]
public ulong? LastMessageId;
[JsonProperty("recipients")]
public User[] Recipients;
/// <summary>
/// the id of the guild
/// </summary>
[JsonProperty("guild_id")]
public ulong? GuildId;
/// <summary>
/// the name of the channel (2-100 characters)
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// sorting position of the channel
/// </summary>
[JsonProperty("position")]
public int? Position;
/// <summary>
/// an array of overwrite objects
/// </summary>
[JsonProperty("permission_overwrites")]
public PermissionOverwrite[] PermissionOverwrites;
/// <summary>
/// the channel topic (0-1024 characters)
/// </summary>
[JsonProperty("topic")]
public string Topic;
/// <summary>
/// the bitrate (in bits) of the voice channel
/// </summary>
[JsonProperty("bitrate")]
public int? Bitrate;
/// <summary>
/// the user limit of the voice channel
/// </summary>
[JsonProperty("user_limit")]
public int? UserLimit;
}
}

View file

@ -0,0 +1,12 @@
namespace Maki.Structures.Channels
{
/// <summary>
/// Discord API Channel type
/// </summary>
internal enum ChannelType
{
Text = 0,
Private = 1,
Voice = 2,
}
}

View file

@ -0,0 +1,89 @@
using Newtonsoft.Json;
using System;
namespace Maki.Structures.Embeds
{
/// <summary>
/// Discord API Embed structure
/// </summary>
internal struct Embed
{
/// <summary>
/// title of embed
/// </summary>
[JsonProperty("title")]
public string Title;
/// <summary>
/// type of embed (always 'rich' for webhook embeds)
/// </summary>
[JsonProperty("type")]
public string Type;
/// <summary>
/// description of embed
/// </summary>
[JsonProperty("description")]
public string Description;
/// <summary>
/// url of embed
/// </summary>
[JsonProperty("url")]
public string Url;
/// <summary>
/// timestamp of embed content
/// </summary>
[JsonProperty("timestamp")]
public DateTime? Timestamp;
/// <summary>
/// colour code of the embed
/// </summary>
[JsonProperty("color")]
public uint Colour;
/// <summary>
/// footer information
/// </summary>
[JsonProperty("footer")]
public EmbedFooter? Footer;
/// <summary>
/// image information
/// </summary>
[JsonProperty("image")]
public EmbedImage? Image;
/// <summary>
/// thumbnail information
/// </summary>
[JsonProperty("thumbnail")]
public EmbedImage? Thumbnail;
/// <summary>
/// video information
/// </summary>
[JsonProperty("video")]
public EmbedVideo? Video;
/// <summary>
/// provider information
/// </summary>
[JsonProperty("provider")]
public EmbedProvider? Provider;
/// <summary>
/// author information
/// </summary>
[JsonProperty("author")]
public EmbedAuthor? Author;
/// <summary>
/// fields information
/// </summary>
[JsonProperty("fields")]
public EmbedField[] Fields;
}
}

View file

@ -0,0 +1,34 @@
using Newtonsoft.Json;
namespace Maki.Structures.Embeds
{
/// <summary>
/// Discord API Embed Author structure
/// </summary>
internal struct EmbedAuthor
{
/// <summary>
/// name of author
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// url of author
/// </summary>
[JsonProperty("url")]
public string Url;
/// <summary>
/// url of author icon (only supports http(s) and attachments)
/// </summary>
[JsonProperty("icon_url")]
public string IconUrl;
/// <summary>
/// a proxied url of author icon
/// </summary>
[JsonProperty("proxy_icon_url")]
public string ProxyIconUrl;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Embeds
{
/// <summary>
/// Discord API Embed Field structure
/// </summary>
internal struct EmbedField
{
/// <summary>
/// name of the field
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// value of the field
/// </summary>
[JsonProperty("value")]
public string Value;
/// <summary>
/// whether or not this field should display inline
/// </summary>
[JsonProperty("inline")]
public bool Inline;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Embeds
{
/// <summary>
/// Discord API Embed Footer structure
/// </summary>
internal struct EmbedFooter
{
/// <summary>
/// footer text
/// </summary>
[JsonProperty("text")]
public string Text;
/// <summary>
/// url of footer icon (only supports http(s) and attachments)
/// </summary>
[JsonProperty("icon_url")]
public string IconUrl;
/// <summary>
/// a proxied url of footer icon
/// </summary>
[JsonProperty("proxy_icon_url")]
public string ProxyIconUrl;
}
}

View file

@ -0,0 +1,34 @@
using Newtonsoft.Json;
namespace Maki.Structures.Embeds
{
/// <summary>
/// Discord API Embed Image and Thumbnail structure
/// </summary>
internal struct EmbedImage
{
/// <summary>
/// source url of thumbnail (only supports http(s) and attachments)
/// </summary>
[JsonProperty("url")]
public string Url;
/// <summary>
/// a proxied url of the thumbnail
/// </summary>
[JsonProperty("proxy_url")]
public string ProxyUrl;
/// <summary>
/// height of thumbnail
/// </summary>
[JsonProperty("height")]
public int Height;
/// <summary>
/// width of thumbnail
/// </summary>
[JsonProperty("width")]
public int Width;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Embeds
{
/// <summary>
/// Discord API Embed Provider structure
/// </summary>
internal struct EmbedProvider
{
/// <summary>
/// name of provider
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// url of provider
/// </summary>
[JsonProperty("url")]
public string Url;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Embeds
{
/// <summary>
/// Discord API Embed Video structure
/// </summary>
internal struct EmbedVideo
{
/// <summary>
/// source url of video
/// </summary>
[JsonProperty("url")]
public string Url;
/// <summary>
/// height of video
/// </summary>
[JsonProperty("height")]
public int Height;
/// <summary>
/// width of video
/// </summary>
[JsonProperty("width")]
public int Width;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Hello structure
/// </summary>
internal struct GatewayHello
{
/// <summary>
/// the interval (in milliseconds) the client should heartbeat with
/// </summary>
[JsonProperty("heartbeat_interval")]
public int HeartbeatInterval;
/// <summary>
/// used for debugging, array of servers connected to
/// </summary>
[JsonProperty("_trace")]
public string[] DebugTrace;
}
}

View file

@ -0,0 +1,40 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Identification structure
/// </summary>
internal struct GatewayIdentification
{
/// <summary>
/// authentication token
/// </summary>
[JsonProperty("token")]
public string Token;
/// <summary>
/// connection properties
/// </summary>
[JsonProperty("properties")]
public GatewayIdentificationProperties Properties;
/// <summary>
/// whether this connection supports compression of the initial ready packet
/// </summary>
[JsonProperty("compress")]
public bool Compress;
/// <summary>
/// value between 50 and 250, total number of members where the gateway will stop sending offline members in the guild member list
/// </summary>
[JsonProperty("large_threshold")]
public int LargeThreshold;
/// <summary>
/// array of two integers (shard_id, num_shards), used for Guild Sharding
/// </summary>
[JsonProperty("shard")]
public int[] Shard;
}
}

View file

@ -0,0 +1,25 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Identification Properties structure
/// </summary>
internal struct GatewayIdentificationProperties
{
[JsonProperty("$os")]
public string OperatingSystem;
[JsonProperty("$browser")]
public string Browser;
[JsonProperty("$device")]
public string Device;
[JsonProperty("$referrer")]
public string Referrer;
[JsonProperty("$referring_domain")]
public string ReferringDomain;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Info structure
/// </summary>
internal struct GatewayInfo
{
/// <summary>
/// Gateway WSS URL.
/// </summary>
[JsonProperty("url")]
public string Url;
/// <summary>
/// Recommended numbers of shards to with (for bots).
/// </summary>
[JsonProperty("shards")]
public int? Shards;
}
}

View file

@ -0,0 +1,38 @@
using Maki.Gateway;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Payload structure
/// </summary>
internal struct GatewayPayload
{
/// <summary>
/// opcode for the payload
/// </summary>
[JsonProperty("op")]
public GatewayOPCode OPCode;
/// <summary>
/// event data, can be an object or an integer
/// </summary>
[JsonProperty("d")]
public object Data;
public T DataAs<T>() => (Data as JObject).ToObject<T>();
/// <summary>
/// sequence number, used for resuming sessions and heartbeats
/// </summary>
[JsonProperty("s")]
public int? Sequence;
/// <summary>
/// the event name for this payload
/// </summary>
[JsonProperty("t")]
public string Name;
}
}

View file

@ -0,0 +1,49 @@
using Maki.Structures.Channels;
using Maki.Structures.Guilds;
using Maki.Structures.Users;
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Ready structure
/// </summary>
internal struct GatewayReady
{
/// <summary>
/// gateway protocol version
/// </summary>
[JsonProperty("v")]
public int Version;
/// <summary>
/// user object (with email information)
/// </summary>
[JsonProperty("user")]
public User User;
/// <summary>
/// array of DM channel objects
/// </summary>
[JsonProperty("private_channels")]
public Channel[] PrivateChannels;
/// <summary>
/// array of Unavailable Guild objects
/// </summary>
[JsonProperty("guilds")]
public Guild[] UnavailableGuilds;
/// <summary>
/// used for resuming connections
/// </summary>
[JsonProperty("session_id")]
public string Session;
/// <summary>
/// used for debugging, array of servers connected to
/// </summary>
[JsonProperty("_trace")]
public string[] DebugTrace;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Guild Members Request structure
/// </summary>
internal struct GatewayRequestGuildMembers
{
/// <summary>
/// id of the guild to get offline members for
/// </summary>
[JsonProperty("guild_id")]
public ulong Guild;
/// <summary>
/// string that username starts with, or an empty string to return all members
/// </summary>
[JsonProperty("query")]
public string Query;
/// <summary>
/// maximum number of members to send or 0 to request all members matched
/// </summary>
[JsonProperty("limit")]
public int Limit;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Resume structure
/// </summary>
internal struct GatewayResume
{
/// <summary>
/// session token
/// </summary>
[JsonProperty("token")]
public string Token;
/// <summary>
/// session id
/// </summary>
[JsonProperty("session_id")]
public string Session;
/// <summary>
/// last sequence number received
/// </summary>
[JsonProperty("seq")]
public int LastSequence;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Status Update structure
/// </summary>
internal struct GatewayStatusUpdate
{
/// <summary>
/// unix time (in milliseconds) of when the client went idle, or null if the client is not idle
/// </summary>
[JsonProperty("idle_since")]
public ulong? IdleSince;
/// <summary>
/// either null, or an object with one key 'name', representing the name of the game being played
/// </summary>
[JsonProperty("game")]
public object Game;
}
}

View file

@ -0,0 +1,34 @@
using Newtonsoft.Json;
namespace Maki.Structures.Gateway
{
/// <summary>
/// Discord Gateway Voice State Update structure
/// </summary>
internal struct GatewayVoiceStateUpdate
{
/// <summary>
/// id of the guild
/// </summary>
[JsonProperty("guild_id")]
public ulong Guild;
/// <summary>
/// id of the voice channel client wants to join (null if disconnecting)
/// </summary>
[JsonProperty("channel_id")]
public ulong? Channel;
/// <summary>
/// is the client muted
/// </summary>
[JsonProperty("self_mute")]
public bool Muted;
/// <summary>
/// is the client deafened
/// </summary>
[JsonProperty("self_deaf")]
public bool Deafened;
}
}

View file

@ -0,0 +1,159 @@
using Maki.Structures.Channels;
using Maki.Structures.Messages;
using Maki.Structures.Presences;
using Maki.Structures.Roles;
using Newtonsoft.Json;
using System;
namespace Maki.Structures.Guilds
{
/// <summary>
/// Discord API Guild structure
/// </summary>
internal struct Guild
{
/// <summary>
/// guild id
/// </summary>
[JsonProperty("id")]
public ulong Id;
/// <summary>
/// guild name (2-100 characters)
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// icon hash
/// </summary>
[JsonProperty("icon")]
public string IconHash;
/// <summary>
/// splash hash
/// </summary>
[JsonProperty("splash")]
public string SplashHash;
/// <summary>
/// id of owner
/// </summary>
[JsonProperty("owner_id")]
public ulong? OwnerId;
/// <summary>
/// the id of the voice region
/// </summary>
[JsonProperty("region")]
public string VoiceRegionId;
/// <summary>
/// id of afk channel
/// </summary>
[JsonProperty("afk_channel_id")]
public ulong? AfkChannelId;
/// <summary>
/// afk timeout in seconds
/// </summary>
[JsonProperty("afk_timeout")]
public int? AfkTimeout;
/// <summary>
/// is this guild embeddable (e.g. widget)
/// </summary>
[JsonProperty("embed_enabled")]
public bool? WidgetEnabled;
/// <summary>
/// id of embedded channel
/// </summary>
[JsonProperty("embed_channel_id")]
public ulong? WidgetChannelId;
/// <summary>
/// level of verification
/// </summary>
[JsonProperty("verification_level")]
public int? VerificationLevel;
/// <summary>
/// default message notifications level
/// </summary>
[JsonProperty("default_message_notifications")]
public int? MessageNotificationLevel;
/// <summary>
/// array of role objects
/// </summary>
[JsonProperty("roles")]
public Role[] Roles;
/// <summary>
/// array of emoji objects
/// </summary>
[JsonProperty("emojis")]
public Emoji[] Emojis;
/// <summary>
/// array of guild features
/// </summary>
[JsonProperty("features")]
public string[] Features;
/// <summary>
/// required MFA level for the guild
/// </summary>
[JsonProperty("mfa_level")]
public int? MultiFactorAuthLevel;
/// <summary>
/// date this guild was joined at
/// </summary>
[JsonProperty("joined_at")]
public DateTime? Created;
/// <summary>
/// whether this is considered a large guild
/// </summary>
[JsonProperty("large")]
public bool? IsLarge;
/// <summary>
/// is this guild unavailable
/// </summary>
[JsonProperty("unavailable")]
public bool? IsUnavailable;
/// <summary>
/// total number of members in this guild
/// </summary>
[JsonProperty("member_count")]
public int? MemberCount;
/// <summary>
/// array of voice state objects (without the guild_id key)
/// </summary>
[JsonProperty("voice_states")]
public object[] VoiceStates;
/// <summary>
/// array of guild member objects
/// </summary>
[JsonProperty("members")]
public GuildMember[] Members;
/// <summary>
/// array of channel objects
/// </summary>
[JsonProperty("channels")]
public Channel[] Channels;
/// <summary>
/// array of simple presence objects, which share the same fields as Presence Update event sans a roles or guild_id key
/// </summary>
[JsonProperty("presences")]
public Presence[] Presences;
}
}

View file

@ -0,0 +1,78 @@
using Maki.Structures.Users;
using Newtonsoft.Json;
using System;
namespace Maki.Structures.Guilds
{
/// <summary>
/// Discord API Guild Integration structure
/// </summary>
internal struct GuildIntegration
{
/// <summary>
/// integration id
/// </summary>
[JsonProperty("id")]
public ulong Id;
/// <summary>
/// integration name
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// integration type (twitch, youtube, etc)
/// </summary>
[JsonProperty("type")]
public string Type;
/// <summary>
/// is this integration enabled
/// </summary>
[JsonProperty("enabled")]
public bool IsEnabled;
/// <summary>
/// is this integration syncing
/// </summary>
[JsonProperty("syncing")]
public bool IsSyncing;
/// <summary>
/// id that this integration uses for "subscribers"
/// </summary>
[JsonProperty("role_id")]
public ulong RoleId;
/// <summary>
/// the behavior of expiring subscribers
/// </summary>
[JsonProperty("expire_behavior")]
public int ExpireBehaviour;
/// <summary>
/// the grace period before expiring subscribers
/// </summary>
[JsonProperty("expire_grace_period")]
public int ExpireGracePeriod;
/// <summary>
/// user for this integration
/// </summary>
[JsonProperty("user")]
public User User;
/// <summary>
/// integration account information
/// </summary>
[JsonProperty("account")]
public GuildIntegrationAccount Account;
/// <summary>
/// when this integration was last synced
/// </summary>
[JsonProperty("synced_at")]
public DateTime LastSync;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Guilds
{
/// <summary>
/// Discord API Guild Integration Account structure
/// </summary>
internal struct GuildIntegrationAccount
{
/// <summary>
/// id of the account
/// </summary>
[JsonProperty("id")]
public string Id;
/// <summary>
/// name of the account
/// </summary>
[JsonProperty("name")]
public string Name;
}
}

View file

@ -0,0 +1,54 @@
using Maki.Structures.Users;
using Newtonsoft.Json;
using System;
namespace Maki.Structures.Guilds
{
/// <summary>
/// Discord API Guild Member structure
/// </summary>
internal struct GuildMember
{
/// <summary>
/// user object
/// </summary>
[JsonProperty("user")]
public User User;
/// <summary>
/// this users guild nickname (if one is set)
/// </summary>
[JsonProperty("nick")]
public string Nickname;
/// <summary>
/// array of role object id's
/// </summary>
[JsonProperty("roles")]
public ulong[] Roles;
/// <summary>
/// date the user joined the guild
/// </summary>
[JsonProperty("joined_at")]
public DateTime? JoinedAt;
/// <summary>
/// if the user is deafened
/// </summary>
[JsonProperty("deaf")]
public bool? IsDeafened;
/// <summary>
/// if the user is muted
/// </summary>
[JsonProperty("mute")]
public bool? IsMuted;
/// <summary>
/// Guild Id (populated in gateway add)
/// </summary>
[JsonProperty("guild_id")]
public ulong? GuildId;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Guilds
{
/// <summary>
/// Discord API Guild Members Chunk Response structure
/// </summary>
internal struct GuildMembersChunk
{
/// <summary>
/// the id of the guild
/// </summary>
[JsonProperty("guild_id")]
public ulong Guild;
/// <summary>
/// set of guild members
/// </summary>
[JsonProperty("members")]
public GuildMember[] Members;
}
}

View file

@ -0,0 +1,29 @@
using Maki.Structures.Roles;
using Newtonsoft.Json;
namespace Maki.Structures.Guilds
{
/// <summary>
/// Discord API Guild Role structure
/// </summary>
internal struct GuildRole
{
/// <summary>
/// Populated in the _CREATE and _UPDATE event
/// </summary>
[JsonProperty("role")]
public Role? Role;
/// <summary>
/// Populated in the _DELETE event
/// </summary>
[JsonProperty("role_id")]
public ulong? RoleId;
/// <summary>
/// Guild Id this role belongs to
/// </summary>
[JsonProperty("guild_id")]
public ulong Guild;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Guilds
{
/// <summary>
/// Discord API Gulld Widget structure
/// </summary>
internal struct GuildWidget
{
/// <summary>
/// if the embed is enabled
/// </summary>
[JsonProperty("enabled")]
public bool IsEnabled;
/// <summary>
/// the embed channel id
/// </summary>
[JsonProperty("channel_id")]
public ulong ChannelId;
}
}

View file

@ -0,0 +1,52 @@
using Newtonsoft.Json;
namespace Maki.Structures.Messages
{
/// <summary>
/// Discord API Attachment structure
/// </summary>
internal struct Attachment
{
/// <summary>
/// attachment id
/// </summary>
[JsonProperty("id")]
public ulong Id;
/// <summary>
/// name of file attached
/// </summary>
[JsonProperty("filename")]
public string Filename;
/// <summary>
/// size of file in bytes
/// </summary>
[JsonProperty("size")]
public int Size;
/// <summary>
/// source url of file
/// </summary>
[JsonProperty("url")]
public string Url;
/// <summary>
/// a proxied url of file
/// </summary>
[JsonProperty("proxy_url")]
public string ProxyUrl;
/// <summary>
/// height of file (if image)
/// </summary>
[JsonProperty("height")]
public int? Height;
/// <summary>
/// width of file (if image)
/// </summary>
[JsonProperty("width")]
public int? Width;
}
}

View file

@ -0,0 +1,40 @@
using Newtonsoft.Json;
namespace Maki.Structures.Messages
{
/// <summary>
/// Discord API Emoji structure
/// </summary>
internal struct Emoji
{
/// <summary>
/// id of emoji (if custom emoji)
/// </summary>
[JsonProperty("id")]
public ulong? Id;
/// <summary>
/// name of emoji
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// roles this emoji is active for
/// </summary>
[JsonProperty("roles")]
public ulong[] Roles;
/// <summary>
/// whether this emoji must be wrapped in colons
/// </summary>
[JsonProperty("require_colons")]
public bool RequiresColons;
/// <summary>
/// whether this emoji is managed
/// </summary>
[JsonProperty("managed")]
public bool IsManaged;
}
}

View file

@ -0,0 +1,110 @@
using Maki.Structures.Embeds;
using Maki.Structures.Users;
using Newtonsoft.Json;
using System;
namespace Maki.Structures.Messages
{
/// <summary>
/// Discord API Message structure
/// </summary>
internal struct Message
{
/// <summary>
/// id of the message
/// </summary>
[JsonProperty("id")]
public ulong Id;
/// <summary>
/// id of the channel the message was sent in
/// </summary>
[JsonProperty("channel_id")]
public ulong ChannelId;
/// <summary>
/// the author of this message (doesn't contain a valid user when sent by webhook)
/// </summary>
[JsonProperty("author")]
public User User;
/// <summary>
/// contents of the message
/// </summary>
[JsonProperty("content")]
public string Content;
/// <summary>
/// when this message was sent
/// </summary>
[JsonProperty("timestamp")]
public DateTime Sent;
/// <summary>
/// when this message was edited (or null if never)
/// </summary>
[JsonProperty("edited_timestamp")]
public DateTime? Edited;
/// <summary>
/// whether this was a TTS message
/// </summary>
[JsonProperty("tts")]
public bool IsTTS;
/// <summary>
/// whether this message mentions everyone
/// </summary>
[JsonProperty("mention_everyone")]
public bool MentioningEveryone;
/// <summary>
/// users specifically mentioned in the message
/// </summary>
[JsonProperty("mentions")]
public User[] Mentions;
/// <summary>
/// roles specifically mentioned in this message
/// </summary>
[JsonProperty("mention_roles")]
public ulong[] MentionsRoles;
/// <summary>
/// any attached files
/// </summary>
[JsonProperty("attachments")]
public Attachment[] Attachments;
/// <summary>
/// any embedded content
/// </summary>
[JsonProperty("embeds")]
public Embed[] Embeds;
/// <summary>
/// reactions to the message
/// Not actually populated ever???
/// </summary>
[JsonProperty("reactions")]
public MessageReaction[] Reactions;
/// <summary>
/// used for validating a message was sent
/// </summary>
[JsonProperty("nonce")]
public long? Nonce;
/// <summary>
/// whether this message is pinned
/// </summary>
[JsonProperty("pinned")]
public bool IsPinned;
/// <summary>
/// if the message is generated by a webhook, this is the webhook's id
/// </summary>
[JsonProperty("webhook_id")]
public string WebhookId;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Messages
{
/// <summary>
/// Discord API Message Reaction structure
/// </summary>
internal struct MessageReaction
{
/// <summary>
/// times this emoji has been used to react
/// </summary>
[JsonProperty("count")]
public int Times;
/// <summary>
/// whether the current user reacted using this emoji
/// </summary>
[JsonProperty("me")]
public bool ByMe;
/// <summary>
/// emoji information
/// </summary>
[JsonProperty("emoji")]
public Emoji Emoji;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Presences
{
/// <summary>
/// Discord API Game structure
/// </summary>
internal struct Game
{
/// <summary>
/// the game's name
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// see Game Types
/// </summary>
[JsonProperty("type")]
public GameType? Type;
/// <summary>
/// stream url, is validated when type is Streaming/1
/// </summary>
[JsonProperty("url")]
public string Url;
}
}

View file

@ -0,0 +1,18 @@
namespace Maki.Structures.Presences
{
/// <summary>
/// Discord API Game Type enum
/// </summary>
internal enum GameType
{
/// <summary>
/// Playing {name}
/// </summary>
Game = 0,
/// <summary>
/// Streaming {name}
/// </summary>
Streaming = 1,
}
}

View file

@ -0,0 +1,41 @@
using Maki.Structures.Users;
using Newtonsoft.Json;
namespace Maki.Structures.Presences
{
/// <summary>
/// Discord API Presence structure
/// </summary>
internal struct Presence
{
/// <summary>
/// the user presence is being updated for
/// </summary>
[JsonProperty("user")]
public User User;
/// <summary>
/// roles this user is in
/// </summary>
[JsonProperty("roles")]
public ulong[] Roles;
/// <summary>
/// null, or the user's current activity
/// </summary>
[JsonProperty("game")]
public Game? Game;
/// <summary>
/// id of the guild
/// </summary>
[JsonProperty("guild_id")]
public ulong Guild;
/// <summary>
/// either "idle", "dnd", "online" or "offline"
/// </summary>
[JsonProperty("status")]
public string Status;
}
}

View file

@ -0,0 +1,28 @@
using Newtonsoft.Json;
namespace Maki.Structures.Presences
{
/// <summary>
/// Discord API Typing Start event structure
/// </summary>
internal struct TypingStart
{
/// <summary>
/// id of the channel
/// </summary>
[JsonProperty("channel_id")]
public ulong Channel;
/// <summary>
/// id of the user
/// </summary>
[JsonProperty("user_id")]
public ulong User;
/// <summary>
/// unix time (in seconds) of when the user started typing
/// </summary>
[JsonProperty("timestamp")]
public int Time;
}
}

View file

@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace Maki.Structures.Rest
{
internal struct Error
{
/// <summary>
/// Error code
/// </summary>
[JsonProperty("code")]
public uint Code;
/// <summary>
/// Error message
/// </summary>
[JsonProperty("message")]
public string Message;
}
}

View file

@ -0,0 +1,20 @@
using Maki.Structures.Embeds;
using Newtonsoft.Json;
namespace Maki.Structures.Rest
{
internal struct MessageCreate
{
[JsonProperty("content")]
public string Text;
[JsonProperty("nonce")]
public long? Nonce;
[JsonProperty("tts")]
public bool TTS;
[JsonProperty("embed")]
public Embed? Embed;
}
}

View file

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Maki.Structures.Rest
{
internal struct RoleCreate
{
[JsonProperty("name")]
public string Name;
[JsonProperty("permissions")]
public DiscordPermission Perms;
[JsonProperty("color")]
public uint Colour;
[JsonProperty("hoist")]
public bool Hoist;
[JsonProperty("mentionable")]
public bool Mentionable;
}
}

View file

@ -0,0 +1,58 @@
using Newtonsoft.Json;
namespace Maki.Structures.Roles
{
/// <summary>
/// Discord API Role structure
/// </summary>
internal struct Role
{
/// <summary>
/// role id
/// </summary>
[JsonProperty("id")]
public ulong Id;
/// <summary>
/// role name
/// </summary>
[JsonProperty("name")]
public string Name;
/// <summary>
/// integer representation of hexadecimal color code
/// </summary>
[JsonProperty("color")]
public uint? Colour;
/// <summary>
/// if this role is pinned in the user listing
/// </summary>
[JsonProperty("hoist")]
public bool? IsHoisted;
/// <summary>
/// position of this role
/// </summary>
[JsonProperty("position")]
public int? Position;
/// <summary>
/// permission bit set
/// </summary>
[JsonProperty("permissions")]
public int? Permissions;
/// <summary>
/// whether this role is managed by an integration
/// </summary>
[JsonProperty("managed")]
public bool? IsManaged;
/// <summary>
/// whether this role is mentionable
/// </summary>
[JsonProperty("mentionable")]
public bool? IsMentionable;
}
}

View file

@ -0,0 +1,34 @@
using Newtonsoft.Json;
namespace Maki.Structures.Users
{
/// <summary>
/// Discord API Permission Overwrite structure
/// </summary>
internal struct PermissionOverwrite
{
/// <summary>
/// role or user id
/// </summary>
[JsonProperty("id")]
public ulong Id;
/// <summary>
/// either 'role' or 'member'
/// </summary>
[JsonProperty("type")]
public string Type;
/// <summary>
/// permission bit set
/// </summary>
[JsonProperty("allow")]
public int Allow;
/// <summary>
/// permission bit set
/// </summary>
[JsonProperty("deny")]
public int Deny;
}
}

View file

@ -0,0 +1,64 @@
using Newtonsoft.Json;
namespace Maki.Structures.Users
{
/// <summary>
/// Discord API User structure
/// </summary>
internal struct User
{
/// <summary>
/// the user's id
/// </summary>
[JsonProperty("id")]
public ulong Id;
/// <summary>
/// Guild ID, not always populated.
/// </summary>
[JsonProperty("guild_id")]
public ulong? GuildId;
/// <summary>
/// the user's username, not unique across the platform
/// </summary>
[JsonProperty("username")]
public string Username;
/// <summary>
/// the user's 4-digit discord-tag
/// </summary>
[JsonProperty("discriminator")]
public ushort Tag;
/// <summary>
/// the user's avatar hash
/// </summary>
[JsonProperty("avatar")]
public string AvatarHash;
/// <summary>
/// whether the user belongs to an OAuth2 application
/// </summary>
[JsonProperty("bot")]
public bool IsBot;
/// <summary>
/// whether the user has two factor enabled on their account
/// </summary>
[JsonProperty("mfa_enabled")]
public bool HasMFA;
/// <summary>
/// whether the email on this account has been verified
/// </summary>
[JsonProperty("verified")]
public bool IsVerified;
/// <summary>
/// the user's email
/// </summary>
[JsonProperty("email")]
public string EMail;
}
}

14
Maki/Utility.cs Normal file
View file

@ -0,0 +1,14 @@
using System;
namespace Maki
{
internal static class Utility
{
public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static readonly DateTime DiscordEpoch = new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static DateTime FromUnixTimeMilliseconds(long ms) => UnixEpoch.AddMilliseconds(ms);
public static DateTime FromDiscordTimeMilliseconds(long ms) => DiscordEpoch.AddMilliseconds(ms);
}
}

5
Maki/packages.config Normal file
View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" />
<package id="WebSocketSharp" version="1.0.3-rc11" targetFramework="net40" />
</packages>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
</configuration>

58
MakiTest/MakiTest.csproj Normal file
View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3B147886-0307-4AB8-92A5-4C5CBB93B580}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>MakiTest</RootNamespace>
<AssemblyName>MakiTest</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Maki\Maki.csproj">
<Project>{97523aed-b694-42c2-96ac-86a1d65109f7}</Project>
<Name>Maki</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

68
MakiTest/Program.cs Normal file
View file

@ -0,0 +1,68 @@
using Maki;
using System;
using System.IO;
using System.Threading;
namespace MakiTest
{
class Program
{
static void Main(string[] args)
{
string[] tokenInfo = File.ReadAllLines("token.txt");
string token = tokenInfo[0] ?? string.Empty;
DiscordTokenType type = (tokenInfo[1] ?? string.Empty) == "user" ? DiscordTokenType.User : DiscordTokenType.Bot;
string username = string.Empty;
string password = string.Empty;
string mfaCode = null;
if (tokenInfo.Length >= 5)
{
username = tokenInfo[2];
password = tokenInfo[3];
mfaCode = tokenInfo[4];
}
else if (string.IsNullOrEmpty(token))
throw new Exception("Please set a token or login details.");
using (ManualResetEvent mre = new ManualResetEvent(false))
using (Discord client = new Discord())
{
Console.CancelKeyPress += (s, e) => { e.Cancel = true; mre.Set(); };
if (!string.IsNullOrEmpty(token))
client.Connect(token, type);
else
client.Connect(username, password, mfaCode);
client.OnReady += (me) => Console.WriteLine($"Connected as {me.NameWithTag} ({me.Id})!");
client.OnServerCreate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) created.");
client.OnServerUpdate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) updated.");
client.OnServerDelete += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) deleted.");
client.OnEmojisUpdate += (serv) => Console.WriteLine($"Server {serv.Name} ({serv.Id}) emojis updated.");
client.OnChannelCreate += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) created.");
client.OnChannelUpdate += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) updated.");
client.OnChannelDelete += (chan) => Console.WriteLine($"Channel #{chan.Name} ({chan.Id}) in {chan.Server.Name} ({chan.Server.Id}) deleted.");
client.OnBanAdd += (user, serv) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) been banned from {serv.Name} ({serv.Id}).");
client.OnBanRemove += (user, serv) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) been unbanned from {serv.Name} ({serv.Id}).");
client.OnMemberAdd += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) joined {member.Server.Name} ({member.Server.Id}).");
client.OnMemberRemove += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) left {member.Server.Name} ({member.Server.Id}).");
client.OnMemberUpdate += (member) => Console.WriteLine($"{member.NameWithTag} ({member.User.Id}) was updated for {member.Server.Name} ({member.Server.Id}).");
client.OnMessageCreate += (msg) => Console.WriteLine($"{msg.Sender.NameWithTag} ({msg.Sender.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}: {msg.Text}");
client.OnMessageUpdate += (msg) => Console.WriteLine($"{msg.Sender.NameWithTag} ({msg.Sender.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}: {msg.Text}");
client.OnMessageDelete += (msg) => Console.WriteLine($"{msg.Sender.NameWithTag} ({msg.Sender.User.Id}) in {msg.Channel.Server.Name} ({msg.Channel.Server.Id}) #{msg.Channel.Name} ({msg.Channel.Id}) {msg.Id}");
client.OnRoleCreate += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) created in {role.Server.Name} ({role.Server.Id}).");
client.OnRoleUpdate += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) updated in {role.Server.Name} ({role.Server.Id}).");
client.OnRoleDelete += (role) => Console.WriteLine($"Role {role.Name} ({role.Id}) deleted in {role.Server.Name} ({role.Server.Id}).");
client.OnTypingStart += (user, chan) => Console.WriteLine($"{user.NameWithTag} ({user.User.Id}) started typing in #{chan.Name} ({chan.Id}) in {user.Server.Name} ({user.Server.Id})");
client.OnPresenceUpdate += (user) => Console.WriteLine($"Presence of {user.NameWithTag} ({user.User.Id}) update for {user.Server.Name} ({user.Server.Id}).");
client.OnUserUpdate += (user) => Console.WriteLine($"{user.NameWithTag} ({user.Id}) updated.");
mre.WaitOne();
}
Console.WriteLine("Stopped, press any key to close the program...");
Console.ReadKey();
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MakiTest")]
[assembly: AssemblyDescription("Quick application to test communications")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MakiTest")]
[assembly: AssemblyCopyright("flash.moe 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3b147886-0307-4ab8-92a5-4c5cbb93b580")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

6
README.md Normal file
View file

@ -0,0 +1,6 @@
# Maki
Maki is a Discord library mainly intended for personal use. It follows a more synchronous structure as opposed to something like Discord.net. I'm personally not the greatest fan of the whole async/await system, hence the existence of this library.
## Requirements
Something that can compile and run .net framework 4.0 applications and install nuget packages.