Compare commits

...

17 Commits
4.0.0 ... 4.0.4

Author SHA1 Message Date
Kwoth
cc522ef872 Updated CHANGELOG.md 2022-03-04 04:55:39 +01:00
Kwoth
0e192ee7f0 Removed some lingering strings calling Expressions 'CustomReactions'. Id which shows up when you add a new expr is now correct. 2022-03-04 04:51:02 +01:00
Kwoth
ddd0592b30 Changed the intent error message to say 'all intents' 2022-03-04 02:23:48 +01:00
Kwoth
c4efe2965b Upped version to 4.0.3 2022-03-04 01:28:14 +01:00
Kwoth
a90b5a62f3 Fixed number spam in the console 2022-03-04 01:26:46 +01:00
Kwoth
50a4497532 Update CHANGELOG.md 2022-03-03 23:32:05 +00:00
Kwoth
c3d6183d73 Update CHANGELOG.md 2022-03-03 23:30:44 +00:00
Kwoth
864a8fd7b6 Updated CHANGELOG.md 2022-03-03 23:22:12 +01:00
Kwoth
e7db631151 Fixed .rero issue which was introduced in 4.0 2022-03-03 22:32:28 +01:00
Kwoth
c7b312196e Added upgrade instructions to the docs 2022-03-03 10:15:17 +01:00
Kwoth
8cd7a50720 Updated guide to reflect the new changes to the installer 2022-03-03 03:09:59 +01:00
Kwoth
f82c4c7019 - Updated Changelog.md
- Fixed intent error message condition
2022-03-03 02:45:18 +01:00
Kwoth
03367c5ec4 Added usePrivilegedIntents to creds.yml as an option for users who don't have them enabled
Added an explanation on how to enable intents if the bot doesn't have them and fails to login
2022-03-03 02:31:32 +01:00
Kwoth
01cc6e52d5 Enabled client log everywhere which could hopefully print login errors 2022-03-03 01:42:22 +01:00
Kwoth
c903bc9003 Added one more log line to pinpoint the login hang 2022-03-03 01:33:05 +01:00
Kwoth
323699d103 Small change to Bot.cs to pinpoint the logging in hang issue 2022-03-03 01:13:49 +01:00
Kwoth
63ced029ab Updated CHANGELOG.md 2022-03-02 06:54:17 +01:00
13 changed files with 147 additions and 74 deletions

View File

@@ -5,6 +5,36 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
## Unreleased
- More cool stuff coming soon
## [4.0.4] - 04.03.2022
### Fixed
- Fixed the `id` which shows up when you add a new Expression
- Fixed some strings which were still referring to "CustomReaction(s)" instead of "Expression(s)"
## [4.0.3] - 04.03.2022
### Fixed
- Console should no longer spam numbers when `.antispam` is enabled
## [4.0.2] - 03.03.2022
### Fixed
- Fixed `.rero` not working due to a bug introduced in 4.0
## [4.0.1] - 03.03.2022
### Added
- Added `usePrivilegedIntents` to creds.yml if you don't have or don't want (?) to use them
- Added a human-readable, detailed error message if logging in fails due to missing privileged intents
## [4.0.0] - 02.03.2022
### Added
- Added `.deleteemptyservers` command
- Added `.curtr <id>` which lets you see full information about one of your own transactions with the specified id
@@ -43,6 +73,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- [dev] Moved FilterWordsChannelId to a separate table
### Fixed
- Fixed twitch stream notifications (rewrote it to use the new api)
- Fixed an extra whitespace in usage part of command help if the command has no arguments
- Possible small fix for `.prune` ratelimiting
- `.gvc` should now properly trigger when a user is already in a gvc and changes his activity
@@ -462,4 +493,4 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
### Removed
- Removed admin requirement on `.scrm` as it didn't make sense
- Some Music commands are removed because of the complexity they bring in with little value (if you *really* want them back, you can open an issue and specify your *good* reason)
- Some Music commands are removed because of the complexity they bring in with little value (if you *really* want them back, you can open an issue and specify your *good* reason)

View File

