mirror of
https://github.com/Stone-Red-Code/DiscordCLI.git
synced 2026-09-04 17:15:59 +02:00
Added DM support
- Added DM support - Improved Token input - Put the Commands class in its own file
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,175 @@
|
||||
using DSharpPlus.Entities;
|
||||
using DSharpPlus;
|
||||
using Stone_Red_Utilities.ColorConsole;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace DiscordCLI
|
||||
{
|
||||
internal class Commands
|
||||
{
|
||||
private readonly DiscordClient client;
|
||||
|
||||
private readonly OutputManager outputManager;
|
||||
|
||||
public Commands(DiscordClient dicordClient, OutputManager outputMan)
|
||||
{
|
||||
client = dicordClient;
|
||||
outputManager = outputMan;
|
||||
}
|
||||
|
||||
protected void ListGuilds(string args)
|
||||
{
|
||||
int index = 1;
|
||||
foreach (DiscordGuild guild in client.Guilds.Values)
|
||||
{
|
||||
Console.WriteLine($"{index}. {guild.Name}");
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ListDms(string args)
|
||||
{
|
||||
int index = 1;
|
||||
foreach (DiscordDmChannel dmChannel in client.PrivateChannels)
|
||||
{
|
||||
Console.WriteLine($"{index}. {string.Join(", ", dmChannel.Recipients.Select(x => x.Username))}");
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ListGuildChannels(string args)
|
||||
{
|
||||
IReadOnlyCollection<DiscordChannel> textChannels;
|
||||
DiscordGuild guild;
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
if (int.TryParse(args, out int ind))
|
||||
{
|
||||
guild = client.Guilds?.Values.ElementAtOrDefault(ind - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
guild = client.Guilds?.FirstOrDefault(x => x.Value.Name == args).Value;
|
||||
}
|
||||
GlobalInformation.currentTextChannel = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
guild = GlobalInformation.currentGuild;
|
||||
}
|
||||
|
||||
if (guild is null)
|
||||
{
|
||||
ConsoleExt.WriteLine("Guild not found!", ConsoleColor.Red);
|
||||
GlobalInformation.currentTextChannel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalInformation.currentGuild = guild;
|
||||
textChannels = guild.Channels;
|
||||
|
||||
int index = 1;
|
||||
foreach (DiscordChannel channel in textChannels)
|
||||
{
|
||||
switch (channel.Type)
|
||||
{
|
||||
case ChannelType.Category:
|
||||
//Console.WriteLine($"[{channel.Name}]");
|
||||
break;
|
||||
|
||||
case ChannelType.Text:
|
||||
Console.WriteLine($" {index}. {channel.Name}");
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async void EnterChannel(string args)
|
||||
{
|
||||
DiscordChannel textChannel;
|
||||
|
||||
if (GlobalInformation.currentGuild is null)
|
||||
{
|
||||
ConsoleExt.WriteLine("You are not in a guild!", ConsoleColor.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (int.TryParse(args, out int ind))
|
||||
{
|
||||
textChannel = GlobalInformation.currentGuild?.Channels.Where(x => x.Type == ChannelType.Text).ElementAtOrDefault(ind - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
textChannel = GlobalInformation.currentGuild?.Channels.Where(x => x.Type == ChannelType.Text).FirstOrDefault(x => x.Name == args);
|
||||
}
|
||||
|
||||
GlobalInformation.currentTextChannel = textChannel;
|
||||
|
||||
if (textChannel is null)
|
||||
{
|
||||
ConsoleExt.WriteLine("Channel not found!", ConsoleColor.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DiscordMessage message in (await textChannel.GetMessagesAsync(10)).Reverse())
|
||||
{
|
||||
try
|
||||
{
|
||||
await outputManager.WriteMessage(message, textChannel, GlobalInformation.currentGuild);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async void EnterDmChannel(string args)
|
||||
{
|
||||
DiscordDmChannel dmChannel;
|
||||
|
||||
GlobalInformation.currentGuild = null;
|
||||
|
||||
if (int.TryParse(args, out int ind))
|
||||
{
|
||||
dmChannel = client.PrivateChannels.ElementAtOrDefault(ind - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
dmChannel = client.PrivateChannels.FirstOrDefault(x => x.Name == args);
|
||||
}
|
||||
|
||||
GlobalInformation.currentTextChannel = dmChannel;
|
||||
|
||||
if (dmChannel is null)
|
||||
{
|
||||
ConsoleExt.WriteLine("Channel not found!", ConsoleColor.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DiscordMessage message in (await dmChannel.GetMessagesAsync(10)).Reverse())
|
||||
{
|
||||
try
|
||||
{
|
||||
await outputManager.WriteMessage(message, dmChannel, GlobalInformation.currentGuild);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void DeleteToken(string args)
|
||||
{
|
||||
File.Delete(Program.tokenPath);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-136
@@ -1,9 +1,6 @@
|
||||
using DSharpPlus.Entities;
|
||||
using DSharpPlus;
|
||||
using DSharpPlus;
|
||||
using Stone_Red_Utilities.ColorConsole;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
@@ -24,13 +21,19 @@ namespace DiscordCLI
|
||||
{ "exit", ("exits application", null) },
|
||||
{ "logout", ("deletes auth token and exits application", DeleteToken) },
|
||||
{ "guilds", ("lists all guilds you are in", ListGuilds) },
|
||||
{ "dms", ("lists all private channels (Not implemented yet)", ListDms) },
|
||||
{ "dms", ("lists all private channels", ListDms) },
|
||||
{ "channels", ("lists all channels of guild args:<guild name/index>", ListGuildChannels) },
|
||||
{ "enterg", ("enter guild args:<guild name/index>", ListGuildChannels) },
|
||||
{ "enterc", ("enter chat args:<channel name/index>", EnterChannel) },
|
||||
{ "enterc", ("enter channel args:<channel name/index>", EnterChannel) },
|
||||
{ "enterd", ("enter DM channel args:<channel name/index>", EnterDmChannel) },
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the command and return true if the program should exit
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckCommand(string input)
|
||||
{
|
||||
input = input.Trim().ToLower().Replace('\n', '\0');
|
||||
@@ -38,13 +41,12 @@ namespace DiscordCLI
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return false;
|
||||
|
||||
if (input.StartsWith('<'))
|
||||
if (input.StartsWith(InputManager.prefix))
|
||||
{
|
||||
input = input.Remove(0, 1);
|
||||
}
|
||||
else if (GlobalInformation.currentTextChannel != null)
|
||||
{
|
||||
Console.CursorTop--;
|
||||
Console.Write("\r" + new string(' ', Console.WindowWidth));
|
||||
GlobalInformation.currentTextChannel.SendMessageAsync(input);
|
||||
return false;
|
||||
@@ -58,8 +60,11 @@ namespace DiscordCLI
|
||||
string args = input.Contains(" ") ? input[input.IndexOf(" ")..].Trim() : null;
|
||||
input = input.Contains(" ") ? input.Substring(0, input.IndexOf(" ")) : input;
|
||||
|
||||
if (input == "help")
|
||||
if (input == "help" || input == "?")
|
||||
{
|
||||
Console.WriteLine($"Prefix: '{InputManager.prefix}' (Only required in text channels)");
|
||||
Console.WriteLine();
|
||||
|
||||
for (int i = 0; i < CommandsList.Count; i++)
|
||||
{
|
||||
Console.Write($"{i + 1}. {CommandsList.Keys.ElementAt(i)}".PadRight(15));
|
||||
@@ -77,131 +82,4 @@ namespace DiscordCLI
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Commands
|
||||
{
|
||||
private IReadOnlyDictionary<ulong, DiscordGuild> socketGuildsCache;
|
||||
|
||||
private readonly DiscordClient client;
|
||||
|
||||
private readonly OutputManager outputManager;
|
||||
|
||||
public Commands(DiscordClient dicordClient, OutputManager outputMan)
|
||||
{
|
||||
client = dicordClient;
|
||||
outputManager = outputMan;
|
||||
}
|
||||
|
||||
protected void ListGuilds(string args)
|
||||
{
|
||||
socketGuildsCache ??= client.Guilds;
|
||||
|
||||
int index = 1;
|
||||
foreach (DiscordGuild guild in socketGuildsCache.Values)
|
||||
{
|
||||
Console.WriteLine($"{index}. {guild.Name}");
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ListDms(string args)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected void ListGuildChannels(string args)
|
||||
{
|
||||
IReadOnlyCollection<DiscordChannel> textChannels;
|
||||
DiscordGuild guild;
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
if (int.TryParse(args, out int ind))
|
||||
{
|
||||
guild = socketGuildsCache?.Values.ElementAtOrDefault(ind - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
guild = socketGuildsCache?.FirstOrDefault(x => x.Value.Name == args).Value;
|
||||
}
|
||||
GlobalInformation.currentTextChannel = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
guild = GlobalInformation.currentGuild;
|
||||
}
|
||||
|
||||
if (guild is null)
|
||||
{
|
||||
ConsoleExt.WriteLine("Guild not found!", ConsoleColor.Red);
|
||||
GlobalInformation.currentTextChannel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalInformation.currentGuild = guild;
|
||||
textChannels = guild.Channels;
|
||||
|
||||
int index = 1;
|
||||
foreach (DiscordChannel channel in textChannels)
|
||||
{
|
||||
switch (channel.Type)
|
||||
{
|
||||
case ChannelType.Category:
|
||||
//Console.WriteLine($"[{channel.Name}]");
|
||||
break;
|
||||
|
||||
case ChannelType.Text:
|
||||
Console.WriteLine($" {index}. {channel.Name}");
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async void EnterChannel(string args)
|
||||
{
|
||||
DiscordChannel textChannel;
|
||||
|
||||
if (GlobalInformation.currentGuild is null)
|
||||
{
|
||||
ConsoleExt.WriteLine("You are not in a guild!", ConsoleColor.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (int.TryParse(args, out int ind))
|
||||
{
|
||||
textChannel = GlobalInformation.currentGuild?.Channels.Where(x => x.Type == ChannelType.Text).ElementAtOrDefault(ind - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
textChannel = GlobalInformation.currentGuild?.Channels.Where(x => x.Type == ChannelType.Text).FirstOrDefault(x => x.Name == args);
|
||||
}
|
||||
|
||||
GlobalInformation.currentTextChannel = textChannel;
|
||||
|
||||
if (textChannel is null)
|
||||
{
|
||||
ConsoleExt.WriteLine("Channel not found!", ConsoleColor.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DiscordMessage message in (await textChannel.GetMessagesAsync(10)).Reverse())
|
||||
{
|
||||
try
|
||||
{
|
||||
await outputManager.WriteMessage(message, textChannel, GlobalInformation.currentGuild);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void DeleteToken(string args)
|
||||
{
|
||||
File.Delete(Program.tokenPath);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using DSharpPlus;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using DSharpPlus.Entities;
|
||||
using System.Linq;
|
||||
|
||||
namespace DiscordCLI
|
||||
{
|
||||
@@ -8,7 +10,8 @@ namespace DiscordCLI
|
||||
{
|
||||
private readonly CommandsManager commandsManager;
|
||||
private readonly DiscordClient client;
|
||||
public string Input { get; private set; } = "<";
|
||||
public const string prefix = ">";
|
||||
public string Input { get; private set; } = prefix;
|
||||
|
||||
public InputManager(DiscordClient dicordClient, CommandsManager commandsMan)
|
||||
{
|
||||
@@ -23,8 +26,9 @@ namespace DiscordCLI
|
||||
bool exit = false;
|
||||
while (!exit)
|
||||
{
|
||||
string infoString = $"\r[{client.CurrentUser.Username}#{client.CurrentUser.Discriminator}] [{GlobalInformation.currentGuild?.Name}/{GlobalInformation.currentTextChannel?.Name}] ==> ";
|
||||
if (Input.StartsWith('<'))
|
||||
DiscordDmChannel dmChannel = GlobalInformation.currentTextChannel as DiscordDmChannel;
|
||||
string infoString = $"\r[{client.CurrentUser.Username}#{client.CurrentUser.Discriminator}] [{(dmChannel is null ? GlobalInformation.currentGuild?.Name : "DMs")}/{(dmChannel is null ? GlobalInformation.currentTextChannel?.Name : string.Join(", ", dmChannel.Recipients.Select(x => x.Username)))}] ==> ";
|
||||
if (Input.StartsWith(prefix))
|
||||
Console.Write(Environment.NewLine + infoString);
|
||||
|
||||
Input = string.Empty;
|
||||
@@ -48,12 +52,11 @@ namespace DiscordCLI
|
||||
Console.CursorLeft = infoString.Length + Input.Length - 1;
|
||||
} while (keyInfo.Key != ConsoleKey.Enter);
|
||||
|
||||
if (GlobalInformation.currentTextChannel == null && !Input.StartsWith('<'))
|
||||
Input = "<" + Input;
|
||||
if (GlobalInformation.currentTextChannel == null && !Input.StartsWith(prefix))
|
||||
Input = prefix + Input;
|
||||
|
||||
exit = commandsManager.CheckCommand(Input);
|
||||
}
|
||||
Environment.Exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+45
-33
@@ -29,12 +29,19 @@ namespace DiscordCLI
|
||||
return;
|
||||
|
||||
DiscordUser user = message.Author;
|
||||
DiscordMember discordMember = await guild.GetMemberAsync(user.Id);
|
||||
DiscordColor discordColor = discordMember.Color;
|
||||
|
||||
Color color = Color.FromArgb(discordColor.R, discordColor.G, discordColor.B);
|
||||
if (GlobalInformation.currentGuild is not null)
|
||||
{
|
||||
DiscordMember discordMember = await guild.GetMemberAsync(user.Id);
|
||||
DiscordColor discordColor = discordMember.Color;
|
||||
Color color = Color.FromArgb(discordColor.R, discordColor.G, discordColor.B);
|
||||
|
||||
WriteTop($"[{discordMember.DisplayName}]", color, message, true, true, $"{discordMember.Username}#{discordMember.Discriminator} {message.Timestamp.LocalDateTime}");
|
||||
WriteTop($"[{discordMember.DisplayName}]", color, message, true, true, $"{discordMember.Username}#{discordMember.Discriminator} {message.Timestamp.LocalDateTime}");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteTop($"[{user.Username}]", Color.White, message, true, true, $"{user.Username}#{user.Discriminator} {message.Timestamp.LocalDateTime}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(message.Content))
|
||||
WriteTop(message.Content, Color.White, message);
|
||||
@@ -57,12 +64,13 @@ namespace DiscordCLI
|
||||
WriteTop(">>> ", Color.FromArgb(embed.Color.Value), message, true, false);
|
||||
WriteTop($"{embed.Description}", Color.White, message, false);
|
||||
}
|
||||
foreach (DiscordEmbedField field in embed.Fields)
|
||||
{
|
||||
WriteTop(">>> ", Color.FromArgb(embed.Color.Value), message, true, false);
|
||||
WriteTop($"{field.Name}{Environment.NewLine}{field.Value}", Color.White, message, false);
|
||||
}
|
||||
//WriteTop($"{string.Join(Environment.NewLine, embed.Fields.Select(x => $"> {x.Name}{Environment.NewLine}{x.Value}"))}", Color.White, message);
|
||||
|
||||
if (embed.Fields is not null)
|
||||
foreach (DiscordEmbedField field in embed.Fields)
|
||||
{
|
||||
WriteTop(">>> ", Color.FromArgb(embed.Color.Value), message, true, false);
|
||||
WriteTop($"{field.Name}{Environment.NewLine}{field.Value}", Color.White, message, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -71,7 +79,9 @@ namespace DiscordCLI
|
||||
}
|
||||
|
||||
WriteTop(string.Empty, Color.White, message);
|
||||
Console.Write($"[{client.CurrentUser.Username}#{client.CurrentUser.Discriminator}] [{GlobalInformation.currentGuild?.Name}/{GlobalInformation.currentTextChannel?.Name}] ==> ");
|
||||
DiscordDmChannel dmChannel = GlobalInformation.currentTextChannel as DiscordDmChannel;
|
||||
string infoString = $"\r[{client.CurrentUser.Username}#{client.CurrentUser.Discriminator}] [{(dmChannel is null ? GlobalInformation.currentGuild?.Name : "DMs")}/{(dmChannel is null ? GlobalInformation.currentTextChannel?.Name : string.Join(", ", dmChannel.Recipients.Select(x => x.Username)))}] ==> ";
|
||||
Console.Write(infoString);
|
||||
Console.Write(InputManager.Input);
|
||||
}
|
||||
|
||||
@@ -112,26 +122,28 @@ namespace DiscordCLI
|
||||
}
|
||||
}
|
||||
|
||||
foreach (DiscordChannel channel in discordMessage.MentionedChannels)
|
||||
{
|
||||
if (channel?.Mention is not null)
|
||||
if (words[i].Contains(channel.Mention))
|
||||
{
|
||||
words[i] = words[i].Replace(channel.Mention, $"#{channel.Name}");
|
||||
mentionColor = ConsoleColor.Blue;
|
||||
}
|
||||
}
|
||||
if (!discordMessage.Channel.IsPrivate)
|
||||
foreach (DiscordChannel channel in discordMessage.MentionedChannels)
|
||||
{
|
||||
if (channel?.Mention is not null)
|
||||
if (words[i].Contains(channel.Mention))
|
||||
{
|
||||
words[i] = words[i].Replace(channel.Mention, $"#{channel.Name}");
|
||||
mentionColor = ConsoleColor.Blue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (DiscordRole role in discordMessage.MentionedRoles)
|
||||
{
|
||||
if (role?.Mention is not null)
|
||||
if (words[i].Contains(role.Mention))
|
||||
{
|
||||
words[i] = words[i].Replace(role.Mention, $"@{role.Name}");
|
||||
DiscordColor discordColor = role.Color;
|
||||
mentionColor = ClosestConsoleColor(Color.FromArgb(discordColor.R, discordColor.G, discordColor.B));
|
||||
}
|
||||
}
|
||||
if (!discordMessage.Channel.IsPrivate)
|
||||
foreach (DiscordRole role in discordMessage.MentionedRoles)
|
||||
{
|
||||
if (role?.Mention is not null)
|
||||
if (words[i].Contains(role.Mention))
|
||||
{
|
||||
words[i] = words[i].Replace(role.Mention, $"@{role.Name}");
|
||||
DiscordColor discordColor = role.Color;
|
||||
mentionColor = ClosestConsoleColor(Color.FromArgb(discordColor.R, discordColor.G, discordColor.B));
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleExt.Write(words[i] + " ", mentionColor == ConsoleColor.Black ? consoleColor : mentionColor);
|
||||
}
|
||||
@@ -150,7 +162,7 @@ namespace DiscordCLI
|
||||
private ConsoleColor ClosestConsoleColor(Color targetColor)
|
||||
{
|
||||
double minDif = double.MaxValue;
|
||||
ConsoleColor bestColor = ConsoleColor.White;
|
||||
ConsoleColor closestColor = ConsoleColor.White;
|
||||
|
||||
foreach (ConsoleColor consoleColor in Enum.GetValues(typeof(ConsoleColor)))
|
||||
{
|
||||
@@ -174,10 +186,10 @@ namespace DiscordCLI
|
||||
if (diff < minDif)
|
||||
{
|
||||
minDif = diff;
|
||||
bestColor = consoleColor;
|
||||
closestColor = consoleColor;
|
||||
}
|
||||
}
|
||||
return bestColor;
|
||||
return closestColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@ namespace DiscordCLI
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
=> new Program().MainAsync().GetAwaiter().GetResult();
|
||||
public static void Main() => new Program().MainAsync().GetAwaiter().GetResult();
|
||||
|
||||
private DiscordClient client;
|
||||
private InputManager inputManager;
|
||||
@@ -27,6 +26,7 @@ namespace DiscordCLI
|
||||
if (File.Exists(tokenPath))
|
||||
token = File.ReadAllText(tokenPath);
|
||||
|
||||
tokenInput:
|
||||
while (string.IsNullOrEmpty(token))
|
||||
{
|
||||
Console.Write("Enter auth token: ");
|
||||
@@ -52,7 +52,11 @@ namespace DiscordCLI
|
||||
ConsoleExt.WriteLine(ex, ConsoleColor.Red);
|
||||
|
||||
if (ex.Message.Contains("Authentication failed"))
|
||||
{
|
||||
File.Delete(tokenPath);
|
||||
token = string.Empty;
|
||||
goto tokenInput;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
c330ac1ec415f28e8dfa881f69ac743ee03b156f
|
||||
d74c436d6f045a94c6aefe230b53cf4fbca29cc5
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user