using System; using System.Threading.Tasks; using App.Metrics; using Autofac; using NodaTime; using Myriad.Cache; using Myriad.Extensions; using Myriad.Gateway; using Myriad.Rest; using Myriad.Rest.Types; using Myriad.Rest.Types.Requests; using Myriad.Types; using PluralKit.Core; namespace PluralKit.Bot; public class Context { private readonly ILifetimeScope _provider; private readonly IMetrics _metrics; private readonly CommandMessageService _commandMessageService; private Command? _currentCommand; public Context(ILifetimeScope provider, Shard shard, Guild? guild, Channel channel, MessageCreateEvent message, int commandParseOffset, PKSystem senderSystem, SystemConfig config, MessageContext messageContext) { Message = (Message)message; Shard = shard; Guild = guild; Channel = channel; System = senderSystem; Config = config; MessageContext = messageContext; Cache = provider.Resolve(); Database = provider.Resolve(); Repository = provider.Resolve(); _metrics = provider.Resolve(); _provider = provider; _commandMessageService = provider.Resolve(); Parameters = new Parameters(message.Content?.Substring(commandParseOffset)); Rest = provider.Resolve(); Cluster = provider.Resolve(); } public readonly IDiscordCache Cache; public readonly DiscordApiClient Rest; public readonly Channel Channel; public User Author => Message.Author; public GuildMemberPartial Member => ((MessageCreateEvent)Message).Member; public readonly Message Message; public readonly Guild Guild; public readonly Shard Shard; public readonly Cluster Cluster; public readonly MessageContext MessageContext; public Task BotPermissions => Cache.PermissionsIn(Channel.Id); public Task UserPermissions => Cache.PermissionsFor((MessageCreateEvent)Message); public readonly PKSystem System; public readonly SystemConfig Config; public DateTimeZone Zone => Config?.Zone ?? DateTimeZone.Utc; public readonly Parameters Parameters; internal readonly IDatabase Database; internal readonly ModelRepository Repository; public async Task Reply(string text = null, Embed embed = null, AllowedMentions? mentions = null) { var botPerms = await BotPermissions; if (!botPerms.HasFlag(PermissionSet.SendMessages)) // Will be "swallowed" during the error handler anyway, this message is never shown. throw new PKError("PluralKit does not have permission to send messages in this channel."); if (embed != null && !botPerms.HasFlag(PermissionSet.EmbedLinks)) throw new PKError("PluralKit does not have permission to send embeds in this channel. Please ensure I have the **Embed Links** permission enabled."); var msg = await Rest.CreateMessage(Channel.Id, new MessageRequest { Content = text, Embed = embed, // Default to an empty allowed mentions object instead of null (which means no mentions allowed) AllowedMentions = mentions ?? new AllowedMentions() }); if (embed != null) { // Sensitive information that might want to be deleted by :x: reaction is typically in an embed format (member cards, for example) // This may need to be changed at some point but works well enough for now await _commandMessageService.RegisterMessage(msg.Id, msg.ChannelId, Author.Id); } return msg; } public async Task Execute(Command? commandDef, Func handler, bool deprecated = false) { _currentCommand = commandDef; try { using (_metrics.Measure.Timer.Time(BotMetrics.CommandTime, new MetricTags("Command", commandDef?.Key ?? "null"))) await handler(_provider.Resolve()); _metrics.Measure.Meter.Mark(BotMetrics.CommandsRun); } catch (PKSyntaxError e) { await Reply($"{Emojis.Error} {e.Message}\n**Command usage:**\n> pk;{commandDef?.Usage}"); } catch (PKError e) { await Reply($"{Emojis.Error} {e.Message}"); } catch (TimeoutException) { // Got a complaint the old error was a bit too patronizing. Hopefully this is better? await Reply($"{Emojis.Error} Operation timed out, sorry. Try again, perhaps?"); } if (deprecated && commandDef != null) await Reply($"{Emojis.Warn} This command is deprecated and will be removed soon. In the future, please use `pk;{commandDef.Key}`."); } /// /// Same as LookupContextFor, but skips flags / config checks. /// public LookupContext DirectLookupContextFor(SystemId systemId) => System?.Id == systemId ? LookupContext.ByOwner : LookupContext.ByNonOwner; public LookupContext LookupContextFor(SystemId systemId) { var hasPrivateOverride = this.MatchFlag("private", "priv"); var hasPublicOverride = this.MatchFlag("public", "pub"); if (hasPrivateOverride && hasPublicOverride) throw new PKError("Cannot match both public and private flags at the same time."); if (System?.Id != systemId) { if (hasPrivateOverride) throw Errors.NotOwnInfo; return LookupContext.ByNonOwner; } if (hasPrivateOverride) return LookupContext.ByOwner; if (hasPublicOverride) return LookupContext.ByNonOwner; return Config.ShowPrivateInfo ? LookupContext.ByOwner : LookupContext.ByNonOwner; } public IComponentContext Services => _provider; }