@@ -26,14 +26,24 @@ It is recommended that you use **Ubuntu 20.04**, as there have been nearly no pr
## Linux From Source
Open Terminal (if you're on an installation with a window manager) and navigate to the location where you want to install the bot (for example `cd ~`)
##### Migration from v3 -> v4
Follow the following few steps only if you're migrating from v3. If not, skip to installation instructions.
Use the new installer script: `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
> - Install prerequisites (type `1` and press `enter`)
> - Download (type `2` and press `enter`)
> - Run (type `3` and press `enter`)
> - Done
##### Installation Instructions
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
Open Terminal (if you're on an installation with a window manager) and navigate to the location where you want to install the bot (for example `cd ~`)
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
2. Install prerequisites (type `1` and press enter)
3. Download the bot (type `2` and press enter)
4. Exit the installer (type `5` and press enter)
4. Exit the installer (type `6` and press enter)
5. Copy the creds.yml template `cp nadekobot/output/creds_example.yml nadekobot/output/creds.yml`
6. Open `nadekobot/output/creds.yml` with your favorite text editor. We will use nano here
- `nano nadekobot/output/creds.yml`
@@ -42,12 +52,13 @@ Open Terminal (if you're on an installation with a window manager) and navigate
- `CTRL` + `X`
- `Y`
- `Enter`
8. Run the bot (type `3` and press enter)
8. Run the installer script again `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
9. Run the bot (type `3` and press enter)
##### Source Update Instructions
1. ⚠ Stop the bot ⚠
2. Update and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
2. Update and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
3. Update the bot (type `2` and press enter)
4. Run the bot (type `3` and press enter)
5. 🎉

View File

@@ -31,7 +31,7 @@ sudo ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
##### Installation Instructions
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
2. Install prerequisites (type `1` and press enter)
3. Download the bot (type `2` and press enter)
4. Exit the installer in order to set up your `creds.yml`
@@ -49,7 +49,7 @@ sudo ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
##### Update Instructions
1. ⚠ Stop the bot
2. Update and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`
2. Update and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/v4/linuxAIO.sh && bash linuxAIO.sh`
3. Update the bot (type `2` and press enter)
4. Run the bot (type `3` and press enter)
5. 🎉

View File

@@ -52,6 +52,9 @@ public sealed class Bot
#else
50;
#endif
if(!_creds.UsePrivilegedIntents)
Log.Warning("You are not using privileged intents. Some features will not work properly");
Client = new(new()
{
@@ -63,8 +66,10 @@ public sealed class Bot
AlwaysDownloadUsers = false,
AlwaysResolveStickers = false,
AlwaysDownloadDefaultStickers = false,
GatewayIntents = GatewayIntents.All,
LogGatewayIntentWarnings = false
GatewayIntents = _creds.UsePrivilegedIntents
? GatewayIntents.All
: GatewayIntents.AllUnprivileged,
LogGatewayIntentWarnings = false,
});
_commandService = new(new()
@@ -75,9 +80,7 @@ public sealed class Bot
// _interactionService = new(Client.Rest);
#if GLOBAL_NADEKO || DEBUG
Client.Log += Client_Log;
#endif
}
@@ -216,30 +219,28 @@ public sealed class Bot
private async Task LoginAsync(string token)
{
var clientReady = new TaskCompletionSource<bool>();
var clientReady = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
Task SetClientReady()
async Task SetClientReady()
{
_ = Task.Run(async () =>
clientReady.TrySetResult(true);
try
{
clientReady.TrySetResult(true);
try
{
foreach (var chan in await Client.GetDMChannelsAsync())
await chan.CloseAsync();
}
catch
{
// ignored
}
});
return Task.CompletedTask;
foreach (var chan in await Client.GetDMChannelsAsync())
await chan.CloseAsync();
}
catch
{
// ignored
}
}
//connect
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
try
{
Client.Ready += SetClientReady;
await Client.LoginAsync(TokenType.Bot, token);
await Client.StartAsync();
}
@@ -253,11 +254,10 @@ public sealed class Bot
LoginErrorHandler.Handle(ex);
Helpers.ReadErrorAndExit(4);
}
Client.Ready += SetClientReady;
await clientReady.Task;
await clientReady.Task.ConfigureAwait(false);
Client.Ready -= SetClientReady;
Client.JoinedGuild += Client_JoinedGuild;
Client.LeftGuild += Client_LeftGuild;
@@ -341,7 +341,33 @@ public sealed class Bot
private Task Client_Log(LogMessage arg)
{
if (arg.Exception is not null)
if (arg.Message?.Contains("unknown dispatch", StringComparison.InvariantCultureIgnoreCase) ?? false)
return Task.CompletedTask;
if (arg.Exception is { InnerException: WebSocketClosedException { CloseCode: 4014 } })
{
Log.Error(@"
Login failed.
*** Please enable privileged intents ***
Certain Nadeko features require Discord's privileged gateway intents.
These include greeting and goodbye messages, as well as creating the Owner message channels for DM forwarding.
How to enable privileged intents:
1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
2. Select your Application.
3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
4. Enable all intents.
5. Restart your bot.
Read this only if your bot is in 100 or more servers:
You'll need to apply to use the intents with Discord, but for small selfhosts, all that is required is enabling the intents in the developer portal.
Yes, this is a new thing from Discord, as of October 2020. No, there's nothing we can do about it. Yes, we're aware it worked before.
While waiting for your bot to be accepted, you can change the 'usePrivilegedIntents' inside your creds.yml to 'false', although this will break many of the nadeko's features");
}
else if (arg.Exception is not null)
Log.Warning(arg.Exception, "{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);
else
Log.Warning("{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);

View File

@@ -14,12 +14,15 @@ public sealed class Creds : IBotCredentials
[Comment(@"List of Ids of the users who have bot owner permissions
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
public ICollection<ulong> OwnerIds { get; set; }
[Comment("Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")]
public bool UsePrivilegedIntents { get; set; }
[Comment(@"The number of shards that the bot will running on.
Leave at 1 if you don't know what you're doing.")]
public int TotalShards { get; set; }
[Comment(
[Comment(
@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
Used only for Youtube Data Api (at the moment).")]
@@ -96,8 +99,9 @@ Windows default
public Creds()
{
Version = 3;
Version = 4;
Token = string.Empty;
UsePrivilegedIntents = true;
OwnerIds = new List<ulong>();
TotalShards = 1;
GoogleApiKey = string.Empty;

View File

@@ -6,6 +6,7 @@ public interface IBotCredentials
string Token { get; }
string GoogleApiKey { get; }
ICollection<ulong> OwnerIds { get; }
bool UsePrivilegedIntents { get; }
string RapidApiKey { get; }
Creds.DbOptions Db { get; }

View File

@@ -10,7 +10,6 @@ public sealed class UserSpamStats
lock (_applyLock)
{
Cleanup();
Log.Information("{Count}", _messageTracker.Count.ToString());
return _messageTracker.Count;
}
}

View File

@@ -27,7 +27,7 @@ public partial class Administration
if (input.Length % 2 != 0 || target is null)
return;
var all = await input.Chunk(input.Length / 2)
var all = await input.Chunk(2)
.Select(async x =>
{
var inputRoleStr = x.First();

View File

@@ -31,7 +31,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -39,8 +39,8 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.new_cust_react))
.WithDescription($"#{ex.Id}")
.WithTitle(GetText(strs.expr_new))
.WithDescription($"#{new kwum(ex.Id)}")
.AddField(GetText(strs.trigger), key)
.AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
@@ -56,7 +56,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if ((channel is null && !_creds.IsOwner(ctx.User))
|| (channel is not null && !((IGuildUser)ctx.User).GuildPermissions.Administrator))
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -65,14 +65,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.edited_cust_react))
.WithTitle(GetText(strs.expr_edited))
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger)
.AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
}
else
await ReplyErrorLocalizedAsync(strs.edit_fail);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
}
[Cmd]
@@ -86,7 +86,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (expressions is null || !expressions.Any())
{
await ReplyErrorLocalizedAsync(strs.no_found);
await ReplyErrorLocalizedAsync(strs.expr_no_found);
return;
}
@@ -105,7 +105,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
: " // " + string.Join(" ", ex.GetReactions())))
.Join('\n');
return _eb.Create().WithOkColor().WithTitle(GetText(strs.custom_reactions)).WithDescription(desc);
return _eb.Create().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
},
expressions.Length,
20);
@@ -118,7 +118,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (found is null)
{
await ReplyErrorLocalizedAsync(strs.no_found_id);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
return;
}
@@ -135,7 +135,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -145,13 +145,13 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.deleted))
.WithTitle(GetText(strs.expr_deleted))
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
.AddField(GetText(strs.response), ex.Response.TrimTo(1024)));
}
else
await ReplyErrorLocalizedAsync(strs.no_found_id);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
}
[Cmd]
@@ -159,21 +159,21 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
var ex = _service.GetExpression(ctx.Guild?.Id, id);
if (ex is null)
{
await ReplyErrorLocalizedAsync(strs.no_found);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
return;
}
if (emojiStrs.Length == 0)
{
await _service.ResetExprReactions(ctx.Guild?.Id, id);
await ReplyConfirmLocalizedAsync(strs.crr_reset(Format.Bold(id.ToString())));
await ReplyConfirmLocalizedAsync(strs.expr_reset(Format.Bold(id.ToString())));
return;
}
@@ -204,7 +204,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
await _service.SetExprReactions(ctx.Guild?.Id, id, succ);
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()),
await ReplyConfirmLocalizedAsync(strs.expr_set(Format.Bold(id.ToString()),
succ.Select(static x => x.ToString()).Join(", ")));
}
@@ -237,14 +237,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
var (success, newVal) = await _service.ToggleExprOptionAsync(id, option);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.no_found_id);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
return;
}
@@ -270,7 +270,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
.WithDescription("This will delete all custom reactions on this server.")))
{
var count = _service.DeleteAllExpressions(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.cleared(count));
await ReplyConfirmLocalizedAsync(strs.exprs_cleared(count));
}
}
@@ -279,7 +279,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -298,7 +298,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}

