mirror of
https://github.com/RPCS3/discord-bot.git
synced 2024-11-27 04:00:34 +00:00
scrape only full game lists in PSN stores, also cache title names
new isssue detections for log parser consistent reaction with emoji only / text when can't ability to disable commands at runtime (fixes #56) command to check for game updates various other bugfixes
This commit is contained in:
parent
fbad33ea13
commit
998c27c966
@ -7,20 +7,18 @@ using CompatBot.Database.Providers;
|
||||
using CompatBot.Utils;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
using DSharpPlus.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
{
|
||||
[Group("piracy"), RequiresBotModRole, RequiresDm]
|
||||
[Group("piracy"), RequiresBotModRole, RequiresDm, TriggersTyping]
|
||||
[Description("Used to manage piracy filters **in DM**")]
|
||||
internal sealed class Antipiracy: BaseCommandModule
|
||||
internal sealed class Antipiracy: BaseCommandModuleCustom
|
||||
{
|
||||
[Command("list"), Aliases("show")]
|
||||
[Description("Lists all filters")]
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
var result = new StringBuilder("```")
|
||||
.AppendLine("ID | Trigger")
|
||||
.AppendLine("-----------------------------");
|
||||
@ -28,23 +26,17 @@ namespace CompatBot.Commands
|
||||
foreach (var item in await db.Piracystring.ToListAsync().ConfigureAwait(false))
|
||||
result.AppendLine($"{item.Id:0000} | {item.String}");
|
||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||
await typingTask;
|
||||
}
|
||||
|
||||
[Command("add")]
|
||||
[Description("Adds a new piracy filter trigger")]
|
||||
public async Task Add(CommandContext ctx, [RemainingText, Description("A plain string to match")] string trigger)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
var wasSuccessful = await PiracyStringProvider.AddAsync(trigger).ConfigureAwait(false);
|
||||
(DiscordEmoji reaction, string msg) result = wasSuccessful
|
||||
? (Config.Reactions.Success, "New trigger successfully saved!")
|
||||
: (Config.Reactions.Failure, "Trigger already defined.");
|
||||
await Task.WhenAll(
|
||||
ctx.RespondAsync(result.msg),
|
||||
ctx.Message.CreateReactionAsync(result.reaction),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
if (wasSuccessful)
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, "New trigger successfully saved!").ConfigureAwait(false);
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "Trigger already defined.").ConfigureAwait(false);
|
||||
if (wasSuccessful)
|
||||
await List(ctx).ConfigureAwait(false);
|
||||
}
|
||||
@ -53,19 +45,14 @@ namespace CompatBot.Commands
|
||||
[Description("Removes a piracy filter trigger")]
|
||||
public async Task Remove(CommandContext ctx, [Description("Filter ids to remove separated with spaces")] params int[] ids)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
(DiscordEmoji reaction, string msg) result = (Config.Reactions.Success, $"Trigger{(ids.Length == 1 ? "" : "s")} successfully removed!");
|
||||
var failedIds = new List<int>();
|
||||
foreach (var id in ids)
|
||||
if (!await PiracyStringProvider.RemoveAsync(id).ConfigureAwait(false))
|
||||
failedIds.Add(id);
|
||||
if (failedIds.Count > 0)
|
||||
result = (Config.Reactions.Failure, "Some ids couldn't be removed: " + string.Join(", ", failedIds));
|
||||
await Task.WhenAll(
|
||||
ctx.RespondAsync(result.msg),
|
||||
ctx.Message.CreateReactionAsync(result.reaction),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
await ctx.RespondAsync("Some ids couldn't be removed: " + string.Join(", ", failedIds)).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Trigger{(ids.Length == 1 ? "" : "s")} successfully removed!").ConfigureAwait(false);
|
||||
await List(ctx).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Utils;
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
using DSharpPlus.Entities;
|
||||
@ -21,16 +24,18 @@ namespace CompatBot.Commands.Attributes
|
||||
public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
|
||||
{
|
||||
var result = await IsAllowed(ctx, help);
|
||||
//await ctx.RespondAsync($"Check for {GetType().Name} resulted in {result}").ConfigureAwait(false);
|
||||
#if DEBUG
|
||||
ctx.Client.DebugLogger.LogMessage(LogLevel.Debug, "", $"Check for {GetType().Name} resulted in {result}", DateTime.Now);
|
||||
#endif
|
||||
if (result)
|
||||
{
|
||||
if (ReactOnSuccess != null && !help)
|
||||
await ctx.Message.CreateReactionAsync(ReactOnSuccess).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(ReactOnSuccess).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ReactOnFailure != null && !help)
|
||||
await ctx.Message.CreateReactionAsync(ReactOnFailure).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(ReactOnFailure, $"{ReactOnFailure} {ctx.Message.Author.Mention} you do not have required permissions, this incident will be reported").ConfigureAwait(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
23
CompatBot/Commands/Attributes/TriggersTyping.cs
Normal file
23
CompatBot/Commands/Attributes/TriggersTyping.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
|
||||
namespace CompatBot.Commands.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
|
||||
internal class TriggersTyping: CheckBaseAttribute
|
||||
{
|
||||
public bool InDmOnly { get; set; }
|
||||
|
||||
public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
|
||||
{
|
||||
if (help)
|
||||
return true;
|
||||
|
||||
if (!InDmOnly || ctx.Channel.IsPrivate)
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using CompatApiClient;
|
||||
using CompatApiClient.POCOs;
|
||||
using CompatApiClient.Utils;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Utils;
|
||||
using CompatBot.Utils.ResultFormatters;
|
||||
using DSharpPlus;
|
||||
@ -15,7 +16,7 @@ using DSharpPlus.Entities;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
{
|
||||
internal sealed class CompatList : BaseCommandModule
|
||||
internal sealed class CompatList : BaseCommandModuleCustom
|
||||
{
|
||||
private static readonly Client client = new Client();
|
||||
|
||||
@ -84,15 +85,13 @@ Example usage:
|
||||
await DoRequestAndRespond(ctx, requestBuilder).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("filters")]
|
||||
[Command("filters"), TriggersTyping(InDmOnly = true)]
|
||||
[Description("Provides information about available filters for the !top command")]
|
||||
public async Task Filters(CommandContext ctx)
|
||||
{
|
||||
var getDmTask = ctx.CreateDmAsync();
|
||||
if (ctx.Channel.IsPrivate)
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var embed = new DiscordEmbedBuilder {Description = "List of recognized tokens in each filter category", Color = Config.Colors.Help}
|
||||
.AddField("Regions", DicToDesc(ApiConfig.Regions))
|
||||
//.AddField("Regions", DicToDesc(ApiConfig.Regions))
|
||||
.AddField("Statuses", DicToDesc(ApiConfig.Statuses))
|
||||
.AddField("Release types", DicToDesc(ApiConfig.ReleaseTypes))
|
||||
.Build();
|
||||
@ -100,12 +99,11 @@ Example usage:
|
||||
await dm.SendMessageAsync(embed: embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("latest"), Aliases("download")]
|
||||
[Command("latest"), Aliases("download"), TriggersTyping]
|
||||
[Description("Provides links to the latest RPCS3 build")]
|
||||
[Cooldown(1, 30, CooldownBucketType.Channel)]
|
||||
public async Task Latest(CommandContext ctx)
|
||||
{
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var info = await client.GetUpdateAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
var embed = await info.AsEmbedAsync().ConfigureAwait(false);
|
||||
await ctx.RespondAsync(embed: embed.Build()).ConfigureAwait(false);
|
||||
|
24
CompatBot/Commands/CustomBaseCommand.cs
Normal file
24
CompatBot/Commands/CustomBaseCommand.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Database.Providers;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
{
|
||||
internal class BaseCommandModuleCustom : BaseCommandModule
|
||||
{
|
||||
public override async Task BeforeExecutionAsync(CommandContext ctx)
|
||||
{
|
||||
var disabledCmds = DisabledCommandsProvider.Get();
|
||||
if (disabledCmds.Contains(ctx.Command.QualifiedName) && !disabledCmds.Contains("*"))
|
||||
{
|
||||
await ctx.RespondAsync(embed: new DiscordEmbedBuilder {Color = Config.Colors.Maintenance, Description = "Command is currently disabled"}).ConfigureAwait(false);
|
||||
throw new DSharpPlus.CommandsNext.Exceptions.ChecksFailedException(ctx.Command, ctx, new CheckBaseAttribute[] {new RequiresDm()});
|
||||
}
|
||||
|
||||
await base.BeforeExecutionAsync(ctx).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CompatApiClient.Utils;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Database;
|
||||
using CompatBot.Utils;
|
||||
@ -16,7 +17,7 @@ namespace CompatBot.Commands
|
||||
[Group("explain"), Aliases("botsplain", "define")]
|
||||
[Cooldown(1, 3, CooldownBucketType.Channel)]
|
||||
[Description("Used to manage and show explanations")]
|
||||
internal sealed class Explain: BaseCommandModule
|
||||
internal sealed class Explain: BaseCommandModuleCustom
|
||||
{
|
||||
[GroupCommand]
|
||||
public async Task ShowExplanation(CommandContext ctx, [RemainingText, Description("Term to explain")] string term)
|
||||
@ -65,7 +66,7 @@ namespace CompatBot.Commands
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
||||
await ctx.RespondAsync($"Unknown term `{term.Sanitize()}`. Use `!explain list` to look at defined terms").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("add"), RequiresBotModRole]
|
||||
@ -76,26 +77,18 @@ namespace CompatBot.Commands
|
||||
{
|
||||
term = term.StripQuotes();
|
||||
if (string.IsNullOrEmpty(explanation))
|
||||
{
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(Config.Reactions.Failure),
|
||||
ctx.RespondAsync("An explanation for the term must be provided")
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "An explanation for the term must be provided").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
if (await db.Explanation.AnyAsync(e => e.Keyword == term).ConfigureAwait(false))
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(Config.Reactions.Failure),
|
||||
ctx.RespondAsync($"'{term}' is already defined. Use `update` to update an existing term.")
|
||||
).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"`{term}` is already defined. Use `update` to update an existing term.").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
await db.Explanation.AddAsync(new Explanation {Keyword = term, Text = explanation}).ConfigureAwait(false);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"`{term}` was successfully added").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,17 +105,12 @@ namespace CompatBot.Commands
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
{
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(Config.Reactions.Failure),
|
||||
ctx.RespondAsync($"Term '{term}' is not defined")
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
item.Text = explanation;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, "Term was updated").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,17 +126,12 @@ namespace CompatBot.Commands
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == oldTerm).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
{
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(Config.Reactions.Failure),
|
||||
ctx.RespondAsync($"Term '{oldTerm}' is not defined")
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{oldTerm}` is not defined").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
item.Keyword = newTerm;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Renamed `{oldTerm}` to `{newTerm}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,14 +146,13 @@ namespace CompatBot.Commands
|
||||
if ("to".Equals(to, StringComparison.InvariantCultureIgnoreCase))
|
||||
await Rename(ctx, oldTerm, newTerm).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("list"), LimitedToSpamChannel]
|
||||
[Command("list"), LimitedToSpamChannel, TriggersTyping]
|
||||
[Description("List all known terms that could be used for !explain command")]
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var keywords = await db.Explanation.Select(e => e.Keyword).OrderBy(t => t).ToListAsync().ConfigureAwait(false);
|
||||
@ -199,17 +181,12 @@ namespace CompatBot.Commands
|
||||
{
|
||||
var item = await db.Explanation.FirstOrDefaultAsync(e => e.Keyword == term).ConfigureAwait(false);
|
||||
if (item == null)
|
||||
{
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(Config.Reactions.Failure),
|
||||
ctx.RespondAsync($"Term '{term}' is not defined")
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Term `{term}` is not defined").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
db.Explanation.Remove(item);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Removed `{term}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,18 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Utils;
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
using DSharpPlus.CommandsNext.Converters;
|
||||
using DSharpPlus.Entities;
|
||||
using org.mariuszgromada.math.mxparser;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
{
|
||||
internal sealed class Misc: BaseCommandModule
|
||||
internal sealed class Misc: BaseCommandModuleCustom
|
||||
{
|
||||
private readonly Random rng = new Random();
|
||||
|
||||
@ -41,27 +43,40 @@ namespace CompatBot.Commands
|
||||
"So-so"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> Me = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
||||
{
|
||||
"I", "me", "myself", "moi", "self"
|
||||
};
|
||||
|
||||
[Command("credits"), Aliases("about")]
|
||||
[Description("Author Credit")]
|
||||
public async Task Credits(CommandContext ctx)
|
||||
{
|
||||
var embed = new DiscordEmbedBuilder
|
||||
DiscordEmoji hcorion;
|
||||
try
|
||||
{
|
||||
Title = "RPCS3 Compatibility Bot",
|
||||
Url = "https://github.com/RPCS3/discord-bot",
|
||||
Description = "Made by:\n" +
|
||||
" Roberto Anić Banić aka nicba1010\n" +
|
||||
" 13xforever".FixSpaces(),
|
||||
Color = DiscordColor.Purple,
|
||||
};
|
||||
hcorion = DiscordEmoji.FromName(ctx.Client, ":hcorion:");
|
||||
}
|
||||
catch
|
||||
{
|
||||
hcorion = DiscordEmoji.FromUnicode("🍁");
|
||||
}
|
||||
var embed = new DiscordEmbedBuilder
|
||||
{
|
||||
Title = "RPCS3 Compatibility Bot",
|
||||
Url = "https://github.com/RPCS3/discord-bot",
|
||||
Color = DiscordColor.Purple,
|
||||
}.AddField("Made by", "🇭🇷 Roberto Anić Banić aka nicba1010\n" +
|
||||
"💮 13xforever".FixSpaces())
|
||||
.AddField("People who ~~broke~~ helped test the bot", "🐱 Juhn\n" +
|
||||
$"{hcorion} hcorion");
|
||||
await ctx.RespondAsync(embed: embed.Build());
|
||||
}
|
||||
|
||||
[Command("math")]
|
||||
[Command("math"), TriggersTyping]
|
||||
[Description("Math, here you go Juhn")]
|
||||
public async Task Math(CommandContext ctx, [RemainingText, Description("Math expression")] string expression)
|
||||
{
|
||||
var typing = ctx.TriggerTypingAsync();
|
||||
var result = @"Something went wrong ¯\\_(ツ)\_/¯" + "\nMath is hard, yo";
|
||||
try
|
||||
{
|
||||
@ -73,35 +88,28 @@ namespace CompatBot.Commands
|
||||
ctx.Client.DebugLogger.LogMessage(LogLevel.Warning, "", "Math failed: " + e.Message, DateTime.Now);
|
||||
}
|
||||
await ctx.RespondAsync(result).ConfigureAwait(false);
|
||||
await typing.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("roll")]
|
||||
[Description("Generates a random number between 1 and N. Can also roll dices like `2d6`. Default is 1d6")]
|
||||
public Task Roll(CommandContext ctx)
|
||||
{
|
||||
return Roll(ctx, 6);
|
||||
}
|
||||
|
||||
[Command("roll")]
|
||||
public async Task Roll(CommandContext ctx, [Description("Some positive number")] int maxValue)
|
||||
[Description("Generates a random number between 1 and maxValue. Can also roll dices like `2d6`. Default is 1d6")]
|
||||
public async Task Roll(CommandContext ctx, [Description("Some positive natural number")] int maxValue = 6, [Description("Optional text"), RemainingText] string comment = null)
|
||||
{
|
||||
string result = null;
|
||||
if (maxValue > 1)
|
||||
lock (rng) result = (rng.Next(maxValue) + 1).ToString();
|
||||
if (string.IsNullOrEmpty(result))
|
||||
await ctx.Message.CreateReactionAsync(DiscordEmoji.FromUnicode("💩")).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(DiscordEmoji.FromUnicode("💩"), $"How is {maxValue} a positive natural number?").ConfigureAwait(false);
|
||||
else
|
||||
await ctx.RespondAsync(result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("roll")]
|
||||
public async Task Roll(CommandContext ctx, [Description("Dices to roll (i.e. 2d6 for two 6-sided dices)")] string dices)
|
||||
public async Task Roll(CommandContext ctx, [Description("Dices to roll (i.e. 2d6 for two 6-sided dices)")] string dices, [Description("Optional text"), RemainingText] string comment = null)
|
||||
{
|
||||
var result = "";
|
||||
if (dices is string dice && Regex.IsMatch(dice, @"\d+d\d+"))
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var diceParts = dice.Split('d', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (int.TryParse(diceParts[0], out var num) && int.TryParse(diceParts[1], out var face) &&
|
||||
0 < num && num < 101 &&
|
||||
@ -117,10 +125,9 @@ namespace CompatBot.Commands
|
||||
else
|
||||
result = rolls.Sum().ToString();
|
||||
}
|
||||
await typingTask.ConfigureAwait(false);
|
||||
}
|
||||
if (string.IsNullOrEmpty(result))
|
||||
await ctx.Message.CreateReactionAsync(DiscordEmoji.FromUnicode("💩")).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(DiscordEmoji.FromUnicode("💩"), "Invalid dice description passed").ConfigureAwait(false);
|
||||
else
|
||||
await ctx.RespondAsync(result).ConfigureAwait(false);
|
||||
}
|
||||
@ -130,7 +137,8 @@ namespace CompatBot.Commands
|
||||
public async Task EightBall(CommandContext ctx, [RemainingText, Description("A yes/no question")] string question)
|
||||
{
|
||||
string answer;
|
||||
lock (rng) answer = EightBallAnswers[rng.Next(EightBallAnswers.Count)];
|
||||
lock (rng)
|
||||
answer = EightBallAnswers[rng.Next(EightBallAnswers.Count)];
|
||||
await ctx.RespondAsync(answer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -138,8 +146,30 @@ namespace CompatBot.Commands
|
||||
[Description("Gives an ~~unrelated~~ expert judgement on the matter at hand")]
|
||||
public async Task Rate(CommandContext ctx, [RemainingText, Description("Something to rate")] string whatever)
|
||||
{
|
||||
string answer;
|
||||
lock (rng) answer = RateAnswers[rng.Next(RateAnswers.Count)];
|
||||
var choices = RateAnswers;
|
||||
var whateverParts = whatever.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
|
||||
if (whatever is string neko && (neko.Contains("neko", StringComparison.InvariantCultureIgnoreCase) || neko.Contains("272032356922032139")))
|
||||
{
|
||||
choices = RateAnswers.Concat(Enumerable.Repeat("Ugh", 100)).ToList();
|
||||
if (await new DiscordUserConverter().ConvertAsync("272032356922032139", ctx).ConfigureAwait(false) is Optional<DiscordUser> user && user.HasValue)
|
||||
whatever = user.Value.Id.ToString();
|
||||
}
|
||||
else if (whatever is string sonic && sonic.Contains("sonic"))
|
||||
{
|
||||
choices = RateAnswers.Concat(Enumerable.Repeat("💩 out of 🦔", 100)).Concat(new []{"Sonic out of 🦔", "Sonic out of 10"}).ToList();
|
||||
whatever = "Sonic";
|
||||
}
|
||||
else if (whateverParts.Length == 1)
|
||||
{
|
||||
if (Me.Contains(whateverParts[0]))
|
||||
whatever = ctx.Message.Author.Id.ToString();
|
||||
else if (await new DiscordUserConverter().ConvertAsync(whateverParts[0], ctx).ConfigureAwait(false) is Optional<DiscordUser> user && user.HasValue)
|
||||
whatever = user.Value.Id.ToString();
|
||||
}
|
||||
whatever = DateTime.UtcNow.ToString("yyyyMMdd") + whatever?.Trim();
|
||||
var seed = whatever.GetHashCode(StringComparison.CurrentCultureIgnoreCase);
|
||||
var seededRng = new Random(seed);
|
||||
var answer = choices[seededRng.Next(choices.Count)];
|
||||
await ctx.RespondAsync(answer).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
44
CompatBot/Commands/Psn.Check.cs
Normal file
44
CompatBot/Commands/Psn.Check.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CompatApiClient.Utils;
|
||||
using CompatBot.EventHandlers;
|
||||
using CompatBot.Utils;
|
||||
using CompatBot.Utils.ResultFormatters;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
using PsnClient;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
{
|
||||
internal sealed partial class Psn
|
||||
{
|
||||
private static readonly Client Client = new Client();
|
||||
|
||||
[Group("check")]
|
||||
[Description("Commands to check for various stuff on PSN")]
|
||||
public sealed class Check : BaseCommandModuleCustom
|
||||
{
|
||||
[Command("updates"), Aliases("update")]
|
||||
[Description("Checks if specified product has any updates")]
|
||||
public async Task Updates(CommandContext ctx, [RemainingText, Description("Product ID such as BLUS12345")] string productId)
|
||||
{
|
||||
productId = ProductCodeLookup.GetProductIds(productId).FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(productId))
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"`{productId.Sanitize()}` is not a valid product ID").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var updateInfo = await Client.GetTitleUpdatesAsync(productId, Config.Cts.Token).ConfigureAwait(false);
|
||||
if (updateInfo?.Tag?.Packages?.Length > 0)
|
||||
{
|
||||
var embed = await updateInfo.AsEmbedAsync(ctx.Client).ConfigureAwait(false);
|
||||
await ctx.RespondAsync(embed: embed).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.RespondAsync("No updates were found").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
63
CompatBot/Commands/Psn.cs
Normal file
63
CompatBot/Commands/Psn.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Database;
|
||||
using CompatBot.Utils;
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
{
|
||||
[Group("psn")]
|
||||
[Description("Commands related to PSN metadata")]
|
||||
internal sealed partial class Psn: BaseCommandModuleCustom
|
||||
{
|
||||
[Command("fix"), RequiresBotModRole]
|
||||
[Description("Reset thumbnail cache for specified product")]
|
||||
public async Task Fix(CommandContext ctx, [Description("Product ID to reset")] string productId)
|
||||
{
|
||||
var linksToRemove = new List<(string contentId, string link)>();
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var items = db.Thumbnail.Where(i => i.ProductCode == productId && !string.IsNullOrEmpty(i.EmbeddableUrl));
|
||||
foreach (var thumb in items)
|
||||
{
|
||||
linksToRemove.Add((thumb.ContentId, thumb.EmbeddableUrl));
|
||||
thumb.EmbeddableUrl = null;
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
await TryDeleteThumbnailCache(ctx, linksToRemove).ConfigureAwait(false);
|
||||
await ctx.RespondAsync($"Removed {linksToRemove.Count} cached links").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task TryDeleteThumbnailCache(CommandContext ctx, List<(string contentId, string link)> linksToRemove)
|
||||
{
|
||||
var contentIds = linksToRemove.ToDictionary(l => l.contentId, l => l.link);
|
||||
try
|
||||
{
|
||||
var channel = await ctx.Client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);
|
||||
var messages = await channel.GetMessagesAsync(1000).ConfigureAwait(false);
|
||||
foreach (var msg in messages)
|
||||
if (contentIds.TryGetValue(msg.Content, out var lnk) && msg.Attachments.Any(a => a.Url == lnk))
|
||||
{
|
||||
try
|
||||
{
|
||||
await msg.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ctx.Client.DebugLogger.LogMessage(LogLevel.Warning, "", "Couldn't delete cached thumbnail image: " + e, DateTime.Now);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ctx.Client.DebugLogger.LogMessage(LogLevel.Warning, "", e.ToString(), DateTime.Now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
198
CompatBot/Commands/Sudo.Bot.Commands.cs
Normal file
198
CompatBot/Commands/Sudo.Bot.Commands.cs
Normal file
@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Database.Providers;
|
||||
using CompatBot.Utils;
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
|
||||
namespace CompatBot.Commands
|
||||
{
|
||||
internal partial class Sudo
|
||||
{
|
||||
[Group]
|
||||
[Description("Used to enabe and disable bot commands at runtime")]
|
||||
public sealed class Commands : BaseCommandModule
|
||||
{
|
||||
[Command("list"), Aliases("show"), TriggersTyping]
|
||||
[Description("Lists the disabled commands")]
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
var list = DisabledCommandsProvider.Get();
|
||||
if (list.Count > 0)
|
||||
{
|
||||
var result = new StringBuilder("Currently disabled commands:").AppendLine().AppendLine("```");
|
||||
foreach (var cmd in list)
|
||||
result.AppendLine(cmd);
|
||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.RespondAsync("All commands are enabled").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("disable"), Aliases("add")]
|
||||
[Description("Disables the specified command")]
|
||||
public async Task Disable(CommandContext ctx, [RemainingText, Description("Fully qualified command to disable, e.g. `explain add` or `sudo mod *`")] string command)
|
||||
{
|
||||
var isPrefix = command.EndsWith('*');
|
||||
if (isPrefix)
|
||||
command = command.TrimEnd('*', ' ');
|
||||
|
||||
if (string.IsNullOrEmpty(command) && !isPrefix)
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "You need to specify the command").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.StartsWith(ctx.Command.Parent.QualifiedName))
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "Cannot disable command management commands").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var cmd = GetCommand(ctx, command);
|
||||
if (isPrefix)
|
||||
{
|
||||
if (cmd == null && !string.IsNullOrEmpty(command))
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Unknown group `{command}`").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (cmd == null)
|
||||
foreach (var c in ctx.CommandsNext.RegisteredCommands.Values)
|
||||
DisableSubcommands(ctx, c);
|
||||
else
|
||||
DisableSubcommands(ctx, cmd);
|
||||
if (ctx.Command.Parent.QualifiedName.StartsWith(command))
|
||||
await ctx.RespondAsync("Some subcommands cannot be disabled").ConfigureAwait(false);
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Disabled `{command}` and all subcommands").ConfigureAwait(false);
|
||||
await List(ctx).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ctx.Client.DebugLogger.LogMessage(LogLevel.Error, "", e.ToString(), DateTime.Now);
|
||||
await ctx.RespondAsync("Error while disabling the group").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cmd == null)
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Unknown command `{command}`").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
command = cmd.QualifiedName;
|
||||
DisabledCommandsProvider.Disable(command);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Disabled `{command}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("enable"), Aliases("reenable", "remove", "delete", "del", "clear")]
|
||||
[Description("Enables the specified command")]
|
||||
public async Task Enable(CommandContext ctx, [RemainingText, Description("Fully qualified command to enable, e.g. `explain add` or `sudo mod *`")] string command)
|
||||
{
|
||||
if (command == "*")
|
||||
{
|
||||
DisabledCommandsProvider.Clear();
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, "Enabled all the commands").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var isPrefix = command.EndsWith('*');
|
||||
if (isPrefix)
|
||||
command = command.TrimEnd('*', ' ');
|
||||
|
||||
if (string.IsNullOrEmpty(command))
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "You need to specify the command").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var cmd = GetCommand(ctx, command);
|
||||
if (isPrefix)
|
||||
{
|
||||
if (cmd == null)
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Unknown group `{command}`").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
EnableSubcommands(ctx, cmd);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Enabled `{command}` and all subcommands").ConfigureAwait(false);
|
||||
await List(ctx).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ctx.Client.DebugLogger.LogMessage(LogLevel.Error, "", e.ToString(), DateTime.Now);
|
||||
await ctx.RespondAsync("Error while enabling the group").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cmd == null)
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"Unknown command `{command}`").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
command = cmd.QualifiedName;
|
||||
DisabledCommandsProvider.Enable(command);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"Enabled `{command}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static Command GetCommand(CommandContext ctx, string qualifiedName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(qualifiedName))
|
||||
return null;
|
||||
|
||||
var groups = ctx.CommandsNext.RegisteredCommands.Values;
|
||||
Command result = null;
|
||||
foreach (var cmdPart in qualifiedName.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (groups.FirstOrDefault(g => g.Name == cmdPart || g.Aliases.Any(a => a == cmdPart)) is Command c)
|
||||
{
|
||||
result = c;
|
||||
if (c is CommandGroup subGroup)
|
||||
groups = subGroup.Children;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void DisableSubcommands(CommandContext ctx, Command cmd)
|
||||
{
|
||||
if (cmd.QualifiedName.StartsWith(ctx.Command.Parent.QualifiedName))
|
||||
return;
|
||||
|
||||
DisabledCommandsProvider.Disable(cmd.QualifiedName);
|
||||
if (cmd is CommandGroup group)
|
||||
foreach (var subCmd in group.Children)
|
||||
DisableSubcommands(ctx, subCmd);
|
||||
}
|
||||
|
||||
private static void EnableSubcommands(CommandContext ctx, Command cmd)
|
||||
{
|
||||
if (cmd.QualifiedName.StartsWith(ctx.Command.Parent.QualifiedName))
|
||||
return;
|
||||
|
||||
DisabledCommandsProvider.Enable(cmd.QualifiedName);
|
||||
if (cmd is CommandGroup group)
|
||||
foreach (var subCmd in group.Children)
|
||||
EnableSubcommands(ctx, subCmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Utils;
|
||||
using DSharpPlus.CommandsNext;
|
||||
using DSharpPlus.CommandsNext.Attributes;
|
||||
@ -15,13 +16,12 @@ namespace CompatBot.Commands
|
||||
|
||||
[Group("bot")]
|
||||
[Description("Commands to manage the bot instance")]
|
||||
public sealed class Bot: BaseCommandModule
|
||||
public sealed partial class Bot: BaseCommandModuleCustom
|
||||
{
|
||||
[Command("version")]
|
||||
[Command("version"), TriggersTyping]
|
||||
[Description("Returns currently checked out bot commit")]
|
||||
public async Task Version(CommandContext ctx)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
using (var git = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo("git", "log -1 --oneline")
|
||||
@ -39,14 +39,12 @@ namespace CompatBot.Commands
|
||||
if (!string.IsNullOrEmpty(stdout))
|
||||
await ctx.RespondAsync("```" + stdout + "```").ConfigureAwait(false);
|
||||
}
|
||||
await typingTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("restart"), Aliases("update")]
|
||||
[Command("restart"), Aliases("update"), TriggersTyping]
|
||||
[Description("Restarts bot and pulls newest commit")]
|
||||
public async Task Restart(CommandContext ctx)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
if (lockObj.Wait(0))
|
||||
{
|
||||
try
|
||||
@ -94,7 +92,6 @@ namespace CompatBot.Commands
|
||||
}
|
||||
else
|
||||
await ctx.RespondAsync("Update is already in progress").ConfigureAwait(false);
|
||||
await typingTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("stop"), Aliases("exit", "shutdown", "terminate")]
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Commands.Converters;
|
||||
using CompatBot.Database;
|
||||
using DSharpPlus;
|
||||
@ -18,13 +19,12 @@ namespace CompatBot.Commands
|
||||
|
||||
[Group("fix"), Hidden]
|
||||
[Description("Commands to fix various stuff")]
|
||||
public sealed class Fix: BaseCommandModule
|
||||
public sealed class Fix: BaseCommandModuleCustom
|
||||
{
|
||||
[Command("timestamps")]
|
||||
[Command("timestamps"), TriggersTyping]
|
||||
[Description("Fixes `timestamp` column in the `warning` table")]
|
||||
public async Task Timestamps(CommandContext ctx)
|
||||
{
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var @fixed = 0;
|
||||
@ -52,11 +52,10 @@ namespace CompatBot.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[Command("channels")]
|
||||
[Command("channels"), TriggersTyping]
|
||||
[Description("Fixes channel mentions in `warning` table")]
|
||||
public async Task Channels(CommandContext ctx)
|
||||
{
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var @fixed = 0;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands.Attributes;
|
||||
using CompatBot.Database.Providers;
|
||||
using CompatBot.Utils;
|
||||
using DSharpPlus.CommandsNext;
|
||||
@ -12,106 +13,83 @@ namespace CompatBot.Commands
|
||||
{
|
||||
[Group("mod")]
|
||||
[Description("Used to manage bot moderators")]
|
||||
public sealed class Mod : BaseCommandModule
|
||||
public sealed class Mod : BaseCommandModuleCustom
|
||||
{
|
||||
[Command("add")]
|
||||
[Command("add"), TriggersTyping]
|
||||
[Description("Adds a new moderator")]
|
||||
public async Task Add(CommandContext ctx, [Description("Discord user to add to the bot mod list")] DiscordMember user)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
(DiscordEmoji reaction, string msg) result =
|
||||
await ModProvider.AddAsync(user.Id).ConfigureAwait(false)
|
||||
? (Config.Reactions.Success, $"{user.Mention} was successfully added as moderator, you now have access to editing the piracy trigger list and other useful things! " +
|
||||
"I will send you the available commands to your message box!")
|
||||
: (Config.Reactions.Failure, $"{user.Mention} is already a moderator");
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(result.reaction),
|
||||
ctx.RespondAsync(result.msg),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
if (await ModProvider.AddAsync(user.Id).ConfigureAwait(false))
|
||||
{
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success,
|
||||
$"{user.Mention} was successfully added as moderator!\n" +
|
||||
"Try using `!help` to see new commands available to you"
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"{user.Mention} is already a moderator").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("remove"), Aliases("delete", "del")]
|
||||
[Command("remove"), Aliases("delete", "del"), TriggersTyping]
|
||||
[Description("Removes a moderator")]
|
||||
public async Task Remove(CommandContext ctx, [Description("Discord user to remove from the bot mod list")] DiscordMember user)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
(DiscordEmoji reaction, string msg) result;
|
||||
if (user.Id == Config.BotAdminId)
|
||||
{
|
||||
result = (Config.Reactions.Denied, $"{ctx.Message.Author.Mention} why would you even try this?! Alerting {user.Mention}");
|
||||
var dm = await user.CreateDmChannelAsync().ConfigureAwait(false);
|
||||
await dm.SendMessageAsync($@"Just letting you know that {ctx.Message.Author.Mention} just tried to strip you off of your mod role ¯\_(ツ)_/¯").ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Denied, $"{ctx.Message.Author.Mention} why would you even try this?! Alerting {user.Mention}", true).ConfigureAwait(false);
|
||||
}
|
||||
else if (await ModProvider.RemoveAsync(user.Id).ConfigureAwait(false))
|
||||
result = (Config.Reactions.Success, $"{user.Mention} removed as moderator!");
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"{user.Mention} removed as moderator!").ConfigureAwait(false);
|
||||
else
|
||||
result = (Config.Reactions.Failure, $"{user.Mention} is not a moderator");
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(result.reaction),
|
||||
ctx.RespondAsync(result.msg),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"{user.Mention} is not a moderator").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("list"), Aliases("show")]
|
||||
[Command("list"), Aliases("show"), TriggersTyping]
|
||||
[Description("Lists all moderators")]
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
var list = new StringBuilder("```");
|
||||
foreach (var mod in ModProvider.Mods.Values)
|
||||
list.AppendLine($"{await ctx.GetUserNameAsync(mod.DiscordId),-32} | {(mod.Sudoer ? "sudo" : "not sudo")}");
|
||||
await ctx.SendAutosplitMessageAsync(list.Append("```")).ConfigureAwait(false);
|
||||
await typingTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("sudo")]
|
||||
[Command("sudo"), TriggersTyping]
|
||||
[Description("Makes a moderator a sudoer")]
|
||||
public async Task Sudo(CommandContext ctx, [Description("Discord user on the moderator list to grant the sudoer rights to")] DiscordMember moderator)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
(DiscordEmoji reaction, string msg) result;
|
||||
if (ModProvider.IsMod(moderator.Id))
|
||||
{
|
||||
result = await ModProvider.MakeSudoerAsync(moderator.Id).ConfigureAwait(false)
|
||||
? (Config.Reactions.Success, $"{moderator.Mention} is now a sudoer")
|
||||
: (Config.Reactions.Failure, $"{moderator.Mention} is already a sudoer");
|
||||
if (await ModProvider.MakeSudoerAsync(moderator.Id).ConfigureAwait(false))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"{moderator.Mention} is now a sudoer").ConfigureAwait(false);
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"{moderator.Mention} is already a sudoer").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
result = (Config.Reactions.Failure, $"{moderator.Mention} is not a moderator (yet)");
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(result.reaction),
|
||||
ctx.RespondAsync(result.msg),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"{moderator.Mention} is not a moderator (yet)").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("unsudo")]
|
||||
[Command("unsudo"), TriggersTyping]
|
||||
[Description("Makes a sudoer a regular moderator")]
|
||||
public async Task Unsudo(CommandContext ctx, [Description("Discord user on the moderator list to strip the sudoer rights from")] DiscordMember sudoer)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
(DiscordEmoji reaction, string msg) result;
|
||||
if (sudoer.Id == Config.BotAdminId)
|
||||
{
|
||||
result = (Config.Reactions.Denied, $"{ctx.Message.Author.Mention} why would you even try this?! Alerting {sudoer.Mention}");
|
||||
var dm = await sudoer.CreateDmChannelAsync().ConfigureAwait(false);
|
||||
await dm.SendMessageAsync($@"Just letting you know that {ctx.Message.Author.Mention} just tried to strip you off of your sudo permissions ¯\_(ツ)_/¯").ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Denied, $"{ctx.Message.Author.Mention} why would you even try this?! Alerting {sudoer.Mention}", true).ConfigureAwait(false);
|
||||
}
|
||||
else if (ModProvider.IsMod(sudoer.Id))
|
||||
{
|
||||
result = await ModProvider.UnmakeSudoerAsync(sudoer.Id).ConfigureAwait(false)
|
||||
? (Config.Reactions.Success, $"{sudoer.Mention} is no longer a sudoer")
|
||||
: (Config.Reactions.Failure, $"{sudoer.Mention} is not a sudoer");
|
||||
if (await ModProvider.UnmakeSudoerAsync(sudoer.Id).ConfigureAwait(false))
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success, $"{sudoer.Mention} is no longer a sudoer").ConfigureAwait(false);
|
||||
else
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"{sudoer.Mention} is not a sudoer").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
result = (Config.Reactions.Failure, $"{sudoer.Mention} is not even a moderator!");
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(result.reaction),
|
||||
ctx.RespondAsync(result.msg),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, $"{sudoer.Mention} is not even a moderator!").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace CompatBot.Commands
|
||||
{
|
||||
[Group("sudo"), RequiresBotSudoerRole]
|
||||
[Description("Used to manage bot moderators and sudoers")]
|
||||
internal sealed partial class Sudo : BaseCommandModule
|
||||
internal sealed partial class Sudo : BaseCommandModuleCustom
|
||||
{
|
||||
[Command("say"), Priority(10)]
|
||||
[Description("Make bot say things, optionally in a specific channel")]
|
||||
|
@ -15,43 +15,36 @@ namespace CompatBot.Commands
|
||||
{
|
||||
internal sealed partial class Warnings
|
||||
{
|
||||
[Group("list"), Aliases("show")]
|
||||
[Group("list"), Aliases("show"), TriggersTyping]
|
||||
[Description("Allows to list warnings in various ways. Users can only see their own warnings.")]
|
||||
public class ListGroup : BaseCommandModule
|
||||
public class ListGroup : BaseCommandModuleCustom
|
||||
{
|
||||
[GroupCommand, Priority(10)]
|
||||
[Description("Show warning list for a user. Default is to show warning list for yourself")]
|
||||
public async Task List(CommandContext ctx, [Description("Discord user to list warnings for")] DiscordUser user)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
if (await CheckListPermissionAsync(ctx, user.Id).ConfigureAwait(false))
|
||||
await ListUserWarningsAsync(ctx.Client, ctx.Message, user.Id, user.Username.Sanitize(), false);
|
||||
await typingTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[GroupCommand]
|
||||
public async Task List(CommandContext ctx, [Description("Id of the user to list warnings for")] ulong userId)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
if (await CheckListPermissionAsync(ctx, userId).ConfigureAwait(false))
|
||||
await ListUserWarningsAsync(ctx.Client, ctx.Message, userId, $"<@{userId}>", false);
|
||||
await typingTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[GroupCommand]
|
||||
[Description("List your own warning list")]
|
||||
public async Task List(CommandContext ctx)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
await List(ctx, ctx.Message.Author).ConfigureAwait(false);
|
||||
await typingTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("users"), RequiresBotModRole]
|
||||
[Command("users"), RequiresBotModRole, TriggersTyping]
|
||||
[Description("List users with warnings, sorted from most warned to least")]
|
||||
public async Task Users(CommandContext ctx)
|
||||
{
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
var userIdColumn = ctx.Channel.IsPrivate ? $"{"User ID",-18} | " : "";
|
||||
var header = $"{"User",-25} | {userIdColumn}Count";
|
||||
var result = new StringBuilder("Warning count per user:").AppendLine("```")
|
||||
@ -77,11 +70,10 @@ namespace CompatBot.Commands
|
||||
await ctx.SendAutosplitMessageAsync(result.Append("```")).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("recent"), Aliases("last", "all"), RequiresBotModRole]
|
||||
[Command("recent"), Aliases("last", "all"), RequiresBotModRole, TriggersTyping]
|
||||
[Description("Shows last issued warnings in chronological order")]
|
||||
public async Task Last(CommandContext ctx, [Description("Optional number of items to show. Default is 10")] int number = 10)
|
||||
{
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
if (number < 1)
|
||||
number = 10;
|
||||
var userIdColumn = ctx.Channel.IsPrivate ? $"{"User ID",-18} | " : "";
|
||||
@ -116,10 +108,7 @@ namespace CompatBot.Commands
|
||||
if (userId == ctx.Message.Author.Id || ModProvider.IsMod(ctx.Message.Author.Id))
|
||||
return true;
|
||||
|
||||
await Task.WhenAll(
|
||||
ctx.Message.CreateReactionAsync(Config.Reactions.Denied),
|
||||
ctx.RespondAsync("Regular users can only view their own warnings")
|
||||
).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Denied, "Regular users can only view their own warnings").ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace CompatBot.Commands
|
||||
{
|
||||
[Group("warn")]
|
||||
[Description("Command used to manage warnings")]
|
||||
internal sealed partial class Warnings: BaseCommandModule
|
||||
internal sealed partial class Warnings: BaseCommandModuleCustom
|
||||
{
|
||||
[GroupCommand] //attributes on overloads do not work, so no easy permission checks
|
||||
[Description("Command used to issue a new warning")]
|
||||
@ -26,12 +26,11 @@ namespace CompatBot.Commands
|
||||
if (!await new RequiresBotModRole().ExecuteCheckAsync(ctx, false).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
if (await AddAsync(ctx, user.Id, user.Username.Sanitize(), ctx.Message.Author, reason).ConfigureAwait(false))
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
||||
await typingTask;
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "Couldn't save the warning, please try again").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[GroupCommand, RequiresBotModRole]
|
||||
@ -40,19 +39,17 @@ namespace CompatBot.Commands
|
||||
if (!await new RequiresBotModRole().ExecuteCheckAsync(ctx, false).ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
await ctx.TriggerTypingAsync().ConfigureAwait(false);
|
||||
if (await AddAsync(ctx, userId, $"<@{userId}>", ctx.Message.Author, reason).ConfigureAwait(false))
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
await ctx.ReactWithAsync(Config.Reactions.Success).ConfigureAwait(false);
|
||||
else
|
||||
await ctx.Message.CreateReactionAsync(Config.Reactions.Failure).ConfigureAwait(false);
|
||||
await typingTask;
|
||||
await ctx.ReactWithAsync(Config.Reactions.Failure, "Couldn't save the warning, please try again").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("remove"), Aliases("delete", "del"), RequiresBotModRole]
|
||||
[Command("remove"), Aliases("delete", "del"), RequiresBotModRole, TriggersTyping]
|
||||
[Description("Removes specified warnings")]
|
||||
public async Task Remove(CommandContext ctx, [Description("Warning IDs to remove separated with space")] params int[] ids)
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
int removedCount;
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
@ -60,14 +57,10 @@ namespace CompatBot.Commands
|
||||
db.Warning.RemoveRange(warningsToRemove);
|
||||
removedCount = await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
(DiscordEmoji reaction, string msg) result = removedCount == ids.Length
|
||||
? (Config.Reactions.Success, $"Warning{(ids.Length == 1 ? "" : "s")} successfully removed!")
|
||||
: (Config.Reactions.Failure, $"Removed {removedCount} items, but was asked to remove {ids.Length}");
|
||||
await Task.WhenAll(
|
||||
ctx.RespondAsync(result.msg),
|
||||
ctx.Message.CreateReactionAsync(result.reaction),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
if (removedCount == ids.Length)
|
||||
await ctx.RespondAsync($"Warning{(ids.Length == 1 ? "" : "s")} successfully removed!").ConfigureAwait(false);
|
||||
else
|
||||
await ctx.RespondAsync($"Removed {removedCount} items, but was asked to remove {ids.Length}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("clear"), RequiresBotModRole]
|
||||
@ -77,12 +70,11 @@ namespace CompatBot.Commands
|
||||
return Clear(ctx, user.Id);
|
||||
}
|
||||
|
||||
[Command("clear"), RequiresBotModRole]
|
||||
[Command("clear"), RequiresBotModRole, TriggersTyping]
|
||||
public async Task Clear(CommandContext ctx, [Description("User ID to clear warnings for")] ulong userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var typingTask = ctx.TriggerTypingAsync();
|
||||
//var removed = await BotDb.Instance.Database.ExecuteSqlCommandAsync($"DELETE FROM `warning` WHERE `discord_id`={userId}").ConfigureAwait(false);
|
||||
int removed;
|
||||
using (var db = new BotDb())
|
||||
@ -91,11 +83,7 @@ namespace CompatBot.Commands
|
||||
db.Warning.RemoveRange(warningsToRemove);
|
||||
removed = await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
await Task.WhenAll(
|
||||
ctx.RespondAsync($"{removed} warning{(removed == 1 ? "" : "s")} successfully removed!"),
|
||||
ctx.Message.CreateReactionAsync(Config.Reactions.Success),
|
||||
typingTask
|
||||
).ConfigureAwait(false);
|
||||
await ctx.RespondAsync($"{removed} warning{(removed == 1 ? "" : "s")} successfully removed!").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TieredCompilation>true</TieredCompilation>
|
||||
<RootNamespace>CompatBot</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -11,6 +11,7 @@ namespace CompatBot.Database
|
||||
public DbSet<Piracystring> Piracystring { get; set; }
|
||||
public DbSet<Warning> Warning { get; set; }
|
||||
public DbSet<Explanation> Explanation { get; set; }
|
||||
public DbSet<DisabledCommand> DisabledCommands { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
@ -21,12 +22,10 @@ namespace CompatBot.Database
|
||||
{
|
||||
//configure indices
|
||||
modelBuilder.Entity<Moderator>().HasIndex(m => m.DiscordId).IsUnique().HasName("moderator_discord_id");
|
||||
|
||||
modelBuilder.Entity<Piracystring>().HasIndex(ps => ps.String).IsUnique().HasName("piracystring_string");
|
||||
|
||||
modelBuilder.Entity<Warning>().HasIndex(w => w.DiscordId).HasName("warning_discord_id");
|
||||
|
||||
modelBuilder.Entity<Explanation>().HasIndex(e => e.Keyword).IsUnique().HasName("explanation_keyword");
|
||||
modelBuilder.Entity<DisabledCommand>().HasIndex(e => e.Command).IsUnique().HasName("disabled_command_command");
|
||||
|
||||
//configure default policy of Id being the primary key
|
||||
modelBuilder.ConfigureDefaultPkConvention();
|
||||
@ -70,4 +69,11 @@ namespace CompatBot.Database
|
||||
[Required]
|
||||
public string Text { get; set; }
|
||||
}
|
||||
|
||||
internal class DisabledCommand
|
||||
{
|
||||
public int Id { get; set; }
|
||||
[Required]
|
||||
public string Command { get; set; }
|
||||
}
|
||||
}
|
||||
|
142
CompatBot/Database/Migrations/BotDb/20180804225045_DisabledCommands.Designer.cs
generated
Normal file
142
CompatBot/Database/Migrations/BotDb/20180804225045_DisabledCommands.Designer.cs
generated
Normal file
@ -0,0 +1,142 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CompatBot.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace CompatBot.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(BotDb))]
|
||||
[Migration("20180804225045_DisabledCommands")]
|
||||
partial class DisabledCommands
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.DisabledCommand", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Command")
|
||||
.IsRequired()
|
||||
.HasColumnName("command");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Command")
|
||||
.IsUnique()
|
||||
.HasName("disabled_command_command");
|
||||
|
||||
b.ToTable("disabled_commands");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Explanation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Keyword")
|
||||
.IsRequired()
|
||||
.HasColumnName("keyword");
|
||||
|
||||
b.Property<string>("Text")
|
||||
.IsRequired()
|
||||
.HasColumnName("text");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Keyword")
|
||||
.IsUnique()
|
||||
.HasName("explanation_keyword");
|
||||
|
||||
b.ToTable("explanation");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Moderator", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<bool>("Sudoer")
|
||||
.HasColumnName("sudoer");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.IsUnique()
|
||||
.HasName("moderator_discord_id");
|
||||
|
||||
b.ToTable("moderator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Piracystring", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("String")
|
||||
.IsRequired()
|
||||
.HasColumnName("string")
|
||||
.HasColumnType("varchar(255)");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("String")
|
||||
.IsUnique()
|
||||
.HasName("piracystring_string");
|
||||
|
||||
b.ToTable("piracystring");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Warning", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<string>("FullReason")
|
||||
.IsRequired()
|
||||
.HasColumnName("full_reason");
|
||||
|
||||
b.Property<ulong>("IssuerId")
|
||||
.HasColumnName("issuer_id");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.Property<long?>("Timestamp")
|
||||
.HasColumnName("timestamp");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.HasName("warning_discord_id");
|
||||
|
||||
b.ToTable("warning");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace CompatBot.Database.Migrations
|
||||
{
|
||||
public partial class DisabledCommands : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "disabled_commands",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
command = table.Column<string>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("id", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "disabled_command_command",
|
||||
table: "disabled_commands",
|
||||
column: "command",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "disabled_commands");
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,26 @@ namespace CompatBot.Database.Migrations
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.DisabledCommand", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Command")
|
||||
.IsRequired()
|
||||
.HasColumnName("command");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Command")
|
||||
.IsUnique()
|
||||
.HasName("disabled_command_command");
|
||||
|
||||
b.ToTable("disabled_commands");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Explanation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace CompatBot.Migrations
|
||||
{
|
||||
[DbContext(typeof(ThumbnailDb))]
|
||||
[Migration("20180801095653_InitialCreate")]
|
||||
[Migration("20180804120920_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@ -54,6 +54,9 @@ namespace CompatBot.Migrations
|
||||
b.Property<string>("EmbeddableUrl")
|
||||
.HasColumnName("embeddable_url");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("ProductCode")
|
||||
.IsRequired()
|
||||
.HasColumnName("product_code");
|
@ -28,6 +28,7 @@ namespace CompatBot.Migrations
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
product_code = table.Column<string>(nullable: false),
|
||||
content_id = table.Column<string>(nullable: true),
|
||||
name = table.Column<string>(nullable: true),
|
||||
url = table.Column<string>(nullable: true),
|
||||
embeddable_url = table.Column<string>(nullable: true),
|
||||
timestamp = table.Column<long>(nullable: false)
|
@ -52,6 +52,9 @@ namespace CompatBot.Migrations
|
||||
b.Property<string>("EmbeddableUrl")
|
||||
.HasColumnName("embeddable_url");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("ProductCode")
|
||||
.IsRequired()
|
||||
.HasColumnName("product_code");
|
||||
|
60
CompatBot/Database/Providers/DisabledCommandsProvider.cs
Normal file
60
CompatBot/Database/Providers/DisabledCommandsProvider.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CompatBot.Database.Providers
|
||||
{
|
||||
internal static class DisabledCommandsProvider
|
||||
{
|
||||
private static readonly HashSet<string> DisabledCommands = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
static DisabledCommandsProvider()
|
||||
{
|
||||
lock (DisabledCommands)
|
||||
using (var db = new BotDb())
|
||||
foreach (var cmd in db.DisabledCommands.ToList())
|
||||
DisabledCommands.Add(cmd.Command);
|
||||
}
|
||||
|
||||
public static HashSet<string> Get() => DisabledCommands;
|
||||
|
||||
public static void Disable(string command)
|
||||
{
|
||||
lock (DisabledCommands)
|
||||
if (DisabledCommands.Add(command))
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
db.DisabledCommands.Add(new DisabledCommand {Command = command});
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Enable(string command)
|
||||
{
|
||||
lock (DisabledCommands)
|
||||
if (DisabledCommands.Remove(command))
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
var cmd = db.DisabledCommands.FirstOrDefault(c => c.Command == command);
|
||||
if (cmd == null)
|
||||
return;
|
||||
|
||||
db.DisabledCommands.Remove(cmd);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
lock (DisabledCommands)
|
||||
{
|
||||
DisabledCommands.Clear();
|
||||
using (var db = new BotDb())
|
||||
{
|
||||
db.DisabledCommands.RemoveRange(db.DisabledCommands);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -31,11 +31,18 @@ namespace CompatBot.Database.Providers
|
||||
try
|
||||
{
|
||||
using (var httpClient = new HttpClient())
|
||||
using (var img = await httpClient.GetStreamAsync(url).ConfigureAwait(false))
|
||||
using (var imgStream = await httpClient.GetStreamAsync(url).ConfigureAwait(false))
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
await imgStream.CopyToAsync(memStream).ConfigureAwait(false);
|
||||
// minimum jpg size is 119 bytes, png is 67 bytes
|
||||
if (memStream.Length < 64)
|
||||
return null;
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
var spam = await client.GetChannelAsync(Config.ThumbnailSpamId).ConfigureAwait(false);
|
||||
//var message = await spam.SendFileAsync(img, (thumb.ContentId ?? thumb.ProductCode) + ".jpg").ConfigureAwait(false);
|
||||
var message = await spam.SendFileAsync((thumb.ContentId ?? thumb.ProductCode) + ".jpg", img).ConfigureAwait(false);
|
||||
//var message = await spam.SendFileAsync(memStream, (thumb.ContentId ?? thumb.ProductCode) + ".jpg").ConfigureAwait(false);
|
||||
var contentName = (thumb.ContentId ?? thumb.ProductCode);
|
||||
var message = await spam.SendFileAsync(contentName + ".jpg", memStream, contentName).ConfigureAwait(false);
|
||||
thumb.EmbeddableUrl = message.Attachments.First().Url;
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
return thumb.EmbeddableUrl;
|
||||
@ -49,5 +56,14 @@ namespace CompatBot.Database.Providers
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetTitleName(string productCode)
|
||||
{
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var thumb = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productCode.ToUpperInvariant());
|
||||
return thumb?.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ namespace CompatBot.Database
|
||||
[Required]
|
||||
public string ProductCode { get; set; }
|
||||
public string ContentId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string EmbeddableUrl { get; set; }
|
||||
public long Timestamp { get; set; }
|
||||
|
@ -38,7 +38,7 @@ namespace CompatBot.EventHandlers.LogParsing
|
||||
Extractors = new Dictionary<string, Regex>
|
||||
{
|
||||
["Serial:"] = new Regex(@"Serial: (?<serial>[A-z]{4}\d{5})\r?$", DefaultOptions),
|
||||
["Path:"] = new Regex(@"Path: ((?<win_path>\w:/)|(?<lin_path>/[^/])).*?\r?$", DefaultOptions),
|
||||
["Path:"] = new Regex(@"Path: ((?<win_path>\w:/)|(?<lin_path>/[^/]))(.*(?<hdd_game_path>/dev_hdd0/game/.*)/USRDIR.*?|.*?)\r?$", DefaultOptions),
|
||||
["custom config:"] = new Regex("custom config: (?<custom_config>.*?)\r?$", DefaultOptions),
|
||||
},
|
||||
OnNewLineAsync = PiracyCheckAsync,
|
||||
|
@ -63,8 +63,8 @@ namespace CompatBot.EventHandlers.LogParsing
|
||||
else if (result.IsCompleted)
|
||||
await FlushAllLinesAsync(result.Buffer, currentSectionLines, state).ConfigureAwait(false);
|
||||
var sectionStart = currentSectionLines.Count == 0 ? buffer : currentSectionLines.First.Value;
|
||||
reader.AdvanceTo(sectionStart.Start, buffer.End);
|
||||
totalReadBytes += result.Buffer.Slice(0, sectionStart.Start).Length;
|
||||
reader.AdvanceTo(sectionStart.Start, buffer.End);
|
||||
if (totalReadBytes >= Config.LogSizeLimit)
|
||||
{
|
||||
state.Error = LogParseState.ErrorCode.SizeLimit;
|
||||
|
@ -53,7 +53,15 @@ namespace CompatBot.EventHandlers.LogParsing
|
||||
#if DEBUG
|
||||
Console.WriteLine($"regex {group.Name} = {group.Value}");
|
||||
#endif
|
||||
state.WipCollection[group.Name] = group.Value.ToUtf8();
|
||||
if (group.Name == "rap_file")
|
||||
{
|
||||
var currentValue = state.WipCollection[group.Name];
|
||||
if (!string.IsNullOrEmpty(currentValue))
|
||||
currentValue += Environment.NewLine;
|
||||
state.WipCollection[group.Name] = currentValue + group.Value.ToUtf8();
|
||||
}
|
||||
else
|
||||
state.WipCollection[group.Name] = group.Value.ToUtf8();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,9 +52,7 @@ namespace CompatBot.EventHandlers
|
||||
}
|
||||
var previousReplies = previousRepliesBuilder?.ToString() ?? "";
|
||||
|
||||
var codesToLookup = ProductCode.Matches(args.Message.Content)
|
||||
.Select(match => (match.Groups["letters"].Value + match.Groups["numbers"]).ToUpper())
|
||||
.Distinct()
|
||||
var codesToLookup = GetProductIds(args.Message.Content)
|
||||
.Where(c => !previousReplies.Contains(c, StringComparison.InvariantCultureIgnoreCase))
|
||||
.Take(args.Channel.IsPrivate ? 50 : 5)
|
||||
.ToList();
|
||||
@ -76,6 +74,14 @@ namespace CompatBot.EventHandlers
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> GetProductIds(string input)
|
||||
{
|
||||
return ProductCode.Matches(input)
|
||||
.Select(match => (match.Groups["letters"].Value + match.Groups["numbers"]).ToUpper())
|
||||
.Distinct()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static bool NeedToSilence(DiscordMessage msg)
|
||||
{
|
||||
if (string.IsNullOrEmpty(msg.Content))
|
||||
@ -108,16 +114,14 @@ namespace CompatBot.EventHandlers
|
||||
if (result?.ReturnCode == -1)
|
||||
return TitleInfo.CommunicationError.AsEmbed(null, footer);
|
||||
|
||||
var thumbnailUrl = await client.GetThumbnailUrlAsync(code).ConfigureAwait(false);
|
||||
if (result?.Results == null)
|
||||
return TitleInfo.Unknown.AsEmbed(code, footer);
|
||||
return TitleInfo.Unknown.AsEmbed(code, footer, thumbnailUrl);
|
||||
|
||||
if (result.Results.TryGetValue(code, out var info))
|
||||
{
|
||||
var thumbnailUrl = await client.GetThumbnailUrlAsync(code).ConfigureAwait(false);
|
||||
return info.AsEmbed(code, footer, thumbnailUrl);
|
||||
}
|
||||
|
||||
return TitleInfo.Unknown.AsEmbed(code, footer);
|
||||
return TitleInfo.Unknown.AsEmbed(code, footer, thumbnailUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -104,7 +104,7 @@ namespace CompatBot.EventHandlers
|
||||
if (reporters.Count < Config.Moderation.StarbucksThreshold)
|
||||
return;
|
||||
|
||||
await message.CreateReactionAsync(emoji).ConfigureAwait(false);
|
||||
await message.ReactWithAsync(client, emoji).ConfigureAwait(false);
|
||||
await client.ReportAsync("User moderation report ⭐💵", message, reporters).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
32
CompatBot/EventHandlers/ThumbnailCacheMonitor.cs
Normal file
32
CompatBot/EventHandlers/ThumbnailCacheMonitor.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Database;
|
||||
using DSharpPlus.EventArgs;
|
||||
|
||||
namespace CompatBot.EventHandlers
|
||||
{
|
||||
internal static class ThumbnailCacheMonitor
|
||||
{
|
||||
public static async Task OnMessageDeleted(MessageDeleteEventArgs args)
|
||||
{
|
||||
if (args.Channel.Id != Config.ThumbnailSpamId)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrEmpty(args.Message.Content))
|
||||
return;
|
||||
|
||||
if (!args.Message.Attachments.Any())
|
||||
return;
|
||||
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var thumb = db.Thumbnail.FirstOrDefault(i => i.ContentId == args.Message.Content);
|
||||
if (thumb?.EmbeddableUrl is string url && !string.IsNullOrEmpty(url) && args.Message.Attachments.Any(a => a.Url == url))
|
||||
{
|
||||
thumb.EmbeddableUrl = null;
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Commands;
|
||||
using CompatBot.Commands.Converters;
|
||||
@ -13,93 +14,137 @@ namespace CompatBot
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private static readonly SemaphoreSlim InstanceCheck = new SemaphoreSlim(0, 1);
|
||||
private static readonly SemaphoreSlim ShutdownCheck = new SemaphoreSlim(0, 1);
|
||||
|
||||
internal static async Task Main(string[] args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Config.Token))
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
using (var instanceLock = new Mutex(false, @"Global\RPCS3 Compatibility Bot"))
|
||||
{
|
||||
if (instanceLock.WaitOne(1000))
|
||||
try
|
||||
{
|
||||
InstanceCheck.Release();
|
||||
ShutdownCheck.Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
instanceLock.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
});
|
||||
try
|
||||
{
|
||||
Console.WriteLine("No token was specified.");
|
||||
return;
|
||||
thread.Start();
|
||||
if (!InstanceCheck.Wait(1000))
|
||||
{
|
||||
Console.WriteLine("Another instance is already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Config.Token))
|
||||
{
|
||||
Console.WriteLine("No token was specified.");
|
||||
return;
|
||||
}
|
||||
|
||||
using (var db = new BotDb())
|
||||
if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
|
||||
return;
|
||||
|
||||
using (var db = new ThumbnailDb())
|
||||
if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
|
||||
return;
|
||||
|
||||
var psnScrappingTask = new PsnScraper().Run(Config.Cts.Token);
|
||||
|
||||
var config = new DiscordConfiguration
|
||||
{
|
||||
Token = Config.Token,
|
||||
TokenType = TokenType.Bot,
|
||||
UseInternalLogHandler = true,
|
||||
//LogLevel = LogLevel.Debug,
|
||||
};
|
||||
|
||||
using (var client = new DiscordClient(config))
|
||||
{
|
||||
var commands = client.UseCommandsNext(new CommandsNextConfiguration
|
||||
{
|
||||
StringPrefixes = new[] {Config.CommandPrefix},
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
});
|
||||
commands.RegisterConverter(new CustomDiscordChannelConverter());
|
||||
commands.RegisterCommands<Misc>();
|
||||
commands.RegisterCommands<CompatList>();
|
||||
commands.RegisterCommands<Sudo>();
|
||||
commands.RegisterCommands<Antipiracy>();
|
||||
commands.RegisterCommands<Warnings>();
|
||||
commands.RegisterCommands<Explain>();
|
||||
commands.RegisterCommands<Psn>();
|
||||
|
||||
client.Ready += async r =>
|
||||
{
|
||||
Console.WriteLine("Bot is ready to serve!");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Bot user id : {r.Client.CurrentUser.Id} ({r.Client.CurrentUser.Username})");
|
||||
Console.WriteLine($"Bot admin id : {Config.BotAdminId} ({(await r.Client.GetUserAsync(Config.BotAdminId)).Username})");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Checking starbucks backlog...");
|
||||
await r.Client.CheckBacklog().ConfigureAwait(false);
|
||||
Console.WriteLine("Starbucks checked.");
|
||||
};
|
||||
client.MessageReactionAdded += Starbucks.Handler;
|
||||
|
||||
client.MessageCreated += AntipiracyMonitor.OnMessageCreated; // should be first
|
||||
client.MessageCreated += ProductCodeLookup.OnMessageMention;
|
||||
client.MessageCreated += LogInfoHandler.OnMessageCreated;
|
||||
client.MessageCreated += LogsAsTextMonitor.OnMessageCreated;
|
||||
|
||||
client.MessageUpdated += AntipiracyMonitor.OnMessageEdit;
|
||||
|
||||
client.MessageDeleted += ThumbnailCacheMonitor.OnMessageDeleted;
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(e.Message);
|
||||
Console.ResetColor();
|
||||
Console.WriteLine("Terminating.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 1 && ulong.TryParse(args[1], out var channelId))
|
||||
{
|
||||
Console.WriteLine("Found channelId: " + args[1]);
|
||||
var channel = await client.GetChannelAsync(channelId).ConfigureAwait(false);
|
||||
await channel.SendMessageAsync("Bot is up and running").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
while (!Config.Cts.IsCancellationRequested)
|
||||
{
|
||||
if (client.Ping > 1000)
|
||||
await client.ReconnectAsync();
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), Config.Cts.Token).ContinueWith(dt => {/* in case it was cancelled */}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
await psnScrappingTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using (var db = new BotDb())
|
||||
if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
|
||||
return;
|
||||
|
||||
using (var db = new ThumbnailDb())
|
||||
if (!await DbImporter.UpgradeAsync(db, Config.Cts.Token))
|
||||
return;
|
||||
|
||||
var psnScrappingTask = new PsnScraper().Run(Config.Cts.Token);
|
||||
|
||||
var config = new DiscordConfiguration
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
Token = Config.Token,
|
||||
TokenType = TokenType.Bot,
|
||||
UseInternalLogHandler = true,
|
||||
//LogLevel = LogLevel.Debug,
|
||||
};
|
||||
|
||||
using (var client = new DiscordClient(config))
|
||||
{
|
||||
var commands = client.UseCommandsNext(new CommandsNextConfiguration {StringPrefixes = new[] {Config.CommandPrefix}, Services = new ServiceCollection().BuildServiceProvider()});
|
||||
commands.RegisterConverter(new CustomDiscordChannelConverter());
|
||||
commands.RegisterCommands<Misc>();
|
||||
commands.RegisterCommands<CompatList>();
|
||||
commands.RegisterCommands<Sudo>();
|
||||
commands.RegisterCommands<Antipiracy>();
|
||||
commands.RegisterCommands<Warnings>();
|
||||
commands.RegisterCommands<Explain>();
|
||||
|
||||
client.Ready += async r =>
|
||||
{
|
||||
Console.WriteLine("Bot is ready to serve!");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Bot user id : {r.Client.CurrentUser.Id} ({r.Client.CurrentUser.Username})");
|
||||
Console.WriteLine($"Bot admin id : {Config.BotAdminId} ({(await r.Client.GetUserAsync(Config.BotAdminId)).Username})");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Checking starbucks backlog...");
|
||||
await r.Client.CheckBacklog().ConfigureAwait(false);
|
||||
Console.WriteLine("Starbucks checked.");
|
||||
};
|
||||
client.MessageReactionAdded += Starbucks.Handler;
|
||||
|
||||
client.MessageCreated += AntipiracyMonitor.OnMessageCreated; // should be first
|
||||
client.MessageCreated += ProductCodeLookup.OnMessageMention;
|
||||
client.MessageCreated += LogInfoHandler.OnMessageCreated;
|
||||
client.MessageCreated += LogsAsTextMonitor.OnMessageCreated;
|
||||
|
||||
client.MessageUpdated += AntipiracyMonitor.OnMessageEdit;
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(e.Message);
|
||||
Console.ResetColor();
|
||||
Console.WriteLine("Terminating.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 1 && ulong.TryParse(args[1], out var channelId))
|
||||
{
|
||||
Console.WriteLine("Found channelId: " + args[1]);
|
||||
var channel = await client.GetChannelAsync(channelId).ConfigureAwait(false);
|
||||
await channel.SendMessageAsync("Bot is up and running").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
while (!Config.Cts.IsCancellationRequested)
|
||||
{
|
||||
if (client.Ping > 1000)
|
||||
await client.ReconnectAsync();
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), Config.Cts.Token).ContinueWith(dt => {/* in case it was cancelled */}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
await psnScrappingTask.ConfigureAwait(false);
|
||||
Console.WriteLine("Exiting");
|
||||
finally
|
||||
{
|
||||
ShutdownCheck.Release();
|
||||
thread.Join(100);
|
||||
Console.WriteLine("Exiting");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ namespace CompatBot.ThumbScrapper
|
||||
{
|
||||
private static readonly PsnClient.Client Client = new PsnClient.Client();
|
||||
private static readonly Regex ContentIdMatcher = new Regex(@"(?<service_id>(?<service_letters>\w\w)(?<service_number>\d{4}))-(?<product_id>(?<product_letters>\w{4})(?<product_number>\d{5}))_(?<part>\d\d)-(?<label>\w{16})", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
|
||||
private static readonly SemaphoreSlim LockObj = new SemaphoreSlim(1, 1);
|
||||
private static List<string> PsnStores = new List<string>();
|
||||
private static DateTime StoreRefreshTimestamp = DateTime.MinValue;
|
||||
|
||||
public async Task Run(CancellationToken cancellationToken)
|
||||
{
|
||||
@ -26,7 +29,7 @@ namespace CompatBot.ThumbScrapper
|
||||
break;
|
||||
|
||||
await ScrapeStateProvider.CleanAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await RefreshStoresAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DoScrapePassAsync(cancellationToken).ConfigureAwait(false);
|
||||
@ -39,18 +42,59 @@ namespace CompatBot.ThumbScrapper
|
||||
} while (!cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
|
||||
private static async Task RefreshStoresAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ScrapeStateProvider.IsFresh(StoreRefreshTimestamp))
|
||||
return;
|
||||
|
||||
var knownLocales = await Client.GetLocales(cancellationToken).ConfigureAwait(false);
|
||||
var enabledLocales = knownLocales.EnabledLocales ?? new string[0];
|
||||
var result = GetLocalesInPreferredOrder(enabledLocales);
|
||||
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (ScrapeStateProvider.IsFresh(StoreRefreshTimestamp))
|
||||
return;
|
||||
|
||||
PsnStores = result;
|
||||
StoreRefreshTimestamp = DateTime.UtcNow;
|
||||
}
|
||||
finally
|
||||
{
|
||||
LockObj.Release();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PrintError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task DoScrapePassAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var knownLocales = await Client.GetLocales(cancellationToken).ConfigureAwait(false);
|
||||
var enabledLocales = knownLocales.EnabledLocales ?? new string[0];
|
||||
foreach (var locale in GetLocalesInPreferredOrder(enabledLocales))
|
||||
List<string> storesToScrape;
|
||||
await LockObj.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
storesToScrape = new List<string>(PsnStores);
|
||||
}
|
||||
finally
|
||||
{
|
||||
LockObj.Release();
|
||||
}
|
||||
|
||||
var percentPerStore = 1.0 / storesToScrape.Count;
|
||||
for (var storeIdx = 0; storeIdx < storesToScrape.Count; storeIdx++)
|
||||
{
|
||||
var locale = storesToScrape[storeIdx];
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (ScrapeStateProvider.IsFresh(locale))
|
||||
{
|
||||
Console.WriteLine($"Cache for {locale} PSN is fresh, skipping");
|
||||
//Console.WriteLine($"Cache for {locale} PSN is fresh, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -79,18 +123,22 @@ namespace CompatBot.ThumbScrapper
|
||||
};
|
||||
var take = 30;
|
||||
var returned = 0;
|
||||
foreach (var containerId in knownContainers)
|
||||
var containersToScrape = knownContainers.ToList(); //.Where(c => c.Contains("FULL", StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||
var percentPerContainer = 1.0 / containersToScrape.Count;
|
||||
for (var containerIdx = 0; containerIdx < containersToScrape.Count; containerIdx++)
|
||||
{
|
||||
var containerId = containersToScrape[containerIdx];
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (ScrapeStateProvider.IsFresh(locale, containerId))
|
||||
{
|
||||
Console.WriteLine($"\tCache for {locale} container {containerId} is fresh, skipping");
|
||||
//Console.WriteLine($"\tCache for {locale} container {containerId} is fresh, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine($"\tScraping {locale} container {containerId}...");
|
||||
var currentPercent = storeIdx * percentPerStore + containerIdx * percentPerStore * percentPerContainer;
|
||||
Console.WriteLine($"\tScraping {locale} container {containerId} ({currentPercent*100:##0.00}%)...");
|
||||
var total = -1;
|
||||
var start = 0;
|
||||
do
|
||||
@ -151,9 +199,9 @@ namespace CompatBot.ThumbScrapper
|
||||
}
|
||||
}
|
||||
start += take;
|
||||
} while ((returned > 0 || (total > -1 && start*take <= total)) && !cancellationToken.IsCancellationRequested);
|
||||
} while ((returned > 0 || (total > -1 && start * take <= total)) && !cancellationToken.IsCancellationRequested);
|
||||
await ScrapeStateProvider.SetLastRunTimestampAsync(locale, containerId).ConfigureAwait(false);
|
||||
Console.WriteLine($"\tFinished scraping {locale} container {containerId}, processed {start-take+returned} items");
|
||||
Console.WriteLine($"\tFinished scraping {locale} container {containerId}, processed {start - take + returned} items");
|
||||
}
|
||||
await ScrapeStateProvider.SetLastRunTimestampAsync(locale).ConfigureAwait(false);
|
||||
}
|
||||
@ -217,7 +265,7 @@ namespace CompatBot.ThumbScrapper
|
||||
if (string.IsNullOrEmpty(item.Id))
|
||||
continue;
|
||||
|
||||
await AddOrUpdateThumbnailAsync(item.Id, item.Attributes?.ThumbnailUrlBase, cancellationToken).ConfigureAwait(false);
|
||||
await AddOrUpdateThumbnailAsync(item.Id, item.Attributes?.Name, item.Attributes?.ThumbnailUrlBase, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case "legacy-sku":
|
||||
@ -241,7 +289,7 @@ namespace CompatBot.ThumbScrapper
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task AddOrUpdateThumbnailAsync(string contentId, string url, CancellationToken cancellationToken)
|
||||
private static async Task AddOrUpdateThumbnailAsync(string contentId, string name, string url, CancellationToken cancellationToken)
|
||||
{
|
||||
var match = ContentIdMatcher.Match(contentId);
|
||||
if (!match.Success)
|
||||
@ -251,6 +299,7 @@ namespace CompatBot.ThumbScrapper
|
||||
if (!ProductCodeLookup.ProductCode.IsMatch(productCode))
|
||||
return;
|
||||
|
||||
name = string.IsNullOrEmpty(name) ? null : name;
|
||||
using (var db = new ThumbnailDb())
|
||||
{
|
||||
var savedItem = db.Thumbnail.FirstOrDefault(t => t.ProductCode == productCode);
|
||||
@ -260,6 +309,7 @@ namespace CompatBot.ThumbScrapper
|
||||
{
|
||||
ProductCode = productCode,
|
||||
ContentId = contentId,
|
||||
Name = name,
|
||||
Url = url,
|
||||
Timestamp = DateTime.UtcNow.Ticks,
|
||||
};
|
||||
@ -267,10 +317,15 @@ namespace CompatBot.ThumbScrapper
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
if (!ScrapeStateProvider.IsFresh(savedItem.Timestamp) && savedItem.Url != url)
|
||||
if (!ScrapeStateProvider.IsFresh(savedItem.Timestamp))
|
||||
{
|
||||
savedItem.Url = url;
|
||||
savedItem.EmbeddableUrl = null;
|
||||
if (savedItem.Url != url)
|
||||
{
|
||||
savedItem.Url = url;
|
||||
savedItem.EmbeddableUrl = null;
|
||||
}
|
||||
if (name != null && savedItem.Name != name)
|
||||
savedItem.Name = name;
|
||||
}
|
||||
savedItem.ContentId = contentId;
|
||||
savedItem.Timestamp = DateTime.UtcNow.Ticks;
|
||||
|
@ -22,6 +22,16 @@ namespace CompatBot.Utils
|
||||
return ctx.Client.GetUserNameAsync(ctx.Channel, userId, forDmPurposes, defaultName);
|
||||
}
|
||||
|
||||
public static DiscordMember GetMember(this DiscordClient client, DiscordGuild guild, DiscordUser user)
|
||||
{
|
||||
return (from g in client.Guilds
|
||||
where g.Key == guild.Id
|
||||
from u in g.Value.Members
|
||||
where u.Id == user.Id
|
||||
select u
|
||||
).FirstOrDefault();
|
||||
}
|
||||
|
||||
public static async Task<string> GetUserNameAsync(this DiscordClient client, DiscordChannel channel, ulong userId, bool? forDmPurposes = null, string defaultName = "Unknown user")
|
||||
{
|
||||
var isPrivate = forDmPurposes ?? channel.IsPrivate;
|
||||
@ -38,6 +48,20 @@ namespace CompatBot.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ReactWithAsync(this DiscordMessage message, DiscordClient client, DiscordEmoji emoji, string fallbackMessage = null, bool showBoth = false)
|
||||
{
|
||||
var canReact = message.Channel.PermissionsFor(client.GetMember(message.Channel.Guild, client.CurrentUser)).HasPermission(Permissions.AddReactions);
|
||||
if (canReact)
|
||||
await message.CreateReactionAsync(emoji).ConfigureAwait(false);
|
||||
if ((!canReact || showBoth) && !string.IsNullOrEmpty(fallbackMessage))
|
||||
await message.Channel.SendMessageAsync(fallbackMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static Task ReactWithAsync(this CommandContext ctx, DiscordEmoji emoji, string fallbackMessage = null, bool showBoth = false)
|
||||
{
|
||||
return ReactWithAsync(ctx.Message, ctx.Client, emoji, fallbackMessage, showBoth);
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyCollection<DiscordMessage>> GetMessagesBeforeAsync(this DiscordChannel channel, ulong beforeMessageId, int limit = 100, DateTime? timeLimit = null)
|
||||
{
|
||||
if (timeLimit > DateTime.UtcNow)
|
||||
|
@ -196,21 +196,28 @@ namespace CompatBot.Utils.ResultFormatters
|
||||
private static async Task BuildNotesSectionAsync(DiscordEmbedBuilder builder, LogParseState state, NameValueCollection items)
|
||||
{
|
||||
if (items["rap_file"] is string rap)
|
||||
builder.AddField("Missing License", $"Missing `{Path.GetFileName(rap)}`");
|
||||
{
|
||||
var licenseNames = rap.Split(Environment.NewLine).Distinct().Select(p => $"`{Path.GetFileName(p)}`");
|
||||
builder.AddField("Missing Licenses", string.Join(Environment.NewLine, licenseNames));
|
||||
}
|
||||
else if (items["fatal_error"] is string fatalError)
|
||||
builder.AddField("Fatal Error", $"`{fatalError}`");
|
||||
string notes = null;
|
||||
|
||||
var notes = new StringBuilder();
|
||||
if (string.IsNullOrEmpty(items["ppu_decoder"]) || string.IsNullOrEmpty(items["renderer"]))
|
||||
notes += "Log is empty. You need to run the game before uploading the log.";
|
||||
notes.AppendLine("Log is empty. You need to run the game before uploading the log.");
|
||||
if (!string.IsNullOrEmpty(items["hdd_game_path"]) && (items["serial"]?.StartsWith("BL", StringComparison.InvariantCultureIgnoreCase) ?? false))
|
||||
notes.AppendLine($"Disc game inside `{items["hdd_game_path"]}`");
|
||||
if (state.Error == LogParseState.ErrorCode.SizeLimit)
|
||||
notes += "Log was too large, showing last processed run";
|
||||
notes.AppendLine("Log was too large, showing last processed run");
|
||||
|
||||
// should be last check here
|
||||
var updateInfo = await CheckForUpdateAsync(items).ConfigureAwait(false);
|
||||
if (updateInfo != null)
|
||||
notes += $"{Environment.NewLine}Outdated RPCS3 build detected, consider updating";
|
||||
if (notes != null)
|
||||
builder.AddField("Notes", notes);
|
||||
notes.AppendLine("Outdated RPCS3 build detected, consider updating");
|
||||
var notesContent = notes.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(notesContent))
|
||||
builder.AddField("Notes", notesContent);
|
||||
|
||||
if (updateInfo != null)
|
||||
await updateInfo.AsEmbedAsync(builder).ConfigureAwait(false);
|
||||
|
@ -4,7 +4,9 @@ using System.Globalization;
|
||||
using CompatApiClient;
|
||||
using CompatApiClient.Utils;
|
||||
using CompatApiClient.POCOs;
|
||||
using CompatBot.Database.Providers;
|
||||
using DSharpPlus.Entities;
|
||||
using Microsoft.EntityFrameworkCore.Sqlite.Query.ExpressionTranslators.Internal;
|
||||
|
||||
namespace CompatBot.Utils.ResultFormatters
|
||||
{
|
||||
@ -75,7 +77,15 @@ namespace CompatBot.Utils.ResultFormatters
|
||||
var desc = string.IsNullOrEmpty(titleId)
|
||||
? "No product id was found; log might be corrupted, please reupload a new copy"
|
||||
: $"Product code {titleId} was not found in compatibility database, possibly untested!";
|
||||
return new DiscordEmbedBuilder{Description = desc, Color = Config.Colors.CompatStatusUnknown};
|
||||
var result = new DiscordEmbedBuilder
|
||||
{
|
||||
Description = desc,
|
||||
Color = Config.Colors.CompatStatusUnknown,
|
||||
ThumbnailUrl = thumbnailUrl,
|
||||
};
|
||||
if (ThumbnailProvider.GetTitleName(titleId) is string titleName && !string.IsNullOrEmpty(titleName))
|
||||
result.Title = $"[{titleId}] {titleName.Sanitize().Trim(200)}";
|
||||
return result.Build();
|
||||
}
|
||||
|
||||
}
|
||||
|
55
CompatBot/Utils/ResultFormatters/TitlePatchFormatter.cs
Normal file
55
CompatBot/Utils/ResultFormatters/TitlePatchFormatter.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CompatBot.Database.Providers;
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Entities;
|
||||
using PsnClient.POCOs;
|
||||
|
||||
namespace CompatBot.Utils.ResultFormatters
|
||||
{
|
||||
internal static class TitlePatchFormatter
|
||||
{
|
||||
private const long UnderKB = 1000;
|
||||
private const long UnderMB = 1000 * 1024;
|
||||
private const long UnderGB = 1000 * 1024 * 1024;
|
||||
|
||||
|
||||
public static async Task<DiscordEmbed> AsEmbedAsync(this TitlePatch patch, DiscordClient client)
|
||||
{
|
||||
var pkgs = patch.Tag?.Packages;
|
||||
var title = pkgs?.Select(p => p.ParamSfo?.Title).LastOrDefault(t => !string.IsNullOrEmpty(t)) ?? patch.TitleId;
|
||||
var thumbnailUrl = await client.GetThumbnailUrlAsync(patch.TitleId).ConfigureAwait(false);
|
||||
var result = new DiscordEmbedBuilder
|
||||
{
|
||||
Title = title,
|
||||
Color = Config.Colors.DownloadLinks,
|
||||
ThumbnailUrl = thumbnailUrl,
|
||||
};
|
||||
if (pkgs.Length > 1)
|
||||
{
|
||||
result.Description = $"Total download size of all packages is {pkgs.Sum(p => p.Size).AsStorageUnit()}";
|
||||
foreach (var pkg in pkgs)
|
||||
result.AddField($"Update v{pkg.Version} ({pkg.Size.AsStorageUnit()})", $"⏬ [{Path.GetFileName(pkg.Url)}]({pkg.Url})");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Title = $"{title} update v{pkgs[0].Version} ({pkgs[0].Size.AsStorageUnit()})";
|
||||
result.Description = $"⏬ [{Path.GetFileName(pkgs[0].Url)}]({pkgs[0].Url})";
|
||||
}
|
||||
return result.Build();
|
||||
}
|
||||
|
||||
private static string AsStorageUnit(this long bytes)
|
||||
{
|
||||
if (bytes < UnderKB)
|
||||
return $"{bytes} byte{(bytes % 10 == 1 && bytes % 100 != 11 ? "" : "s")}";
|
||||
if (bytes < UnderMB)
|
||||
return $"{bytes / 1024.0:0.##} KB";
|
||||
if (bytes < UnderGB)
|
||||
return $"{bytes / 1024.0 / 1024:0.##} MB";
|
||||
return $"{bytes / 1024.0 / 1024 / 1024:0.##} GB";
|
||||
}
|
||||
}
|
||||
}
|
27
PsnClient/CustomTlsCertificatesHandler.cs
Normal file
27
PsnClient/CustomTlsCertificatesHandler.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace PsnClient
|
||||
{
|
||||
public class CustomTlsCertificatesHandler: HttpClientHandler
|
||||
{
|
||||
private readonly Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> defaultCertHandler;
|
||||
|
||||
public CustomTlsCertificatesHandler()
|
||||
{
|
||||
defaultCertHandler = ServerCertificateCustomValidationCallback;
|
||||
ServerCertificateCustomValidationCallback = IgnoreSonyRootCertificates;
|
||||
}
|
||||
|
||||
private bool IgnoreSonyRootCertificates(HttpRequestMessage requestMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors policyErrors)
|
||||
{
|
||||
//todo: do proper checks with root certs from ps3 fw
|
||||
if (certificate.IssuerName.Name?.StartsWith("SCEI DNAS Root 0") ?? false)
|
||||
return true;
|
||||
|
||||
return defaultCertHandler?.Invoke(requestMessage, certificate, chain, policyErrors) ?? true;
|
||||
}
|
||||
}
|
||||
}
|
46
PsnClient/POCOs/TitlePatch.cs
Normal file
46
PsnClient/POCOs/TitlePatch.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace PsnClient.POCOs
|
||||
{
|
||||
[XmlRoot("titlepatch")]
|
||||
public class TitlePatch
|
||||
{
|
||||
[XmlAttribute("titleid")]
|
||||
public string TitleId { get; set; }
|
||||
[XmlAttribute("status")]
|
||||
public string Status { get; set; }
|
||||
[XmlElement("tag")]
|
||||
public TitlePatchTag Tag { get; set; }
|
||||
}
|
||||
|
||||
public class TitlePatchTag
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
//no root element
|
||||
[XmlElement("package")]
|
||||
public TitlePatchPackage[] Packages { get; set; }
|
||||
}
|
||||
|
||||
public class TitlePatchPackage
|
||||
{
|
||||
[XmlAttribute("version")]
|
||||
public string Version { get; set; }
|
||||
[XmlAttribute("size")]
|
||||
public long Size { get; set; }
|
||||
[XmlAttribute("sha1sum")]
|
||||
public string Sha1Sum { get; set; }
|
||||
[XmlAttribute("url")]
|
||||
public string Url { get; set; }
|
||||
[XmlAttribute("ps3_system_ver")]
|
||||
public string Ps3SystemVer { get; set; }
|
||||
[XmlElement("paramsfo")]
|
||||
public TitlePatchParamSfo ParamSfo { get; set; }
|
||||
}
|
||||
|
||||
public class TitlePatchParamSfo
|
||||
{
|
||||
[XmlElement("TITLE")]
|
||||
public string Title { get; set; }
|
||||
}
|
||||
}
|
@ -22,11 +22,13 @@ namespace PsnClient
|
||||
private readonly HttpClient client;
|
||||
private readonly MediaTypeFormatterCollection dashedFormatters;
|
||||
private readonly MediaTypeFormatterCollection underscoreFormatters;
|
||||
private readonly MediaTypeFormatterCollection xmlFormatters;
|
||||
private static readonly Regex ContainerIdLink = new Regex(@"(?<id>STORE-(\w|\d)+-(\w|\d)+)");
|
||||
|
||||
public Client()
|
||||
{
|
||||
client = HttpClientFactory.Create(new CompressionMessageHandler());
|
||||
|
||||
client = HttpClientFactory.Create(new CustomTlsCertificatesHandler(), new CompressionMessageHandler());
|
||||
var dashedSettings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new JsonContractResolver(NamingStyles.Dashed),
|
||||
@ -40,15 +42,15 @@ namespace PsnClient
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
underscoreFormatters = new MediaTypeFormatterCollection(new[] { new JsonMediaTypeFormatter { SerializerSettings = underscoreSettings } });
|
||||
xmlFormatters = new MediaTypeFormatterCollection(new[] {new XmlMediaTypeFormatter {UseXmlSerializer = true}});
|
||||
}
|
||||
|
||||
public async Task<AppLocales> GetLocales(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, "https://transact.playstation.com/assets/app.json"))
|
||||
using (response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
using (var response = await client.SendAsync(message, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
@ -76,8 +78,7 @@ namespace PsnClient
|
||||
using (var getMessage = new HttpRequestMessage(HttpMethod.Get, "https://store.playstation.com/kamaji/api/valkyrie_storefront/00_09_000/user/stores"))
|
||||
{
|
||||
getMessage.Headers.Add("Cookie", cookieHeaderValue);
|
||||
HttpResponseMessage response;
|
||||
using (response = await client.SendAsync(getMessage, cancellationToken).ConfigureAwait(false))
|
||||
using (var response = await client.SendAsync(getMessage, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
@ -157,9 +158,8 @@ namespace PsnClient
|
||||
{
|
||||
var loc = locale.AsLocaleData();
|
||||
var baseUrl = $"https://store.playstation.com/valkyrie-api/{loc.language}/{loc.country}/999/storefront/{containerId}";
|
||||
HttpResponseMessage response;
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, baseUrl))
|
||||
using (response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
@ -192,9 +192,8 @@ namespace PsnClient
|
||||
filters["size"] = take.ToString();
|
||||
filters["bucket"] = "games";
|
||||
url = url.SetQueryParameters(filters);
|
||||
HttpResponseMessage response;
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, url))
|
||||
using (response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
@ -221,9 +220,8 @@ namespace PsnClient
|
||||
try
|
||||
{
|
||||
var loc = locale.AsLocaleData();
|
||||
HttpResponseMessage response;
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, $"https://store.playstation.com/valkyrie-api/{loc.language}/{loc.country}/999/resolve/{contentId}?depth={depth}"))
|
||||
using (response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
@ -245,6 +243,33 @@ namespace PsnClient
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TitlePatch> GetTitleUpdatesAsync(string productId, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var message = new HttpRequestMessage(HttpMethod.Get, $"https://a0.ww.np.dl.playstation.net/tpl/np/{productId}/{productId}-ver.xml"))
|
||||
using (var response = await client.SendAsync(message, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
|
||||
return await response.Content.ReadAsAsync<TitlePatch>(xmlFormatters, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ConsoleLogger.PrintError(e, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetSessionCookies(string locale, CancellationToken cancellationToken)
|
||||
{
|
||||
var loc = locale.AsLocaleData();
|
||||
|
Loading…
Reference in New Issue
Block a user