Fixed some countries, replacements, updated bot.yml

This commit is contained in:
Toastie 2024-09-14 15:11:14 +12:00
parent 3e35c6ffc7
commit 5b451cee74
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
8 changed files with 159 additions and 160 deletions

View file

@ -5,6 +5,99 @@ public partial class Administration
[Group] [Group]
public partial class GreetCommands : EllieModule<GreetService> public partial class GreetCommands : EllieModule<GreetService>
{ {
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Boost()
=> Toggle(GreetType.Boost);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostDel(int timer = 30)
=> SetDel(GreetType.Boost, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Boost, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Greet()
=> Toggle(GreetType.Greet);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDel(int timer = 30)
=> SetDel(GreetType.Greet, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Greet, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDm()
=> Toggle(GreetType.GreetDm);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmMsg([Leftover] string? text = null)
=> SetMsg(GreetType.GreetDm, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Bye()
=> Toggle(GreetType.Bye);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeDel(int timer = 30)
=> SetDel(GreetType.Bye, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Bye, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Greet, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.GreetDm, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task ByeTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Bye, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task BoostTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Boost, user);
public async Task Toggle(GreetType type) public async Task Toggle(GreetType type)
{ {
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id, type); var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id, type);
@ -75,17 +168,16 @@ public partial class Administration
{ {
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
await _service.SetMessage(ctx.Guild.Id, type, null);
var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type); var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
var msg = conf?.MessageText ?? "No message set."; var msg = conf?.MessageText ?? GreetService.GetDefaultGreet(type);
await Response() await Response()
.Confirm( .Confirm(
type switch type switch
{ {
GreetType.Boost => strs.boostmsg_cur(msg.SanitizeMentions()), GreetType.Boost => strs.boostmsg_cur(msg),
GreetType.Greet => strs.greetmsg_cur(msg.SanitizeMentions()), GreetType.Greet => strs.greetmsg_cur(msg),
GreetType.Bye => strs.byemsg_cur(msg.SanitizeMentions()), GreetType.Bye => strs.byemsg_cur(msg),
GreetType.GreetDm => strs.greetdmmsg_cur(msg.SanitizeMentions()), GreetType.GreetDm => strs.greetdmmsg_cur(msg),
_ => strs.error _ => strs.error
}) })
.SendAsync(); .SendAsync();
@ -121,100 +213,6 @@ public partial class Administration
} }
} }
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Boost()
=> Toggle(GreetType.Boost);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostDel(int timer = 30)
=> SetDel(GreetType.Boost, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task BoostMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Boost, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Greet()
=> Toggle(GreetType.Greet);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDel(int timer = 30)
=> SetDel(GreetType.Greet, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Greet, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDm()
=> Toggle(GreetType.GreetDm);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task GreetDmMsg([Leftover] string? text = null)
=> SetMsg(GreetType.GreetDm, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task Bye()
=> Toggle(GreetType.Bye);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeDel(int timer = 30)
=> SetDel(GreetType.Bye, timer);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public Task ByeMsg([Leftover] string? text = null)
=> SetMsg(GreetType.Bye, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task GreetTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Greet, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task GreetDmTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.GreetDm, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task ByeTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Bye, user);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public Task BoostTest([Leftover] IGuildUser? user = null)
=> Test(GreetType.Boost, user);
public async Task Test(GreetType type, IGuildUser? user = null) public async Task Test(GreetType type, IGuildUser? user = null)
{ {
user ??= (IGuildUser)ctx.User; user ??= (IGuildUser)ctx.User;

View file

@ -14,7 +14,6 @@ public class GreetService : IEService, IReadyExecutor
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly BotConfigService _bss;
private readonly IReplacementService _repSvc; private readonly IReplacementService _repSvc;
private readonly IBotCache _cache; private readonly IBotCache _cache;
private readonly IMessageSenderService _sender; private readonly IMessageSenderService _sender;
@ -29,7 +28,6 @@ public class GreetService : IEService, IReadyExecutor
public GreetService( public GreetService(
DiscordSocketClient client, DiscordSocketClient client,
DbService db, DbService db,
BotConfigService bss,
IMessageSenderService sender, IMessageSenderService sender,
IReplacementService repSvc, IReplacementService repSvc,
IBotCache cache IBotCache cache
@ -37,10 +35,15 @@ public class GreetService : IEService, IReadyExecutor
{ {
_db = db; _db = db;
_client = client; _client = client;
_bss = bss;
_repSvc = repSvc; _repSvc = repSvc;
_cache = cache; _cache = cache;
_sender = sender; _sender = sender;
foreach (var type in Enum.GetValues<GreetType>())
{
_enabled[type] = new();
}
} }
public async Task OnReadyAsync() public async Task OnReadyAsync()
@ -52,11 +55,17 @@ public class GreetService : IEService, IReadyExecutor
var enabled = await uow.GetTable<GreetSettings>() var enabled = await uow.GetTable<GreetSettings>()
.Where(x => x.GuildId.In(guilds)) .Where(x => x.GuildId.In(guilds))
.Where(x => x.IsEnabled) .Where(x => x.IsEnabled)
.Select(x => new
{
x.GuildId,
x.GreetType
})
.ToListAsync(); .ToListAsync();
_enabled = enabled.GroupBy(x => x.GreetType, v => v.GuildId) foreach (var e in enabled)
.ToDictionary(x => x.Key, x => x.ToHashSet().ToConcurrentSet()) {
.ToConcurrent(); _enabled[e.GreetType].Add(e.GuildId);
}
} }
_client.UserJoined += OnUserJoined; _client.UserJoined += OnUserJoined;
@ -146,7 +155,7 @@ public class GreetService : IEService, IReadyExecutor
return Task.CompletedTask; return Task.CompletedTask;
} }
private readonly TypedKey<GreetSettings?> _greetSettingsKey = new(); private readonly TypedKey<GreetSettings?> _greetSettingsKey = new("greet_settings");
public async Task<GreetSettings?> GetGreetSettingsAsync(ulong gid, GreetType type) public async Task<GreetSettings?> GetGreetSettingsAsync(ulong gid, GreetType type)
=> await _cache.GetOrAddAsync<GreetSettings?>(_greetSettingsKey, => await _cache.GetOrAddAsync<GreetSettings?>(_greetSettingsKey,
@ -160,6 +169,9 @@ public class GreetService : IEService, IReadyExecutor
.Where(x => x.GuildId == gid && x.GreetType == type) .Where(x => x.GuildId == gid && x.GreetType == type)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (res is not null)
res.MessageText ??= GetDefaultGreet(type);
return res; return res;
} }
@ -324,12 +336,12 @@ public class GreetService : IEService, IReadyExecutor
} }
private static string GetDefaultGreet(GreetType greetType) public static string GetDefaultGreet(GreetType greetType)
=> greetType switch => greetType switch
{ {
GreetType.Boost => "%user.name% has boosted the server!", GreetType.Boost => "%user.mention% has boosted the server!",
GreetType.Greet => "%user.name% has joined the server!", GreetType.Greet => "%user.mention% has joined the server!",
GreetType.Bye => "%user.name has left the server!", GreetType.Bye => "%user.name% has left the server!",
GreetType.GreetDm => "Welcome to the server %user.name%", GreetType.GreetDm => "Welcome to the server %user.name%",
_ => "%user.name% did something new!" _ => "%user.name% did something new!"
}; };
@ -343,16 +355,16 @@ public class GreetService : IEService, IReadyExecutor
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var q = uow.GetTable<GreetSettings>(); var q = uow.GetTable<GreetSettings>();
if (value is null)
value = !_enabled[greetType].Contains(guildId);
if (value is { } v) if (value is { } v)
{ {
var defaultGreet = GetDefaultGreet(greetType);
await q await q
.InsertOrUpdateAsync(() => new() .InsertOrUpdateAsync(() => new()
{ {
GuildId = guildId, GuildId = guildId,
GreetType = greetType, GreetType = greetType,
MessageText = defaultGreet,
IsEnabled = v, IsEnabled = v,
ChannelId = channelId, ChannelId = channelId,
}, },
@ -367,40 +379,20 @@ public class GreetService : IEService, IReadyExecutor
GreetType = greetType, GreetType = greetType,
}); });
} }
else
{
var result = await q
.Where(x => x.GuildId == guildId && x.GreetType == greetType)
.UpdateWithOutputAsync((old) => new()
{
IsEnabled = !old.IsEnabled
},
(o, n) => n.IsEnabled);
if (result.Length > 0)
value = result[0];
}
if (value is true) if (value is true)
{ {
_enabled[greetType].Add(guildId); _enabled[greetType].Add(guildId);
} return true;
else
{
_enabled[greetType].TryRemove(guildId);
} }
return true; _enabled[greetType].TryRemove(guildId);
return false;
} }
public async Task<bool> SetMessage(ulong guildId, GreetType greetType, string? message) public async Task<bool> SetMessage(ulong guildId, GreetType greetType, string? message)
{ {
message = message?.SanitizeMentions();
if (string.IsNullOrWhiteSpace(message))
message = GetDefaultGreet(greetType);
await using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
await uow.GetTable<GreetSettings>() await uow.GetTable<GreetSettings>()
@ -475,6 +467,7 @@ public class GreetService : IEService, IReadyExecutor
{ {
if (conf.GreetType == GreetType.GreetDm) if (conf.GreetType == GreetType.GreetDm)
{ {
await _greetQueue.Writer.WriteAsync((conf, user, channel as ITextChannel));
return await GreetDmUser(conf, user); return await GreetDmUser(conf, user);
} }

View file

@ -72,7 +72,7 @@ public partial class Searches
} }
[Cmd] [Cmd]
public async Task Image([Leftover] string? query = null) public async Task Image([Leftover] string? query)
{ {
query = query?.Trim(); query = query?.Trim();

View file

@ -12,7 +12,7 @@ namespace EllieBot.Common.Configs;
public sealed partial class BotConfig : ICloneable<BotConfig> public sealed partial class BotConfig : ICloneable<BotConfig>
{ {
[Comment("""DO NOT CHANGE""")] [Comment("""DO NOT CHANGE""")]
public int Version { get; set; } = 7; public int Version { get; set; } = 8;
[Comment(""" [Comment("""
Most commands, when executed, have a small colored line Most commands, when executed, have a small colored line

View file

@ -61,6 +61,18 @@ public sealed partial class ReplacementPatternStore
private void WithUsers() private void WithUsers()
{ {
Register("%user%", static (IUser user) => user.Mention);
Register("%user.mention%", static (IUser user) => user.Mention);
Register("%user.fullname%", static (IUser user) => user.ToString()!);
Register("%user.name%", static (IUser user) => user.Username);
Register("%user.discrim%", static (IUser user) => user.Discriminator);
Register("%user.avatar%", static (IUser user) => user.RealAvatarUrl().ToString());
Register("%user.id%", static (IUser user) => user.Id.ToString());
Register("%user.created_time%", static (IUser user) => user.CreatedAt.ToString("HH:mm"));
Register("%user.created_date%", static (IUser user) => user.CreatedAt.ToString("dd.MM.yyyy"));
Register("%user.joined_time%", static (IGuildUser user) => user.JoinedAt?.ToString("HH:mm"));
Register("%user.joined_date%", static (IGuildUser user) => user.JoinedAt?.ToString("dd.MM.yyyy"));
Register("%user%", Register("%user%",
static (IUser[] users) => string.Join(" ", users.Select(user => user.Mention))); static (IUser[] users) => string.Join(" ", users.Select(user => user.Mention)));
Register("%user.mention%", Register("%user.mention%",

View file

@ -69,5 +69,11 @@ public sealed class BotConfigService : ConfigServiceBase<BotConfig>
c.Version = 7; c.Version = 7;
c.IgnoreOtherBots = true; c.IgnoreOtherBots = true;
}); });
if (data.Version < 8)
ModifyConfig(c =>
{
c.Version = 8;
});
} }
} }

View file

@ -1,5 +1,5 @@
# DO NOT CHANGE # DO NOT CHANGE
version: 7 version: 8
# Most commands, when executed, have a small colored line # Most commands, when executed, have a small colored line
# next to the response. The color depends whether the command # next to the response. The color depends whether the command
# is completed, errored or in progress (pending) # is completed, errored or in progress (pending)
@ -80,16 +80,6 @@ blocked:
modules: [] modules: []
# Which string will be used to recognize the commands # Which string will be used to recognize the commands
prefix: . prefix: .
# Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
# 1st user who joins will get greeted immediately
# If more users join within the next 5 seconds, they will be greeted in groups of 5.
# This will cause %user.mention% and other placeholders to be replaced with multiple users.
# Keep in mind this might break some of your embeds - for example if you have %user.avatar% in the thumbnail,
# it will become invalid, as it will resolve to a list of avatars of grouped users.
# note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
# servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
# and (slightly) reduce the greet spam in those servers.
groupGreets: false
# Whether the bot will rotate through all specified statuses. # Whether the bot will rotate through all specified statuses.
# This setting can be changed via .ropl command. # This setting can be changed via .ropl command.
# See RotatingStatuses submodule in Administration. # See RotatingStatuses submodule in Administration.

View file

@ -140,13 +140,13 @@
imageUrl: https://cdn.nadeko.bot/flags/gt-flag.gif imageUrl: https://cdn.nadeko.bot/flags/gt-flag.gif
- word: Guinea - word: Guinea
imageUrl: https://cdn.nadeko.bot/flags/gv-flag.gif imageUrl: https://cdn.nadeko.bot/flags/gv-flag.gif
- word: Guinea-Bissau - word: Guinea Bissau
imageUrl: https://cdn.nadeko.bot/flags/pu-flag.gif imageUrl: https://cdn.nadeko.bot/flags/pu-flag.gif
- word: Guyana - word: Guyana
imageUrl: https://cdn.nadeko.bot/flags/gy-flag.gif imageUrl: https://cdn.nadeko.bot/flags/gy-flag.gif
- word: Haiti - word: Haiti
imageUrl: https://cdn.nadeko.bot/flags/ha-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ha-flag.gif
- word: Holy See - word: Vatican
imageUrl: https://cdn.nadeko.bot/flags/vt-flag.gif imageUrl: https://cdn.nadeko.bot/flags/vt-flag.gif
- word: Honduras - word: Honduras
imageUrl: https://cdn.nadeko.bot/flags/ho-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ho-flag.gif
@ -184,7 +184,7 @@
imageUrl: https://cdn.nadeko.bot/flags/ku-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ku-flag.gif
- word: Kyrgyzstan - word: Kyrgyzstan
imageUrl: https://cdn.nadeko.bot/flags/kg-flag.gif imageUrl: https://cdn.nadeko.bot/flags/kg-flag.gif
- word: Laos - word: Lao People's Democratic Republic
imageUrl: https://cdn.nadeko.bot/flags/la-flag.gif imageUrl: https://cdn.nadeko.bot/flags/la-flag.gif
- word: Latvia - word: Latvia
imageUrl: https://cdn.nadeko.bot/flags/lg-flag.gif imageUrl: https://cdn.nadeko.bot/flags/lg-flag.gif
@ -282,7 +282,7 @@
imageUrl: https://cdn.nadeko.bot/flags/qa-flag.gif imageUrl: https://cdn.nadeko.bot/flags/qa-flag.gif
- word: Romania - word: Romania
imageUrl: https://cdn.nadeko.bot/flags/ro-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ro-flag.gif
- word: Russia - word: Russian Federation
imageUrl: https://cdn.nadeko.bot/flags/rs-flag.gif imageUrl: https://cdn.nadeko.bot/flags/rs-flag.gif
- word: Rwanda - word: Rwanda
imageUrl: https://cdn.nadeko.bot/flags/rw-flag.gif imageUrl: https://cdn.nadeko.bot/flags/rw-flag.gif
@ -318,7 +318,7 @@
imageUrl: https://cdn.nadeko.bot/flags/so-flag.gif imageUrl: https://cdn.nadeko.bot/flags/so-flag.gif
- word: South Africa - word: South Africa
imageUrl: https://cdn.nadeko.bot/flags/sf-flag.gif imageUrl: https://cdn.nadeko.bot/flags/sf-flag.gif
- word: South Korea - word: Republic of Korea
imageUrl: https://cdn.nadeko.bot/flags/ks-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ks-flag.gif
- word: South Sudan - word: South Sudan
imageUrl: https://cdn.nadeko.bot/flags/od-flag.gif imageUrl: https://cdn.nadeko.bot/flags/od-flag.gif
@ -326,9 +326,9 @@
imageUrl: https://cdn.nadeko.bot/flags/sp-flag.gif imageUrl: https://cdn.nadeko.bot/flags/sp-flag.gif
- word: Sri Lanka - word: Sri Lanka
imageUrl: https://cdn.nadeko.bot/flags/ce-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ce-flag.gif
- word: St. Vincent Grenadines - word: Saint Vincent and the Grenadines
imageUrl: https://cdn.nadeko.bot/flags/vc-flag.gif imageUrl: https://cdn.nadeko.bot/flags/vc-flag.gif
- word: State of Palestine - word: Palestine
imageUrl: https://cdn.nadeko.bot/flags/palestine-flag.gif imageUrl: https://cdn.nadeko.bot/flags/palestine-flag.gif
- word: Sudan - word: Sudan
imageUrl: https://cdn.nadeko.bot/flags/su-flag.gif imageUrl: https://cdn.nadeko.bot/flags/su-flag.gif
@ -338,11 +338,11 @@
imageUrl: https://cdn.nadeko.bot/flags/sw-flag.gif imageUrl: https://cdn.nadeko.bot/flags/sw-flag.gif
- word: Switzerland - word: Switzerland
imageUrl: https://cdn.nadeko.bot/flags/sz-flag.gif imageUrl: https://cdn.nadeko.bot/flags/sz-flag.gif
- word: Syria - word: Syrian Arab Republic
imageUrl: https://cdn.nadeko.bot/flags/sy-flag.gif imageUrl: https://cdn.nadeko.bot/flags/sy-flag.gif
- word: Tajikistan - word: Tajikistan
imageUrl: https://cdn.nadeko.bot/flags/ti-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ti-flag.gif
- word: Tanzania - word: United Republic of Tanzania
imageUrl: https://cdn.nadeko.bot/flags/tz-flag.gif imageUrl: https://cdn.nadeko.bot/flags/tz-flag.gif
- word: Thailand - word: Thailand
imageUrl: https://cdn.nadeko.bot/flags/th-flag.gif imageUrl: https://cdn.nadeko.bot/flags/th-flag.gif
@ -380,7 +380,7 @@
imageUrl: https://cdn.nadeko.bot/flags/nh-flag.gif imageUrl: https://cdn.nadeko.bot/flags/nh-flag.gif
- word: Venezuela - word: Venezuela
imageUrl: https://cdn.nadeko.bot/flags/ve-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ve-flag.gif
- word: Vietnam - word: Viet Nam
imageUrl: https://cdn.nadeko.bot/flags/vm-flag.gif imageUrl: https://cdn.nadeko.bot/flags/vm-flag.gif
- word: Yemen - word: Yemen
imageUrl: https://cdn.nadeko.bot/flags/ym-flag.gif imageUrl: https://cdn.nadeko.bot/flags/ym-flag.gif