View File

@@ -165,9 +165,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
if (File.Exists(CREDS_FILE_NAME))
{
var creds = Yaml.Deserializer.Deserialize<Creds>(File.ReadAllText(CREDS_FILE_NAME));
if (creds.Version <= 2)
if (creds.Version <= 3)
{
creds.Version = 3;
creds.Version = 4;
File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
}
}

View File

@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
{
public const string BOT_VERSION = "4.0.0";
public const string BOT_VERSION = "4.0.3";
public string Author
=> "Kwoth#2452";

View File

@@ -1,10 +1,12 @@
# DO NOT CHANGE
version: 3
version: 4
# Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/
token: ''
# List of Ids of the users who have bot owner permissions
# **DO NOT ADD PEOPLE YOU DON'T TRUST**
ownerIds: []
# Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted
usePrivilegedIntents: true
# The number of shards that the bot will running on.
# Leave at 1 if you don't know what you're doing.
totalShards: 1

View File

@@ -1,13 +1,13 @@
{
"deleted": "Custom Reaction deleted",
"insuff_perms": "Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for server custom reactions.",
"custom_reactions": "Custom Reactions",
"new_cust_react": "New Custom Reaction",
"no_found": "No custom reaction found.",
"no_found_id": "No custom reaction found with that id.",
"cleared": "All {0} custom reactions on this server have been removed.",
"crr_reset": "Custom reaction with id {0} will no longer add reactions.",
"crr_set": "Custom reaction with id {0} will add following reactions to the response message: {1}",
"expr_deleted": "Expression deleted",
"expr_insuff_perms": "Insufficient permissions. Requires Bot ownership for global expressions, and Administrator for server expressions.",
"expressions": "Expressions",
"expr_new": "New Expression",
"expr_no_found": "No expression found.",
"expr_no_found_id": "No expression found with that id.",
"exprs_cleared": "All {0} expressions on this server have been removed.",
"expr_reset": "Expression with id {0} will no longer add reactions.",
"expr_set": "Expression with id {0} will add following reactions to the response message: {1}",
"invalid_emojis": "All emojis you've specified are invalid.",
"invalid_emoji_link": "Specified link is either not an image or exceeds 256KB.",
"emoji_add_error": "Error adding emoji. You either ran out of emoji slots, or image size is inadequate.",
@@ -636,8 +636,8 @@
"vcrole_added": "Users who join {0} voice channel will get {1} role.",
"vcrole_removed": "Users who join {0} voice channel will no longer get a role.",
"vc_role_list": "Voice channel roles",
"option_disabled": "{0} option is now disabled for custom reaction with id {1}.",
"option_enabled": "{0} option is now enabled for custom reaction with id {1}.",
"option_disabled": "{0} option is now disabled for the expression with id {1}.",
"option_enabled": "{0} option is now enabled for the expression with id {1}.",
"aliases_none": "No alias found",
"alias_added": "Typing {0} will now be an alias of {1}.",
"alias_list": "List of aliases",
@@ -847,7 +847,7 @@
"club_apps_for": "Applicants for {0} club",
"club_leaderboard": "Club leaderboard - page {0}",
"template_reloaded": "Xp template has been reloaded.",
"edited_cust_react": "Custom Reaction Edited",
"expr_edited": "Expression Edited",
"self_assign_are_exclusive": "You can only choose 1 role from each group.",
"self_assign_are_not_exclusive": "You can choose any number of roles from any group.",
"self_assign_group": "Group {0}",
@@ -870,7 +870,6 @@
"feed_no_feed": "You haven't subscribed to any feeds on this server.",
"restart_fail": "You must setup RestartCommand in your creds.yml",
"restarting": "Restarting.",
"edit_fail": "Custom reaction with that ID does not exist.",
"streaming": "Streaming",
"rafflecur": "{0} Currency Raffle",
"rafflecur_joined": "User {0} joined the raffle",