mirror of
https://github.com/TeamOctolings/Octobot.git
synced 2025-04-19 16:33:36 +03:00
Remora.Discord part 2 out of ∞
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
This commit is contained in:
parent
2e8392f5d7
commit
d0ecfc7928
8 changed files with 946 additions and 1158 deletions
35
Boyfriend.cs
35
Boyfriend.cs
|
@ -1,13 +1,16 @@
|
|||
using System.Reflection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remora.Discord.API.Abstractions.Gateway.Commands;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.API.Objects;
|
||||
using Remora.Discord.Caching.Extensions;
|
||||
using Remora.Discord.Caching.Services;
|
||||
using Remora.Discord.Gateway;
|
||||
using Remora.Discord.Gateway.Extensions;
|
||||
using Remora.Discord.Hosting.Extensions;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Boyfriend;
|
||||
|
||||
|
@ -15,7 +18,8 @@ public class Boyfriend {
|
|||
public static ILogger<Boyfriend> Logger = null!;
|
||||
public static IConfiguration GuildConfiguration = null!;
|
||||
|
||||
private static readonly Dictionary<string, string> ReflectionMessageCache = new();
|
||||
public static readonly AllowedMentions NoMentions = new(
|
||||
Array.Empty<MentionType>(), Array.Empty<Snowflake>(), Array.Empty<Snowflake>());
|
||||
|
||||
public static async Task Main(string[] args) {
|
||||
var host = CreateHostBuilder(args).UseConsoleLifetime().Build();
|
||||
|
@ -48,9 +52,17 @@ public class Boyfriend {
|
|||
|
||||
services.AddDiscordCaching();
|
||||
services.Configure<CacheSettings>(
|
||||
settings => { settings.SetAbsoluteExpiration<IMessage>(TimeSpan.FromDays(7)); });
|
||||
settings => {
|
||||
settings.SetDefaultAbsoluteExpiration(TimeSpan.FromHours(1));
|
||||
settings.SetDefaultSlidingExpiration(TimeSpan.FromMinutes(30));
|
||||
settings.SetAbsoluteExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||
settings.SetSlidingExpiration<IMessage>(TimeSpan.FromDays(7));
|
||||
});
|
||||
|
||||
services.AddSingleton<IConfigurationBuilder, ConfigurationBuilder>();
|
||||
services.AddTransient<IConfigurationBuilder, ConfigurationBuilder>();
|
||||
|
||||
services.Configure<DiscordGatewayClientOptions>(
|
||||
options => options.Intents |= GatewayIntents.MessageContents);
|
||||
}
|
||||
).ConfigureLogging(
|
||||
c => c.AddConsole()
|
||||
|
@ -60,19 +72,6 @@ public class Boyfriend {
|
|||
}
|
||||
|
||||
public static string GetLocalized(string key) {
|
||||
var propertyName = key;
|
||||
key = $"{Messages.Culture}/{key}";
|
||||
if (ReflectionMessageCache.TryGetValue(key, out var cached)) return cached;
|
||||
|
||||
var toReturn =
|
||||
typeof(Messages).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null)
|
||||
?.ToString();
|
||||
if (toReturn is null) {
|
||||
Logger.LogError("Could not find localized property: {Name}", propertyName);
|
||||
return key;
|
||||
}
|
||||
|
||||
ReflectionMessageCache.Add(key, toReturn);
|
||||
return toReturn;
|
||||
return Messages.ResourceManager.GetString(key, Messages.Culture) ?? key;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,19 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Compile Remove="old\**"/>
|
||||
<Compile Update="Messages.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Messages.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="old\**"/>
|
||||
<EmbeddedResource Update="Messages.resx.bak">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Messages.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,31 +1,121 @@
|
|||
using System.Drawing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.API.Abstractions.Rest;
|
||||
using Remora.Discord.Caching;
|
||||
using Remora.Discord.Caching.Services;
|
||||
using Remora.Discord.Extensions.Embeds;
|
||||
using Remora.Discord.Extensions.Formatting;
|
||||
using Remora.Discord.Gateway.Responders;
|
||||
using Remora.Results;
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
|
||||
namespace Boyfriend;
|
||||
|
||||
public class ReadyResponder : IResponder<IGuildCreate> {
|
||||
public class GuildCreateResponder : IResponder<IGuildCreate> {
|
||||
private readonly IDiscordRestChannelAPI _channelApi;
|
||||
private readonly IDiscordRestUserAPI _userApi;
|
||||
|
||||
public ReadyResponder(IDiscordRestChannelAPI channelApi) {
|
||||
public GuildCreateResponder(IDiscordRestChannelAPI channelApi, IDiscordRestUserAPI userApi) {
|
||||
_channelApi = channelApi;
|
||||
_userApi = userApi;
|
||||
}
|
||||
|
||||
public async Task<Result> RespondAsync(IGuildCreate gatewayEvent, CancellationToken ct = default) {
|
||||
if (!gatewayEvent.Guild.IsT0) return Result.FromSuccess(); // is IAvailableGuild
|
||||
|
||||
var guild = gatewayEvent.Guild.AsT0;
|
||||
if (guild.GetConfigBool("SendReadyMessages").IsDefined(out var enabled)
|
||||
&& enabled
|
||||
&& guild.GetChannel("PrivateFeedbackChannel").IsDefined(out var channel)) {
|
||||
Boyfriend.Logger.LogInformation("Joined guild \"{Name}\"", guild.Name);
|
||||
|
||||
var channelResult = guild.ID.GetChannel("PrivateFeedbackChannel");
|
||||
if (!channelResult.IsDefined(out var channel)) return Result.FromSuccess();
|
||||
|
||||
var currentUserResult = await _userApi.GetCurrentUserAsync(ct);
|
||||
if (!currentUserResult.IsDefined(out var currentUser)) return Result.FromError(currentUserResult);
|
||||
|
||||
if (guild.GetConfigBool("ReceiveStartupMessages").IsDefined(out var shouldSendStartupMessage)
|
||||
&& shouldSendStartupMessage) {
|
||||
Messages.Culture = guild.GetCulture();
|
||||
var i = Random.Shared.Next(1, 4);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(Boyfriend.GetLocalized($"Beep{i}"))
|
||||
.WithDescription(Messages.Ready)
|
||||
.WithUserFooter(currentUser)
|
||||
.WithCurrentTimestamp()
|
||||
.WithColour(Color.Aqua)
|
||||
.Build();
|
||||
|
||||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
channel.ID, string.Format(Messages.Ready, Boyfriend.GetLocalized($"Beep{i}")), ct: ct);
|
||||
channel, embeds: new[] { built }!, ct: ct);
|
||||
}
|
||||
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageDeletedResponder : IResponder<IMessageDelete> {
|
||||
private readonly IDiscordRestAuditLogAPI _auditLogApi;
|
||||
private readonly CacheService _cacheService;
|
||||
private readonly IDiscordRestChannelAPI _channelApi;
|
||||
private readonly IDiscordRestUserAPI _userApi;
|
||||
|
||||
public MessageDeletedResponder(
|
||||
IDiscordRestChannelAPI channelApi, IDiscordRestUserAPI userApi, CacheService cacheService,
|
||||
IDiscordRestAuditLogAPI auditLogApi) {
|
||||
_channelApi = channelApi;
|
||||
_userApi = userApi;
|
||||
_cacheService = cacheService;
|
||||
_auditLogApi = auditLogApi;
|
||||
}
|
||||
|
||||
public async Task<Result> RespondAsync(IMessageDelete gatewayEvent, CancellationToken ct = default) {
|
||||
if (!gatewayEvent.GuildID.IsDefined(out var guildId)) return Result.FromSuccess();
|
||||
|
||||
var channelResult = guildId.GetChannel("PrivateFeedbackChannel");
|
||||
if (!channelResult.IsDefined(out var channel)) return Result.FromSuccess();
|
||||
|
||||
var messageResult = await _cacheService.TryGetValueAsync<IMessage>(
|
||||
new KeyHelpers.MessageCacheKey(gatewayEvent.ChannelID, gatewayEvent.ID), ct);
|
||||
if (messageResult.IsDefined(out var message)) {
|
||||
var auditLogResult = await _auditLogApi.GetGuildAuditLogAsync(
|
||||
guildId, actionType: AuditLogEvent.MessageDelete, limit: 1, ct: ct);
|
||||
if (!auditLogResult.IsDefined(out var auditLogPage)) return Result.FromError(auditLogResult);
|
||||
|
||||
var auditLog = auditLogPage.AuditLogEntries.Single();
|
||||
if (!auditLog.Options.IsDefined(out var options))
|
||||
return Result.FromError(new ArgumentNullError(nameof(auditLog.Options)));
|
||||
|
||||
var user = message.Author;
|
||||
if (options.ChannelID == gatewayEvent.ChannelID
|
||||
&& DateTimeOffset.UtcNow.Subtract(auditLog.ID.Timestamp).TotalSeconds <= 2) {
|
||||
var userResult = await _userApi.GetUserAsync(auditLog.UserID!.Value, ct);
|
||||
if (!userResult.IsDefined(out user)) return Result.FromError(userResult);
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(string.Format(Messages.CachedMessageDeleted, message.Author))
|
||||
.WithTitle(
|
||||
message.Author,
|
||||
string.Format(
|
||||
Messages.CachedMessageDeleted,
|
||||
$"{message.Author.Username}#{message.Author.Discriminator:0000}"))
|
||||
.WithDescription(Markdown.BlockCode(message.Content.SanitizeForBlockCode()))
|
||||
.WithActionFooter(user)
|
||||
.WithTimestamp(message.Timestamp)
|
||||
.WithColour(Color.Crimson)
|
||||
.Build();
|
||||
|
||||
if (!embed.IsDefined(out var built)) return Result.FromError(embed);
|
||||
|
||||
return (Result)await _channelApi.CreateMessageAsync(
|
||||
channel, embeds: new[] { built }, allowedMentions: Boyfriend.NoMentions, ct: ct);
|
||||
}
|
||||
|
||||
return (Result)messageResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
using System.Globalization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||
using Remora.Discord.API;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.API.Objects;
|
||||
using Remora.Discord.Extensions.Embeds;
|
||||
using Remora.Rest.Core;
|
||||
using Remora.Results;
|
||||
|
||||
namespace Boyfriend;
|
||||
|
@ -18,18 +21,49 @@ public static class Extensions {
|
|||
return value is not null ? Result<bool>.FromSuccess(value.Value) : Result<bool>.FromError(new NotFoundError());
|
||||
}
|
||||
|
||||
public static Result<IChannel> GetChannel(this IGuildCreate.IAvailableGuild guild, string key) {
|
||||
var value = Boyfriend.GuildConfiguration.GetValue<ulong?>($"GuildConfigs:{guild.ID}:{key}");
|
||||
if (value is null) return Result<IChannel>.FromError(new NotFoundError());
|
||||
|
||||
var match = guild.Channels.SingleOrDefault(channel => channel!.ID.Equals(value.Value), null);
|
||||
return match is not null
|
||||
? Result<IChannel>.FromSuccess(match)
|
||||
: Result<IChannel>.FromError(new NotFoundError());
|
||||
public static Result<Snowflake> GetChannel(this Snowflake guildId, string key) {
|
||||
var value = Boyfriend.GuildConfiguration.GetValue<ulong?>($"GuildConfigs:{guildId}:{key}");
|
||||
return value is not null
|
||||
? Result<Snowflake>.FromSuccess(DiscordSnowflake.New(value.Value))
|
||||
: Result<Snowflake>.FromError(new NotFoundError());
|
||||
}
|
||||
|
||||
public static CultureInfo GetCulture(this IGuild guild) {
|
||||
var value = Boyfriend.GuildConfiguration.GetValue<string?>($"GuildConfigs:{guild.ID}:Language");
|
||||
return value is not null ? CultureInfoCache[value] : CultureInfoCache["en"];
|
||||
}
|
||||
|
||||
public static EmbedBuilder WithUserFooter(this EmbedBuilder builder, IUser user) {
|
||||
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
||||
var avatarUrl = avatarUrlResult.IsSuccess
|
||||
? avatarUrlResult.Entity.AbsoluteUri
|
||||
: CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
|
||||
|
||||
return builder.WithFooter(new EmbedFooter($"{user.Username}#{user.Discriminator:0000}", avatarUrl));
|
||||
}
|
||||
|
||||
public static EmbedBuilder WithActionFooter(this EmbedBuilder builder, IUser user) {
|
||||
var avatarUrlResult = CDN.GetUserAvatarUrl(user, imageSize: 256);
|
||||
var avatarUrl = avatarUrlResult.IsSuccess
|
||||
? avatarUrlResult.Entity.AbsoluteUri
|
||||
: CDN.GetDefaultUserAvatarUrl(user, imageSize: 256).Entity.AbsoluteUri;
|
||||
|
||||
return builder.WithFooter(
|
||||
new EmbedFooter($"{Messages.IssuedBy}:\n{user.Username}#{user.Discriminator:0000}", avatarUrl));
|
||||
}
|
||||
|
||||
public static EmbedBuilder WithTitle(this EmbedBuilder builder, IUser avatarSource, string text) {
|
||||
var avatarUrlResult = CDN.GetUserAvatarUrl(avatarSource, imageSize: 256);
|
||||
|
||||
var avatarUrl = avatarUrlResult.IsSuccess
|
||||
? avatarUrlResult.Entity
|
||||
: CDN.GetDefaultUserAvatarUrl(avatarSource, imageSize: 256).Entity;
|
||||
|
||||
builder.Author = new EmbedAuthorBuilder(text, iconUrl: avatarUrl.AbsoluteUri);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static string SanitizeForBlockCode(this string s) {
|
||||
return s.Replace("```", "```");
|
||||
}
|
||||
}
|
||||
|
|
1691
Messages.Designer.cs
generated
1691
Messages.Designer.cs
generated
File diff suppressed because it is too large
Load diff
|
@ -98,49 +98,53 @@
|
|||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Ready" xml:space="preserve">
|
||||
<value>{0}I'm ready!</value>
|
||||
</data>
|
||||
<data name="CachedMessageDeleted" xml:space="preserve">
|
||||
<value>Deleted message from {0} in channel {1}: {2}</value>
|
||||
</data>
|
||||
<data name="CachedMessageCleared" xml:space="preserve">
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="Ready" xml:space="preserve">
|
||||
<value>I'm ready!</value>
|
||||
</data>
|
||||
<data name="CachedMessageDeleted" xml:space="preserve">
|
||||
<value>Deleted message by {0}:</value>
|
||||
</data>
|
||||
<data name="CachedMessageCleared" xml:space="preserve">
|
||||
<value>Cleared message from {0} in channel {1}: {2}</value>
|
||||
</data>
|
||||
<data name="CachedMessageEdited" xml:space="preserve">
|
||||
<data name="CachedMessageEdited" xml:space="preserve">
|
||||
<value>Edited message in channel {0}: {1} -> {2}</value>
|
||||
</data>
|
||||
<data name="DefaultWelcomeMessage" xml:space="preserve">
|
||||
<data name="DefaultWelcomeMessage" xml:space="preserve">
|
||||
<value>{0}, welcome to {1}</value>
|
||||
</data>
|
||||
<data name="Beep1" xml:space="preserve">
|
||||
<value>Bah! </value>
|
||||
</data>
|
||||
<data name="Beep2" xml:space="preserve">
|
||||
<value>Bop! </value>
|
||||
</data>
|
||||
<data name="Beep1" xml:space="preserve">
|
||||
<value>Bah!</value>
|
||||
</data>
|
||||
<data name="Beep2" xml:space="preserve">
|
||||
<value>Bop!</value>
|
||||
</data>
|
||||
<data name="Beep3" xml:space="preserve">
|
||||
<value>Beep! </value>
|
||||
</data>
|
||||
<value>Beep!</value>
|
||||
</data>
|
||||
<data name="CommandNoPermissionBot" xml:space="preserve">
|
||||
<value>I do not have permission to execute this command!</value>
|
||||
</data>
|
||||
|
@ -474,4 +478,7 @@
|
|||
<data name="InvalidRemindIn" xml:space="preserve">
|
||||
<value>You need to specify when I should send you the reminder!</value>
|
||||
</data>
|
||||
<data name="IssuedBy" xml:space="preserve">
|
||||
<value>Issued by</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -98,49 +98,53 @@
|
|||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Ready" xml:space="preserve">
|
||||
<value>{0}Я запустился!</value>
|
||||
</data>
|
||||
<data name="CachedMessageDeleted" xml:space="preserve">
|
||||
<value>Удалено сообщение от {0} в канале {1}: {2}</value>
|
||||
</data>
|
||||
<data name="CachedMessageCleared" xml:space="preserve">
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="Ready" xml:space="preserve">
|
||||
<value>Я запустился!</value>
|
||||
</data>
|
||||
<data name="CachedMessageDeleted" xml:space="preserve">
|
||||
<value>Сообщение {0} удалено:</value>
|
||||
</data>
|
||||
<data name="CachedMessageCleared" xml:space="preserve">
|
||||
<value>Очищено сообщение от {0} в канале {1}: {2}</value>
|
||||
</data>
|
||||
<data name="CachedMessageEdited" xml:space="preserve">
|
||||
<data name="CachedMessageEdited" xml:space="preserve">
|
||||
<value>Отредактировано сообщение в канале {0}: {1} -> {2}</value>
|
||||
</data>
|
||||
<data name="DefaultWelcomeMessage" xml:space="preserve">
|
||||
<data name="DefaultWelcomeMessage" xml:space="preserve">
|
||||
<value>{0}, добро пожаловать на сервер {1}</value>
|
||||
</data>
|
||||
<data name="Beep1" xml:space="preserve">
|
||||
<value>Бап! </value>
|
||||
</data>
|
||||
<data name="Beep2" xml:space="preserve">
|
||||
<value>Боп! </value>
|
||||
</data>
|
||||
<data name="Beep1" xml:space="preserve">
|
||||
<value>Бап!</value>
|
||||
</data>
|
||||
<data name="Beep2" xml:space="preserve">
|
||||
<value>Боп!</value>
|
||||
</data>
|
||||
<data name="Beep3" xml:space="preserve">
|
||||
<value>Бип! </value>
|
||||
</data>
|
||||
<value>Бип!</value>
|
||||
</data>
|
||||
<data name="CommandNoPermissionBot" xml:space="preserve">
|
||||
<value>У меня недостаточно прав для выполнения этой команды!</value>
|
||||
</data>
|
||||
|
@ -465,13 +469,16 @@
|
|||
<data name="SettingsAutoStartEvents" xml:space="preserve">
|
||||
<value>Автоматически начинать события</value>
|
||||
</data>
|
||||
<data name="MissingReminderText" xml:space="preserve">
|
||||
<data name="MissingReminderText" xml:space="preserve">
|
||||
<value>Тебе нужно указать текст напоминания!</value>
|
||||
</data>
|
||||
<data name="FeedbackReminderAdded" xml:space="preserve">
|
||||
<data name="FeedbackReminderAdded" xml:space="preserve">
|
||||
<value>Хорошо, я упомяну тебя <t:{0}:f></value>
|
||||
</data>
|
||||
<data name="InvalidRemindIn" xml:space="preserve">
|
||||
<data name="InvalidRemindIn" xml:space="preserve">
|
||||
<value>Нужно указать время, через которое придёт напоминание!</value>
|
||||
</data>
|
||||
<data name="IssuedBy" xml:space="preserve">
|
||||
<value>Ответственный</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -98,49 +98,53 @@
|
|||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Ready" xml:space="preserve">
|
||||
<value>{0}я родился!</value>
|
||||
</data>
|
||||
<data name="CachedMessageDeleted" xml:space="preserve">
|
||||
<value>вырезано сообщение от {0} в канале {1}: {2}</value>
|
||||
</data>
|
||||
<data name="CachedMessageCleared" xml:space="preserve">
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="Ready" xml:space="preserve">
|
||||
<value>я родился!</value>
|
||||
</data>
|
||||
<data name="CachedMessageDeleted" xml:space="preserve">
|
||||
<value>сообщение {0} вырезано:</value>
|
||||
</data>
|
||||
<data name="CachedMessageCleared" xml:space="preserve">
|
||||
<value>вырезано сообщение (используя `!clear`) от {0} в канале {1}: {2}</value>
|
||||
</data>
|
||||
<data name="CachedMessageEdited" xml:space="preserve">
|
||||
<data name="CachedMessageEdited" xml:space="preserve">
|
||||
<value>переделано сообщение от {0}: {1} -> {2}</value>
|
||||
</data>
|
||||
<data name="DefaultWelcomeMessage" xml:space="preserve">
|
||||
<data name="DefaultWelcomeMessage" xml:space="preserve">
|
||||
<value>{0}, добро пожаловать на сервер {1}</value>
|
||||
</data>
|
||||
<data name="Beep1" xml:space="preserve">
|
||||
<value>брах! </value>
|
||||
</data>
|
||||
<data name="Beep2" xml:space="preserve">
|
||||
<value>брох! </value>
|
||||
</data>
|
||||
<data name="Beep1" xml:space="preserve">
|
||||
<value>брах!</value>
|
||||
</data>
|
||||
<data name="Beep2" xml:space="preserve">
|
||||
<value>брох!</value>
|
||||
</data>
|
||||
<data name="Beep3" xml:space="preserve">
|
||||
<value>брух! </value>
|
||||
</data>
|
||||
<value>брух!</value>
|
||||
</data>
|
||||
<data name="CommandNoPermissionBot" xml:space="preserve">
|
||||
<value>у меня прав нету, сделай что нибудь.</value>
|
||||
</data>
|
||||
|
@ -256,7 +260,7 @@
|
|||
<value>{0}квест {1} начинается в {2}!</value>
|
||||
</data>
|
||||
<data name="SettingsFrowningFace" xml:space="preserve">
|
||||
<value>оъмъомоъемъъео(((( </value>
|
||||
<value>оъмъомоъемъъео((((</value>
|
||||
</data>
|
||||
<data name="EventCancelled" xml:space="preserve">
|
||||
<value>квест {0} отменен!{1}</value>
|
||||
|
@ -465,13 +469,16 @@
|
|||
<data name="SettingsAutoStartEvents" xml:space="preserve">
|
||||
<value>автоматом стартить квесты</value>
|
||||
</data>
|
||||
<data name="MissingReminderText" xml:space="preserve">
|
||||
<data name="MissingReminderText" xml:space="preserve">
|
||||
<value>для крафта напоминалки нужен текст</value>
|
||||
</data>
|
||||
<data name="FeedbackReminderAdded" xml:space="preserve">
|
||||
<data name="FeedbackReminderAdded" xml:space="preserve">
|
||||
<value>вас понял, упоминание будет <t:{0}:f></value>
|
||||
</data>
|
||||
<data name="InvalidRemindIn" xml:space="preserve">
|
||||
<data name="InvalidRemindIn" xml:space="preserve">
|
||||
<value>шизоид у меня на часах такого нету</value>
|
||||
</data>
|
||||
<data name="IssuedBy" xml:space="preserve">
|
||||
<value>ответственный</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
Loading…
Add table
Reference in a new issue