Compare commits

..

No commits in common. "1.4" and "redis" have entirely different histories.
1.4 ... redis

41 changed files with 4693 additions and 673 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.12
VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}"
EndProject
@ -33,4 +33,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
EndGlobalSection
EndGlobal

View File

@ -56,13 +56,13 @@ Follow this Detailed [Guide](http://discord.kongslien.net/guide.html).
###Question 12: I'm building NadekoBot from source, but I get hundreds of (namespace) errors without changing anything!?
-----
**Answer:** Using Visual Studio, you can solve these errors by going to `Tools` -> `NuGet Package Manager` -> `Manage NuGet Packages for Solution`. Go to the Installed tab, select the Packages that were missing (usually `Newtonsoft.json` and `RestSharp`) and install them for all projects.
**Answer:** Using Visual Studio, you can solve these errors by going to `Tools` -> `NuGet Package Manager` -> `Manage NuGet Packages for Solution`. Go to the Installed tab, select the Packages that were missing (usually `Newtonsoft.json` and `RestSharp`) and install them for all projects
###Question 13: My bot has all permissions but it's still saying, "Failed to add roles. Bot has insufficient permissions". How do I fix this?
----------
**Answer:** Discord has added a few new features and the roles now follow the role hierarchy, which means you need to place your bot's role above every other role on your server to fix the issue. [Here's](https://support.discordapp.com/hc/en-us/articles/214836687-Role-Management-101) a link to Discord's role management 101.
**Answer:** Discord has added few new features and the roles now follows the role hierarchy which means you need to place your bot's role above every-other role your server has to fix the role hierarchy issue. [Here's](https://support.discordapp.com/hc/en-us/articles/214836687-Role-Management-101) a link to Discords role management 101.
**Please Note:** *The bot can only set/add all roles below its own highest role. It cannot assign its "highest role" to anyone else.*
**Please Note:** *The bot can only set/add all roles below its own highest role. It can not assign it's "highest role" to anyone else.*
###Question 14: I've broken permissions and am stuck, can I reset permissions?
----------

View File

@ -1,9 +1,9 @@
Permissions Overview
===================
Have you ever felt confused or even overwhelmed when trying to set Nadeko's permissions? In this guide we will be explaining how to use the
permission commands correctly and even cover a few common questions! Every command we discuss here can be found in the [Commands List](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#permissions).
permission commands correctly and even cover a few common questions! Every command we discuss here can be found in the [Commands List](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/#permissions).
**To see the guide, [click here](http://nadekobot.readthedocs.io/en/latest/Permissions%20System/)**
**To see the old guide for versions 0.9 and below, see [here](http://nadekobot.readthedocs.io/en/latest/Permissions%20System/)**
Why do we use the Permissions Commands?
------------------------------
@ -15,14 +15,11 @@ With the permissions system it possible to restrict who can skip the current son
First Time Setup
------------------
To change permissions you **must** meet the following requirements:
To change permissions you **must** meet the following requirement:
**Be the owner of the server**
**If you are NOT the server owner, get the role specified by `.permrole` (By default, this is Nadeko)**
**Have the role specified by `.permrole` (By default, this is Nadeko)**
If you have an existing role called `Nadeko` but can't assign it to yourself, create a new role called `Nadeko` and assign that to yourself.
![img0](https://i.imgur.com/5QKZqqy.gif)
If you would like to set a different role, such as `Admins`, to be the role required to edit permissions, do `.permrole Admins` (you must have the current permission role to be able to do this).
@ -38,7 +35,7 @@ To view this permissions chain, do `.listperms`, with the top of the chain being
If you want to remove a permission from the chain of permissions, do `.removeperm X` to remove rule number X and similarly, do `.moveperm X Y` to move rule number X to number Y (moving, not swapping!).
As an example, if you wanted to enable NSFW for a certain role, say "Lewd", you could do `.rolemdl NSFW enable Lewd`.
This adds the rule to the top of the permissions chain so even if the default `.sm NSFW disable` rule exists, the "Lewd" role will be able to use the NSFW module.
This adds the rule to the top of the permissions chain so even if the default `.sm NSFW disabled` rule exists, the "Lewd" role will be able to use the NSFW module.
If you want the bot to notify users why they can't use a command or module, use `.verbose true` and Nadeko will tell you what rule is preventing the command.

View File

@ -6,17 +6,17 @@ Prerequisites
*Clone the repo*
`git clone -b 1.4 https://github.com/Kwoth/NadekoBot`
`cd NadekoBot/src/NadekoBot`
Edit `credentials.json.` Read the [JSON Explanations](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/) guide if you don't know how to set it up.
Edit `credentials.json.` Read the JSON Exaplanations guide on the left if you don't know how to set it up
*run*
`dotnet restore`
`dotnet run -c Release`
*when you decide to update in the future (might not work if you've made custom edits to the source, make sure you know how git works)*
*when you decide to updatein the future (might not work if you've made custom edits to the source, make sure you know how git works)*
`git pull`
`dotnet restore`
`dotnet run -c Release`
[.netcore]: https://www.microsoft.com/net/download/core#/sdk
[ffmpeg]: http://ffmpeg.zeranoe.com/builds/
[git]: https://git-scm.com/downloads
[git]: https://git-scm.com/downloads

View File

@ -1,30 +1,30 @@
#### If you have NadekoBot 1.x on Windows
- Go to `NadekoBot\src\NadekoBot` and backup your `credentials.json` file; then go to `NadekoBot\src\NadekoBot\bin\Release\netcoreapp1.0` and backup your `data` folder.
- Follow the [Windows Guide](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/) and install the latest version of **NadekoBot**.
- Navigate to your **old** `Nadeko` folder and copy your `credentials.json` file and the `data` folder.
- Paste credentials into the **NadekoBot 1.4x+** `C:\Program Files\NadekoBot\system` folder.
- Paste your **old** `Nadeko` data folder into **NadekoBot 1.4x+** `C:\Program Files\NadekoBot\system` folder.
- Paste your `credentials.json` file into the `C:\Program Files\NadekoBot\system` folder.
- Paste your `data` folder into `C:\Program Files\NadekoBot\system` folder.
- If it asks you to overwrite files, it is fine to do so.
- Next launch your **new** Nadeko as the guide describes, if it is not already running.
#### If you are running Dockerised Nadeko
- Shutdown your existing container `docker stop nadeko`.
- Shutdown your existing container **docker stop nadeko**.
- Move you credentials and other files to another folder.
- Delete your container `docker rm nadeko`.
- Create a new container `docker create --name=nadeko -v /nadeko/:/root/nadeko uirel/nadeko:1.4`.
- Start the container with `docker start nadeko`, and wait for it to complain about lacking credentials.
- Stop the container, `docker stop nadeko`, open the `Nadeko` folder and replace the credentials, database and other files with your copies.
- Restart the container `docker start nadeko`.
- Delete your container **docker rm nadeko**.
- Create a new container **docker create --name=nadeko -v /nadeko/:/root/nadeko uirel/nadeko:1.4**.
- Start the container **docker start nadeko** wait for it to complain about lacking credentials.
- Stop the container **docker stop nadeko** open the nadeko folder and replace the credentials, database and other files with your copies.
- Restart the container **docker start nadeko**.
#### If you have NadekoBot 1.x on Linux or macOS
- Backup the `NadekoBot.db` from `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data`.
- Backup the `credentials.json` from `NadekoBot/src/NadekoBot/`.
- **For MacOS Users Only:** download and install the latest version of [.NET Core SDK](https://www.microsoft.com/net/core#macos).
- Backup the `NadekoBot.db` from `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data`
- Backup the `credentials.json` from `NadekoBot/src/NadekoBot/`
- **For MacOS Users Only:** download and install the latest version of [.NET Core SDK](https://www.microsoft.com/net/core#macos)
- Next, use the command `cd ~ && wget -N https://github.com/Kwoth/NadekoBot-BashScript/raw/1.4/linuxAIO.sh && bash linuxAIO.sh`
- **For Ubuntu, Debian and CentOS Users Only:** use the option `4. Auto-Install Prerequisites` to install the latest version of .NET Core SDK.
- Use option `1. Download NadekoBot` to update your NadekoBot to 1.4+ version.
- Use option `1. Download NadekoBot` to update your NadekoBot to 1.4.x.
- Next, just [run your NadekoBot.](http://nadekobot.readthedocs.io/en/latest/guides/Linux%20Guide/#running-nadekobot)
- *NOTE: 1.4+ version uses `NadekoBot.db` file from `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data` folder.*
- *NOTE: 1.4.x uses `NadekoBot.db` file from `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data` folder.*

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class totalxp : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "TotalXp",
table: "DiscordUser",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql(MigrationQueries.TotalXp);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TotalXp",
table: "DiscordUser");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class clubadmins : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsClubAdmin",
table: "DiscordUser",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsClubAdmin",
table: "DiscordUser");
}
}
}

View File

@ -34,5 +34,9 @@ INSERT INTO DiscordUser
FROM DiscordUser_tmp;
DROP TABLE DiscordUser_tmp;";
public static string TotalXp { get; } =
@"UPDATE DiscordUser
SET TotalXp = ifnull((SELECT SUM(Xp) FROM UserXpStats WHERE UserId = DiscordUser.UserId), 0)";
}
}

View File

@ -462,14 +462,18 @@ namespace NadekoBot.Migrations
b.Property<string>("Discriminator");
b.Property<bool>("IsClubAdmin");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 236, DateTimeKind.Local));
.HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local));
b.Property<DateTime>("LastXpGain");
b.Property<int>("NotifyOnLevelUp");
b.Property<int>("TotalXp");
b.Property<ulong>("UserId");
b.Property<string>("Username");
@ -1362,7 +1366,7 @@ namespace NadekoBot.Migrations
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 238, DateTimeKind.Local));
.HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local));
b.Property<int>("NotifyOnLevelUp");

View File

@ -30,8 +30,9 @@ namespace NadekoBot.Modules.Administration
private readonly IImagesService _images;
private readonly MusicService _music;
private readonly IBotConfigProvider _bc;
private readonly NadekoBot _bot;
public SelfCommands(DbService db, DiscordSocketClient client,
public SelfCommands(DbService db, NadekoBot bot, DiscordSocketClient client,
MusicService music, IImagesService images, IBotConfigProvider bc)
{
_db = db;
@ -39,6 +40,7 @@ namespace NadekoBot.Modules.Administration
_images = images;
_music = music;
_bc = bc;
_bot = bot;
}
[NadekoCommand, Usage, Description, Aliases]
@ -349,7 +351,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task SetGame([Remainder] string game = null)
{
await _client.SetGameAsync(game).ConfigureAwait(false);
await _bot.SetGameAsync(game).ConfigureAwait(false);
await ReplyConfirmLocalized("set_game").ConfigureAwait(false);
}

View File

@ -7,6 +7,7 @@ using NadekoBot.Modules.Music.Services;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Services
{
@ -16,6 +17,7 @@ namespace NadekoBot.Modules.Administration.Services
private readonly DiscordSocketClient _client;
private readonly MusicService _music;
private readonly Logger _log;
private readonly IDataCache _cache;
private readonly Replacer _rep;
private readonly DbService _db;
private readonly IBotConfigProvider _bcp;
@ -27,50 +29,60 @@ namespace NadekoBot.Modules.Administration.Services
public int Index { get; set; }
}
public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp, MusicService music, DbService db)
public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp,
MusicService music, DbService db, IDataCache cache, NadekoBot bot)
{
_client = client;
_bcp = bcp;
_music = music;
_db = db;
_log = LogManager.GetCurrentClassLogger();
_rep = new ReplacementBuilder()
.WithClient(client)
.WithStats(client)
.WithMusic(music)
.Build();
_cache = cache;
_t = new Timer(async (objState) =>
if (client.ShardId == 0)
{
try
_rep = new ReplacementBuilder()
.WithClient(client)
.WithStats(client)
.WithMusic(music)
.Build();
_t = new Timer(async (objState) =>
{
bcp.Reload();
try
{
bcp.Reload();
var state = (TimerState)objState;
if (!BotConfig.RotatingStatuses)
return;
if (state.Index >= BotConfig.RotatingStatusMessages.Count)
state.Index = 0;
var state = (TimerState)objState;
if (!BotConfig.RotatingStatuses)
return;
if (state.Index >= BotConfig.RotatingStatusMessages.Count)
state.Index = 0;
if (!BotConfig.RotatingStatusMessages.Any())
return;
var status = BotConfig.RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status))
return;
if (!BotConfig.RotatingStatusMessages.Any())
return;
var status = BotConfig.RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status))
return;
status = _rep.Replace(status);
status = _rep.Replace(status);
try { await client.SetGameAsync(status).ConfigureAwait(false); }
try
{
await bot.SetGameAsync(status).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
catch (Exception ex)
{
_log.Warn(ex);
_log.Warn("Rotating playing status errored.\n" + ex);
}
}
catch (Exception ex)
{
_log.Warn("Rotating playing status errored.\n" + ex);
}
}, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
}
}
}

View File

@ -58,8 +58,7 @@ namespace NadekoBot.Modules.CustomReactions
if (channel == null)
{
Array.Resize(ref _service.GlobalReactions, _service.GlobalReactions.Length + 1);
_service.GlobalReactions[_service.GlobalReactions.Length - 1] = cr;
await _service.AddGcr(cr).ConfigureAwait(false);
}
else
{
@ -237,8 +236,7 @@ namespace NadekoBot.Modules.CustomReactions
if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null)
{
uow.CustomReactions.Remove(toDelete);
//todo 91 i can dramatically improve performance of this, if Ids are ordered.
_service.GlobalReactions = _service.GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray();
await _service.DelGcr(toDelete.Id);
success = true;
}
else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId)

View File

@ -15,6 +15,7 @@ using NadekoBot.Modules.CustomReactions.Extensions;
using NadekoBot.Modules.Permissions.Common;
using NadekoBot.Modules.Permissions.Services;
using NadekoBot.Services.Impl;
using Newtonsoft.Json;
namespace NadekoBot.Modules.CustomReactions.Services
{
@ -32,9 +33,11 @@ namespace NadekoBot.Modules.CustomReactions.Services
private readonly CommandHandler _cmd;
private readonly IBotConfigProvider _bc;
private readonly NadekoStrings _strings;
private readonly IDataCache _cache;
public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings,
DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow)
DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow,
IDataCache cache)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
@ -43,12 +46,38 @@ namespace NadekoBot.Modules.CustomReactions.Services
_cmd = cmd;
_bc = bc;
_strings = strings;
_cache = cache;
var sub = _cache.Redis.GetSubscriber();
sub.Subscribe("gcr.added", (ch, msg) =>
{
Array.Resize(ref GlobalReactions, GlobalReactions.Length + 1);
GlobalReactions[GlobalReactions.Length - 1] = JsonConvert.DeserializeObject<CustomReaction>(msg);
}, StackExchange.Redis.CommandFlags.FireAndForget);
sub.Subscribe("gcr.deleted", (ch, msg) =>
{
var id = int.Parse(msg);
GlobalReactions = GlobalReactions.Where(cr => cr?.Id != id).ToArray();
}, StackExchange.Redis.CommandFlags.FireAndForget);
var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
}
public Task AddGcr(CustomReaction cr)
{
var sub = _cache.Redis.GetSubscriber();
return sub.PublishAsync("gcr.added", JsonConvert.SerializeObject(cr));
}
public Task DelGcr(int id)
{
var sub = _cache.Redis.GetSubscriber();
return sub.PublishAsync("gcr.deleted", id);
}
public void ClearStats() => ReactionStats.Clear();
public CustomReaction TryGetCustomReaction(IUserMessage umsg)

View File

@ -331,7 +331,7 @@ namespace NadekoBot.Modules.Gambling
var embed = new EmbedBuilder().WithOkColor();
if (entry.Type == ShopEntryType.Role)
return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(entry.RoleName))).WithIsInline(true))
return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true));
else if (entry.Type == ShopEntryType.List)
@ -349,7 +349,7 @@ namespace NadekoBot.Modules.Gambling
{
if (entry.Type == ShopEntryType.Role)
{
return GetText("shop_role", Format.Bold(entry.RoleName));
return GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"));
}
else if (entry.Type == ShopEntryType.List)
{

View File

@ -3,7 +3,6 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Music.Common
{
@ -18,27 +17,16 @@ namespace NadekoBot.Modules.Music.Common
public string SongUri { get; private set; }
//private volatile bool restart = false;
public SongBuffer(string songUri, string skipTo, bool isLocal)
{
_log = LogManager.GetCurrentClassLogger();
//_log.Warn(songUri);
this.SongUri = songUri;
this._isLocal = isLocal;
try
{
this.p = StartFFmpegProcess(SongUri, 0);
var t = Task.Run(() =>
{
this.p.BeginErrorReadLine();
this.p.ErrorDataReceived += P_ErrorDataReceived;
this.p.WaitForExit();
});
this._outStream = this.p.StandardOutput.BaseStream;
}
catch (System.ComponentModel.Win32Exception)
{
@ -68,113 +56,14 @@ Check the guides for your platform on how to setup ffmpeg correctly:
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardError = false,
CreateNoWindow = true,
});
}
private void P_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (string.IsNullOrWhiteSpace(e.Data))
return;
_log.Error(">>> " + e.Data);
if (e.Data?.Contains("Error in the pull function") == true)
{
_log.Error("Ignore this.");
//restart = true;
}
}
private readonly object locker = new object();
private readonly bool _isLocal;
public Task<bool> StartBuffering(CancellationToken cancelToken)
{
var toReturn = new TaskCompletionSource<bool>();
var _ = Task.Run(() =>
{
try
{
////int maxLoopsPerSec = 25;
//var sw = Stopwatch.StartNew();
////var delay = 1000 / maxLoopsPerSec;
//int currentLoops = 0;
//int _bytesSent = 0;
//try
//{
// //do
// //{
// // if (restart)
// // {
// // var cur = _bytesSent / 3840 / (1000 / 20.0f);
// // _log.Info("Restarting");
// // try { this.p.StandardOutput.Dispose(); } catch { }
// // try { this.p.Dispose(); } catch { }
// // this.p = StartFFmpegProcess(SongUri, cur);
// // }
// // restart = false;
// ++currentLoops;
// byte[] buffer = new byte[readSize];
// int bytesRead = 1;
// while (!cancelToken.IsCancellationRequested && !this.p.HasExited)
// {
// bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, readSize, cancelToken).ConfigureAwait(false);
// _bytesSent += bytesRead;
// if (bytesRead == 0)
// break;
// bool written;
// do
// {
// lock (locker)
// written = _outStream.Write(buffer, 0, bytesRead);
// if (!written)
// await Task.Delay(2000, cancelToken);
// }
// while (!written && !cancelToken.IsCancellationRequested);
// lock (locker)
// if (_outStream.Length > 200_000 || bytesRead == 0)
// if (toReturn.TrySetResult(true))
// _log.Info("Prebuffering finished in {0}", sw.Elapsed.TotalSeconds.ToString("F2"));
// //_log.Info(_outStream.Length);
// await Task.Delay(10);
// }
// //if (cancelToken.IsCancellationRequested)
// // _log.Info("Song canceled");
// //else if (p.HasExited)
// // _log.Info("Song buffered completely (FFmpeg exited)");
// //else if (bytesRead == 0)
// // _log.Info("Nothing read");
// //}
// //while (restart && !cancelToken.IsCancellationRequested);
//return Task.CompletedTask;
toReturn.TrySetResult(true);
}
catch (System.ComponentModel.Win32Exception)
{
_log.Error(@"You have not properly installed or configured FFMPEG.
Please install and configure FFMPEG to play music.
Check the guides for your platform on how to setup ffmpeg correctly:
Windows Guide: https://goo.gl/OjKk8F
Linux Guide: https://goo.gl/ShjCUo");
}
catch (OperationCanceledException) { }
catch (InvalidOperationException) { } // when ffmpeg is disposed
catch (Exception ex)
{
_log.Info(ex);
}
finally
{
if (toReturn.TrySetResult(false))
_log.Info("Prebuffering failed");
}
}, cancelToken);
return toReturn.Task;
}
public int Read(byte[] b, int offset, int toRead)
{
lock (locker)
@ -203,215 +92,4 @@ Check the guides for your platform on how to setup ffmpeg correctly:
this.p.Dispose();
}
}
}
//namespace NadekoBot.Services.Music
//{
// /// <summary>
// /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space.
// /// It also help for large music by deleting files that are already seen.
// /// </summary>
// class SongBuffer : Stream
// {
// public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize)
// {
// MusicPlayer = musicPlayer;
// Basename = basename;
// SongInfo = songInfo;
// SkipTo = skipTo;
// MaxFileSize = maxFileSize;
// CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
// _log = LogManager.GetCurrentClassLogger();
// }
// MusicPlayer MusicPlayer { get; }
// private string Basename { get; }
// private SongInfo SongInfo { get; }
// private int SkipTo { get; }
// private int MaxFileSize { get; } = 2.MiB();
// private long FileNumber = -1;
// private long NextFileToRead = 0;
// public bool BufferingCompleted { get; private set; } = false;
// private ulong CurrentBufferSize = 0;
// private FileStream CurrentFileStream;
// private Logger _log;
// public Task BufferSong(CancellationToken cancelToken) =>
// Task.Run(async () =>
// {
// Process p = null;
// FileStream outStream = null;
// try
// {
// p = Process.Start(new ProcessStartInfo
// {
// FileName = "ffmpeg",
// Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet",
// UseShellExecute = false,
// RedirectStandardOutput = true,
// RedirectStandardError = false,
// CreateNoWindow = true,
// });
// byte[] buffer = new byte[81920];
// int currentFileSize = 0;
// ulong prebufferSize = 100ul.MiB();
// outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
// while (!p.HasExited) //Also fix low bandwidth
// {
// int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false);
// if (currentFileSize >= MaxFileSize)
// {
// try
// {
// outStream.Dispose();
// }
// catch { }
// outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
// currentFileSize = bytesRead;
// }
// else
// {
// currentFileSize += bytesRead;
// }
// CurrentBufferSize += Convert.ToUInt64(bytesRead);
// await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
// while (CurrentBufferSize > prebufferSize)
// await Task.Delay(100, cancelToken);
// }
// BufferingCompleted = true;
// }
// catch (System.ComponentModel.Win32Exception)
// {
// var oldclr = Console.ForegroundColor;
// Console.ForegroundColor = ConsoleColor.Red;
// Console.WriteLine(@"You have not properly installed or configured FFMPEG.
//Please install and configure FFMPEG to play music.
//Check the guides for your platform on how to setup ffmpeg correctly:
// Windows Guide: https://goo.gl/OjKk8F
// Linux Guide: https://goo.gl/ShjCUo");
// Console.ForegroundColor = oldclr;
// }
// catch (Exception ex)
// {
// Console.WriteLine($"Buffering stopped: {ex.Message}");
// }
// finally
// {
// if (outStream != null)
// outStream.Dispose();
// if (p != null)
// {
// try
// {
// p.Kill();
// }
// catch { }
// p.Dispose();
// }
// }
// });
// /// <summary>
// /// Return the next file to read, and delete the old one
// /// </summary>
// /// <returns>Name of the file to read</returns>
// private string GetNextFile()
// {
// string filename = Basename + "-" + NextFileToRead;
// if (NextFileToRead != 0)
// {
// try
// {
// CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length);
// File.Delete(Basename + "-" + (NextFileToRead - 1));
// }
// catch { }
// }
// NextFileToRead++;
// return filename;
// }
// private bool IsNextFileReady()
// {
// return NextFileToRead <= FileNumber;
// }
// private void CleanFiles()
// {
// for (long i = NextFileToRead - 1; i <= FileNumber; i++)
// {
// try
// {
// File.Delete(Basename + "-" + i);
// }
// catch { }
// }
// }
// //Stream part
// public override bool CanRead => true;
// public override bool CanSeek => false;
// public override bool CanWrite => false;
// public override long Length => (long)CurrentBufferSize;
// public override long Position { get; set; } = 0;
// public override void Flush() { }
// public override int Read(byte[] buffer, int offset, int count)
// {
// int read = CurrentFileStream.Read(buffer, offset, count);
// if (read < count)
// {
// if (!BufferingCompleted || IsNextFileReady())
// {
// CurrentFileStream.Dispose();
// CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
// read += CurrentFileStream.Read(buffer, read + offset, count - read);
// }
// if (read < count)
// Array.Clear(buffer, read, count - read);
// }
// return read;
// }
// public override long Seek(long offset, SeekOrigin origin)
// {
// throw new NotImplementedException();
// }
// public override void SetLength(long value)
// {
// throw new NotImplementedException();
// }
// public override void Write(byte[] buffer, int offset, int count)
// {
// throw new NotImplementedException();
// }
// public new void Dispose()
// {
// CurrentFileStream.Dispose();
// MusicPlayer.SongCancelSource.Cancel();
// CleanFiles();
// base.Dispose();
// }
// }
//}
}

View File

@ -16,9 +16,13 @@ using NadekoBot.Modules.NSFW.Exceptions;
namespace NadekoBot.Modules.NSFW
{
// thanks to halitalf for adding autoboob and autobutt features :D
public class NSFW : NadekoTopLevelModule<SearchesService>
{
private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentDictionary<ulong, Timer> _autoBoobTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentDictionary<ulong, Timer> _autoButtTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>();
private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
@ -49,10 +53,34 @@ namespace NadekoBot.Modules.NSFW
.WithDescription($"[{GetText("tag")}: {tag}]({img})"))
.ConfigureAwait(false);
}
private async Task InternalBoobs(IMessageChannel Channel)
{
try
{
JToken obj;
obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}").ConfigureAwait(false))[0];
await Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}").ConfigureAwait(false);
}
catch (Exception ex)
{
await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
}
}
private async Task InternalButts(IMessageChannel Channel)
{
try
{
JToken obj;
obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}").ConfigureAwait(false))[0];
await Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}").ConfigureAwait(false);
}
catch (Exception ex)
{
await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public Task Hentai([Remainder] string tag = null) =>
InternalHentai(Context.Channel, tag, false);
#if !GLOBAL_NADEKO
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(ChannelPermission.ManageMessages)]
@ -65,7 +93,7 @@ namespace NadekoBot.Modules.NSFW
if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false);
await ReplyConfirmLocalized("stopped").ConfigureAwait(false);
return;
}
@ -99,8 +127,90 @@ namespace NadekoBot.Modules.NSFW
interval,
string.Join(", ", tagsArr)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(ChannelPermission.ManageMessages)]
public async Task AutoBoobs(int interval = 0)
{
Timer t;
if (interval == 0)
{
if (!_autoBoobTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("stopped").ConfigureAwait(false);
return;
}
if (interval < 20)
return;
t = new Timer(async (state) =>
{
try
{
await InternalBoobs(Context.Channel).ConfigureAwait(false);
}
catch
{
// ignored
}
}, null, interval * 1000, interval * 1000);
_autoBoobTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return t;
});
await ReplyConfirmLocalized("started", interval).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(ChannelPermission.ManageMessages)]
public async Task AutoButts(int interval = 0)
{
Timer t;
if (interval == 0)
{
if (!_autoButtTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("stopped").ConfigureAwait(false);
return;
}
if (interval < 20)
return;
t = new Timer(async (state) =>
{
try
{
await InternalButts(Context.Channel).ConfigureAwait(false);
}
catch
{
// ignored
}
}, null, interval * 1000, interval * 1000);
_autoButtTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return t;
});
await ReplyConfirmLocalized("started", interval).ConfigureAwait(false);
}
#endif
[NadekoCommand, Usage, Description, Aliases]
public Task Hentai([Remainder] string tag = null) =>
InternalHentai(Context.Channel, tag, false);
[NadekoCommand, Usage, Description, Aliases]
public async Task HentaiBomb([Remainder] string tag = null)
{
@ -199,7 +309,7 @@ namespace NadekoBot.Modules.NSFW
tag = tag.Trim().ToLowerInvariant();
var added = _service.ToggleBlacklistedTag(Context.Guild.Id, tag);
if(added)
if (added)
await ReplyConfirmLocalized("blacklisted_tag_add", tag).ConfigureAwait(false);
else
await ReplyConfirmLocalized("blacklisted_tag_remove", tag).ConfigureAwait(false);

View File

@ -76,11 +76,13 @@ namespace NadekoBot.Modules.Searches.Common
if (images.Length == 0)
return null;
var toReturn = images[_rng.Next(images.Length)];
#if !GLOBAL_NADEKO
foreach (var dledImg in images)
{
if(dledImg != toReturn)
_cache.Add(dledImg);
}
#endif
return toReturn;
}
}

View File

@ -43,20 +43,16 @@ namespace NadekoBot.Modules.Searches
string response;
response = await _service.Http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?q={query}&appid=42cd627dd60debf25a5739e50a217d74&units=metric").ConfigureAwait(false);
string responsef;
responsef = await _service.Http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?q={query}&appid=42cd627dd60debf25a5739e50a217d74&units=imperial").ConfigureAwait(false);
var data = JsonConvert.DeserializeObject<WeatherData>(response);
var dataf = JsonConvert.DeserializeObject<WeatherData>(responsef);
var embed = new EmbedBuilder()
.AddField(fb => fb.WithName("🌍 " + Format.Bold(GetText("location"))).WithValue($"[{data.Name + ", " + data.Sys.Country}](https://openweathermap.org/city/{data.Id})").WithIsInline(true))
.AddField(fb => fb.WithName("📏 " + Format.Bold(GetText("latlong"))).WithValue($"{data.Coord.Lat}, {data.Coord.Lon}").WithIsInline(true))
.AddField(fb => fb.WithName("☁ " + Format.Bold(GetText("condition"))).WithValue(string.Join(", ", data.Weather.Select(w => w.Main))).WithIsInline(true))
.AddField(fb => fb.WithName("😓 " + Format.Bold(GetText("humidity"))).WithValue($"{data.Main.Humidity}%").WithIsInline(true))
.AddField(fb => fb.WithName("💨 " + Format.Bold(GetText("wind_speed"))).WithValue(data.Wind.Speed + " m/s" + " / " + dataf.Wind.Speed + " mph").WithIsInline(true))
.AddField(fb => fb.WithName("🌡 " + Format.Bold(GetText("temperature"))).WithValue(data.Main.Temp + "°C" + " / " + dataf.Main.Temp + "°F").WithIsInline(true))
.AddField(fb => fb.WithName("🔆 " + Format.Bold(GetText("min_max"))).WithValue($"{data.Main.TempMin}°C - {data.Main.TempMax}°C" + "\n" + $"{dataf.Main.TempMin}°F - {dataf.Main.TempMax}°F").WithIsInline(true))
.AddField(fb => fb.WithName("💨 " + Format.Bold(GetText("wind_speed"))).WithValue(data.Wind.Speed + " m/s").WithIsInline(true))
.AddField(fb => fb.WithName("🌡 " + Format.Bold(GetText("temperature"))).WithValue(data.Main.Temp + "°C").WithIsInline(true))
.AddField(fb => fb.WithName("🔆 " + Format.Bold(GetText("min_max"))).WithValue($"{data.Main.TempMin}°C - {data.Main.TempMax}°C").WithIsInline(true))
.AddField(fb => fb.WithName("🌄 " + Format.Bold(GetText("sunrise"))).WithValue($"{data.Sys.Sunrise.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true))
.AddField(fb => fb.WithName("🌇 " + Format.Bold(GetText("sunset"))).WithValue($"{data.Sys.Sunset.ToUnixTimestamp():HH:mm} UTC").WithIsInline(true))
.WithOkColor()

View File

@ -11,10 +11,14 @@ namespace NadekoBot.Modules.Searches.Services
public class AnimeSearchService : INService
{
private readonly Logger _log;
private readonly IDataCache _cache;
private readonly HttpClient _http;
public AnimeSearchService()
public AnimeSearchService(IDataCache cache)
{
_log = LogManager.GetCurrentClassLogger();
_cache = cache;
_http = new HttpClient();
}
public async Task<AnimeResult> GetAnimeData(string query)
@ -25,11 +29,16 @@ namespace NadekoBot.Modules.Searches.Services
{
var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " "));
using (var http = new HttpClient())
link = link.ToLowerInvariant();
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
if (!ok)
{
var res = await http.GetStringAsync(link).ConfigureAwait(false);
return JsonConvert.DeserializeObject<AnimeResult>(res);
data = await _http.GetStringAsync(link).ConfigureAwait(false);
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
}
return JsonConvert.DeserializeObject<AnimeResult>(data);
}
catch
{
@ -44,12 +53,17 @@ namespace NadekoBot.Modules.Searches.Services
try
{
var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " "));
using (var http = new HttpClient())
var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " "));
link = link.ToLowerInvariant();
var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false);
if (!ok)
{
var res = await http.GetStringAsync(link).ConfigureAwait(false);
return JsonConvert.DeserializeObject<MangaResult>(res);
data = await _http.GetStringAsync(link).ConfigureAwait(false);
await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false);
}
return JsonConvert.DeserializeObject<MangaResult>(data);
}
catch
{

View File

@ -26,6 +26,27 @@ namespace NadekoBot.Modules.Xp
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubAdmin([Remainder]IUser toAdmin)
{
bool admin;
try
{
admin = _service.ToggleAdmin(Context.User, toAdmin);
}
catch (InvalidOperationException)
{
await ReplyErrorLocalized("club_admin_error").ConfigureAwait(false);
return;
}
if(admin)
await ReplyConfirmLocalized("club_admin_add", Format.Bold(toAdmin.ToString())).ConfigureAwait(false);
else
await ReplyConfirmLocalized("club_admin_remove", Format.Bold(toAdmin.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubCreate([Remainder]string clubName)
{
@ -97,7 +118,14 @@ namespace NadekoBot.Modules.Xp
.AddField("Level Req.", club.MinimumLevelReq.ToString(), true)
.AddField("Members", string.Join("\n", club.Users
.Skip(page * 10)
.Take(10)), false);
.Take(10)
.OrderByDescending(x => x.IsClubAdmin)
.Select(x =>
{
if (x.IsClubAdmin)
return x.ToString() + "⭐";
return x.ToString();
})), false);
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
return embed.WithThumbnailUrl(club.ImageUrl);
@ -112,7 +140,7 @@ namespace NadekoBot.Modules.Xp
if (--page < 0)
return Task.CompletedTask;
var club = _service.GetBansAndApplications(Context.User.Id);
var club = _service.GetClubWithBansAndApplications(Context.User.Id);
if (club == null)
return ReplyErrorLocalized("club_not_exists");
@ -131,7 +159,8 @@ namespace NadekoBot.Modules.Xp
return new EmbedBuilder()
.WithTitle(GetText("club_bans_for", club.ToString()))
.WithDescription(toShow);
.WithDescription(toShow)
.WithOkColor();
}, bans.Length / 10);
}
@ -143,11 +172,11 @@ namespace NadekoBot.Modules.Xp
if (--page < 0)
return Task.CompletedTask;
var club = _service.GetBansAndApplications(Context.User.Id);
var club = _service.GetClubWithBansAndApplications(Context.User.Id);
if (club == null)
return ReplyErrorLocalized("club_not_exists");
var bans = club
var apps = club
.Applicants
.Select(x => x.User)
.ToArray();
@ -155,16 +184,17 @@ namespace NadekoBot.Modules.Xp
return Context.Channel.SendPaginatedConfirmAsync(_client, page,
curPage =>
{
var toShow = string.Join("\n", bans
var toShow = string.Join("\n", apps
.Skip(page * 10)
.Take(10)
.Select(x => x.ToString()));
return new EmbedBuilder()
.WithTitle(GetText("club_apps_for", club.ToString()))
.WithDescription(toShow);
.WithDescription(toShow)
.WithOkColor();
}, bans.Length / 10);
}, apps.Length / 10);
}
[NadekoCommand, Usage, Description, Aliases]
@ -282,7 +312,7 @@ namespace NadekoBot.Modules.Xp
}
else
{
await ReplyErrorLocalized("club_disaband_error").ConfigureAwait(false);
await ReplyErrorLocalized("club_disband_error").ConfigureAwait(false);
}
}

View File

@ -27,10 +27,11 @@ namespace NadekoBot.Modules.Xp.Services
{
var du = uow.DiscordUsers.GetOrCreate(user);
uow._context.SaveChanges();
var xp = new LevelStats(uow.Xp.GetTotalUserXp(user.Id));
var xp = new LevelStats(du.TotalXp);
if (xp.Level >= 5 && du.Club == null)
{
du.IsClubAdmin = true;
du.Club = new ClubInfo()
{
Name = clubName,
@ -52,6 +53,27 @@ namespace NadekoBot.Modules.Xp.Services
return true;
}
public bool ToggleAdmin(IUser owner, IUser toAdmin)
{
bool newState;
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(owner.Id);
var adminUser = uow.DiscordUsers.GetOrCreate(toAdmin);
if (club.OwnerId == adminUser.Id)
return true;
if (club == null || club.Owner.UserId != owner.Id ||
!club.Users.Contains(adminUser))
throw new InvalidOperationException();
newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
uow.Complete();
}
return newState;
}
public ClubInfo GetClubByMember(IUser user)
{
using (var uow = _db.UnitOfWork)
@ -107,7 +129,7 @@ namespace NadekoBot.Modules.Xp.Services
uow._context.SaveChanges();
if (du.Club != null
|| new LevelStats(uow.Xp.GetTotalUserXp(user.Id)).Level < club.MinimumLevelReq
|| new LevelStats(du.TotalXp).Level < club.MinimumLevelReq
|| club.Bans.Any(x => x.UserId == du.Id)
|| club.Applicants.Any(x => x.UserId == du.Id))
{
@ -134,11 +156,7 @@ namespace NadekoBot.Modules.Xp.Services
discordUser = null;
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(clubOwnerUserId,
set => set.Include(x => x.Applicants)
.ThenInclude(x => x.Club)
.Include(x => x.Applicants)
.ThenInclude(x => x.User));
var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId);
if (club == null)
return false;
@ -147,6 +165,7 @@ namespace NadekoBot.Modules.Xp.Services
return false;
applicant.User.Club = club;
applicant.User.IsClubAdmin = false;
club.Applicants.Remove(applicant);
//remove that user's all other applications
@ -159,15 +178,11 @@ namespace NadekoBot.Modules.Xp.Services
return true;
}
public ClubInfo GetBansAndApplications(ulong ownerUserId)
public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId)
{
using (var uow = _db.UnitOfWork)
{
return uow.Clubs.GetByOwner(ownerUserId,
x => x.Include(y => y.Bans)
.ThenInclude(y => y.User)
.Include(y => y.Applicants)
.ThenInclude(y => y.User));
return uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
}
}
@ -180,6 +195,7 @@ namespace NadekoBot.Modules.Xp.Services
return false;
du.Club = null;
du.IsClubAdmin = false;
uow.Complete();
}
return true;
@ -221,9 +237,7 @@ namespace NadekoBot.Modules.Xp.Services
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(ownerUserId,
set => set.Include(x => x.Applicants)
.ThenInclude(x => x.User));
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
if (club == null)
return false;
@ -256,9 +270,7 @@ namespace NadekoBot.Modules.Xp.Services
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(ownerUserId,
set => set.Include(x => x.Bans)
.ThenInclude(x => x.User));
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
if (club == null)
return false;
@ -277,7 +289,7 @@ namespace NadekoBot.Modules.Xp.Services
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(ownerUserId);
club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId);
if (club == null)
return false;

View File

@ -36,6 +36,7 @@ namespace NadekoBot.Modules.Xp.Services
private readonly IImagesService _images;
private readonly Logger _log;
private readonly NadekoStrings _strings;
private readonly IDataCache _cache;
private readonly FontCollection _fonts = new FontCollection();
public const int XP_REQUIRED_LVL_1 = 36;
@ -54,9 +55,6 @@ namespace NadekoBot.Modules.Xp.Services
private readonly ConcurrentQueue<UserCacheItem> _addMessageXp
= new ConcurrentQueue<UserCacheItem>();
private readonly ConcurrentDictionary<string, byte[]> _imageStreams
= new ConcurrentDictionary<string, byte[]>();
private readonly Timer updateXpTimer;
private readonly HttpClient http = new HttpClient();
private FontFamily _usernameFontFamily;
@ -69,7 +67,7 @@ namespace NadekoBot.Modules.Xp.Services
public XpService(CommandHandler cmd, IBotConfigProvider bc,
IEnumerable<GuildConfig> allGuildConfigs, IImagesService images,
DbService db, NadekoStrings strings)
DbService db, NadekoStrings strings, IDataCache cache)
{
_db = db;
_cmd = cmd;
@ -77,6 +75,7 @@ namespace NadekoBot.Modules.Xp.Services
_images = images;
_log = LogManager.GetCurrentClassLogger();
_strings = strings;
_cache = cache;
//load settings
allGuildConfigs = allGuildConfigs.Where(x => x.XpSettings != null);
@ -145,12 +144,13 @@ namespace NadekoBot.Modules.Xp.Services
du.LastXpGain = DateTime.UtcNow;
var globalXp = uow.Xp.GetTotalUserXp(item.Key.User.Id);
var globalXp = du.TotalXp;
var oldGlobalLevelData = new LevelStats(globalXp);
var newGlobalLevelData = new LevelStats(globalXp + xp);
var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
usr.Xp += xp;
du.TotalXp += xp;
if (du.Club != null)
du.Club.Xp += xp;
var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
@ -308,11 +308,11 @@ namespace NadekoBot.Modules.Xp.Services
}
}
public (ulong UserId, int TotalXp)[] GetUserXps(int page)
public DiscordUser[] GetUserXps(int page)
{
using (var uow = _db.UnitOfWork)
{
return uow.Xp.GetUsersFor(page);
return uow.DiscordUsers.GetUsersXpLeaderboardFor(page);
}
}
@ -403,17 +403,6 @@ namespace NadekoBot.Modules.Xp.Services
return _rewardedUsers.Add(userId);
}
public LevelStats GetGlobalUserStats(ulong userId)
{
int totalXp;
using (var uow = _db.UnitOfWork)
{
totalXp = uow.Xp.GetTotalUserXp(userId);
}
return new LevelStats(totalXp);
}
public FullUserStats GetUserStats(IGuildUser user)
{
DiscordUser du;
@ -425,8 +414,8 @@ namespace NadekoBot.Modules.Xp.Services
{
du = uow.DiscordUsers.GetOrCreate(user);
stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id);
totalXp = uow.Xp.GetTotalUserXp(user.Id);
globalRank = uow.Xp.GetUserGlobalRanking(user.Id);
totalXp = du.TotalXp;
globalRank = uow.DiscordUsers.GetUserGlobalRanking(user.Id);
guildRank = uow.Xp.GetUserGuildRanking(user.Id, user.GuildId);
}
@ -550,7 +539,7 @@ namespace NadekoBot.Modules.Xp.Services
}
}
public Task<Image<Rgba32>> GenerateImageAsync(IGuildUser user)
public Task<MemoryStream> GenerateImageAsync(IGuildUser user)
{
return GenerateImageAsync(GetUserStats(user));
}
@ -566,170 +555,166 @@ namespace NadekoBot.Modules.Xp.Services
_timeFont = _fonts.Find("Whitney-Bold").CreateFont(20);
}
public Task<Image<Rgba32>> GenerateImageAsync(FullUserStats stats) => Task.Run(async () =>
public Task<MemoryStream> GenerateImageAsync(FullUserStats stats) => Task.Run(async () =>
{
var img = Image.Load(_images.XpCard.ToArray());
var username = stats.User.ToString();
var usernameFont = _usernameFontFamily
.CreateFont(username.Length <= 6
? 50
: 50 - username.Length);
img.DrawText("@" + username, usernameFont, Rgba32.White,
new PointF(130, 5));
// level
img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 137));
img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 285));
//club name
var clubName = stats.User.Club?.ToString() ?? "-";
var clubFont = _clubFontFamily
.CreateFont(clubName.Length <= 8
? 35
: 35 - (clubName.Length / 2));
img.DrawText(clubName, clubFont, Rgba32.White,
new PointF(650 - clubName.Length * 10, 40));
var pen = new Pen<Rgba32>(Rgba32.Black, 1);
var brush = Brushes.Solid<Rgba32>(Rgba32.White);
var xpBgBrush = Brushes.Solid<Rgba32>(new Rgba32(0, 0, 0, 0.4f));
var global = stats.Global;
var guild = stats.Guild;
//xp bar
img.FillPolygon(xpBgBrush, new[] {
new PointF(321, 104),
new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104),
new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235),
new PointF(286, 235),
});
img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen,
new PointF(430, 130));
img.FillPolygon(xpBgBrush, new[] {
new PointF(282, 248),
new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248),
new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379),
new PointF(247, 379),
});
img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen,
new PointF(400, 270));
if (stats.FullGuildStats.AwardedXp != 0)
using (var img = Image.Load(_images.XpCard.ToArray()))
{
var sign = stats.FullGuildStats.AwardedXp > 0
? "+ "
: "";
img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen,
new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335));
}
//ranking
var username = stats.User.ToString();
var usernameFont = _usernameFontFamily
.CreateFont(username.Length <= 6
? 50
: 50 - username.Length);
img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 170));
img.DrawText("@" + username, usernameFont, Rgba32.White,
new PointF(130, 5));
img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 317));
// level
//time on this level
img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 137));
string GetTimeSpent(DateTime time)
{
var offset = DateTime.UtcNow - time;
return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m";
}
img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 285));
img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 197));
//club name
img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 344));
var clubName = stats.User.Club?.ToString() ?? "-";
//avatar
var clubFont = _clubFontFamily
.CreateFont(clubName.Length <= 8
? 35
: 35 - (clubName.Length / 2));
if (stats.User.AvatarId != null)
{
try
img.DrawText(clubName, clubFont, Rgba32.White,
new PointF(650 - clubName.Length * 10, 40));
var pen = new Pen<Rgba32>(Rgba32.Black, 1);
var brush = Brushes.Solid<Rgba32>(Rgba32.White);
var xpBgBrush = Brushes.Solid<Rgba32>(new Rgba32(0, 0, 0, 0.4f));
var global = stats.Global;
var guild = stats.Guild;
//xp bar
img.FillPolygon(xpBgBrush, new[] {
new PointF(321, 104),
new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104),
new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235),
new PointF(286, 235),
});
img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen,
new PointF(430, 130));
img.FillPolygon(xpBgBrush, new[] {
new PointF(282, 248),
new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248),
new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379),
new PointF(247, 379),
});
img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen,
new PointF(400, 270));
if (stats.FullGuildStats.AwardedXp != 0)
{
var avatarUrl = stats.User.RealAvatarUrl();
var sign = stats.FullGuildStats.AwardedXp > 0
? "+ "
: "";
img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen,
new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335));
}
byte[] s;
if (!_imageStreams.TryGetValue(avatarUrl, out s))
//ranking
img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 170));
img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 317));
//time on this level
string GetTimeSpent(DateTime time)
{
var offset = DateTime.UtcNow - time;
return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m";
}
img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 197));
img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 344));
//avatar
if (stats.User.AvatarId != null)
{
try
{
using (var temp = await http.GetStreamAsync(avatarUrl))
var avatarUrl = stats.User.RealAvatarUrl();
var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl);
if (!succ)
{
var tempDraw = Image.Load(temp);
tempDraw = tempDraw.Resize(69, 70);
ApplyRoundedCorners(tempDraw, 35);
s = tempDraw.ToStream().ToArray();
using (var temp = await http.GetStreamAsync(avatarUrl))
using (var tempDraw = Image.Load(temp).Resize(69, 70))
{
ApplyRoundedCorners(tempDraw, 35);
data = tempDraw.ToStream().ToArray();
}
await _cache.SetImageDataAsync(avatarUrl, data);
}
var toDraw = Image.Load(data);
_imageStreams.AddOrUpdate(avatarUrl, s, (k, v) => s);
img.DrawImage(toDraw,
1,
new Size(69, 70),
new Point(32, 10));
}
var toDraw = Image.Load(s);
img.DrawImage(toDraw,
1,
new Size(69, 70),
new Point(32, 10));
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
//club image
if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl))
{
var imgUrl = stats.User.Club.ImageUrl;
try
{
byte[] s;
if (!_imageStreams.TryGetValue(imgUrl, out s))
catch (Exception ex)
{
using (var temp = await http.GetStreamAsync(imgUrl))
{
var tempDraw = Image.Load(temp);
tempDraw = tempDraw.Resize(45, 45);
ApplyRoundedCorners(tempDraw, 22.5f);
s = tempDraw.ToStream().ToArray();
}
_imageStreams.AddOrUpdate(imgUrl, s, (k, v) => s);
_log.Warn(ex);
}
var toDraw = Image.Load(s);
img.DrawImage(toDraw,
1,
new Size(45, 45),
new Point(722, 25));
}
catch (Exception ex)
//club image
if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl))
{
_log.Warn(ex);
var imgUrl = stats.User.Club.ImageUrl;
try
{
var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl);
if (!succ)
{
using (var temp = await http.GetStreamAsync(imgUrl))
using (var tempDraw = Image.Load(temp).Resize(45, 45))
{
ApplyRoundedCorners(tempDraw, 22.5f);
data = tempDraw.ToStream().ToArray();
}
await _cache.SetImageDataAsync(imgUrl, data);
}
var toDraw = Image.Load(data);
img.DrawImage(toDraw,
1,
new Size(45, 45),
new Point(722, 25));
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
return img.Resize(432, 211).ToStream();
}
var arr = img.ToStream().ToArray();
//_log.Info("{0:F2} KB", arr.Length * 1.0f / 1.KB());
return img;
});

View File

@ -1,10 +1,12 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common;
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System.Diagnostics;
using System.Linq;
@ -15,23 +17,55 @@ namespace NadekoBot.Modules.Xp
public partial class Xp : NadekoTopLevelModule<XpService>
{
private readonly DiscordSocketClient _client;
private readonly DbService _db;
public Xp(DiscordSocketClient client)
public Xp(DiscordSocketClient client,DbService db)
{
_client = client;
_db = db;
}
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//[OwnerOnly]
//public async Task Populate()
//{
// var rng = new NadekoRandom();
// using (var uow = _db.UnitOfWork)
// {
// for (var i = 0ul; i < 1000000; i++)
// {
// uow.DiscordUsers.Add(new DiscordUser()
// {
// AvatarId = i.ToString(),
// Discriminator = "1234",
// UserId = i,
// Username = i.ToString(),
// Club = null,
// });
// var xp = uow.Xp.GetOrCreateUser(Context.Guild.Id, i);
// xp.Xp = rng.Next(100, 100000);
// }
// uow.Complete();
// }
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
//[Ratelimit(30)]
public async Task Experience([Remainder]IUser user = null)
{
user = user ?? Context.User;
var sw = Stopwatch.StartNew();
await Context.Channel.TriggerTypingAsync();
var img = await _service.GenerateImageAsync((IGuildUser)user);
await Context.Channel.SendFileAsync(img.ToStream(), $"{user.Id}_xp.png")
sw.Stop();
_log.Info("Generating finished in {0:F2}s", sw.Elapsed.TotalSeconds);
sw.Restart();
await Context.Channel.SendFileAsync(img, $"{user.Id}_xp.png")
.ConfigureAwait(false);
sw.Stop();
_log.Info("Sending finished in {0:F2}s", sw.Elapsed.TotalSeconds);
}
[NadekoCommand, Usage, Description, Aliases]
@ -40,7 +74,7 @@ namespace NadekoBot.Modules.Xp
{
page--;
if (page < 0)
if (page < 0 || page > 100)
return Task.CompletedTask;
var roles = _service.GetRoleRewards(Context.Guild.Id)
@ -169,7 +203,7 @@ namespace NadekoBot.Modules.Xp
[RequireContext(ContextType.Guild)]
public Task XpLeaderboard(int page = 1)
{
if (--page < 0)
if (--page < 0 || page > 100)
return Task.CompletedTask;
return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) =>
@ -210,32 +244,28 @@ namespace NadekoBot.Modules.Xp
[RequireContext(ContextType.Guild)]
public async Task XpGlobalLeaderboard(int page = 1)
{
if (--page < 0)
if (--page < 0 || page > 100)
return;
var users = _service.GetUserXps(page);
await Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) =>
var embed = new EmbedBuilder()
.WithTitle(GetText("global_leaderboard"))
.WithOkColor();
if (!users.Any())
embed.WithDescription("-");
else
{
var users = _service.GetUserXps(curPage);
var embed = new EmbedBuilder()
.WithTitle(GetText("global_leaderboard"))
.WithOkColor();
if (!users.Any())
return embed.WithDescription("-");
else
for (int i = 0; i < users.Length; i++)
{
for (int i = 0; i < users.Length; i++)
{
var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false);
embed.AddField(
$"#{(i + 1 + curPage * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}",
$"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp");
}
return embed;
var user = users[i];
embed.AddField(
$"#{(i + 1 + page * 9)} {(user.ToString())}",
$"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp");
}
}, addPaginatedFooter: false);
}
await Context.Channel.EmbedAsync(embed);
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -20,6 +20,8 @@ using NadekoBot.Common.ShardCom;
using NadekoBot.Common.TypeReaders;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Services.Database;
using StackExchange.Redis;
using Newtonsoft.Json;
namespace NadekoBot
{
@ -139,6 +141,7 @@ namespace NadekoBot
.AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this
.AddManual<NadekoBot>(this)
.AddManual<IUnitOfWork>(uow)
.AddManual<IDataCache>(new RedisCache())
.LoadFrom(Assembly.GetEntryAssembly())
.Build();
@ -239,13 +242,6 @@ namespace NadekoBot
#if GLOBAL_NADEKO
isPublicNadeko = true;
#endif
//_log.Info(string.Join(", ", CommandService.Commands
// .Distinct(x => x.Name + x.Module.Name)
// .SelectMany(x => x.Aliases)
// .GroupBy(x => x)
// .Where(x => x.Count() > 1)
// .Select(x => x.Key + $"({x.Count()})")));
//unload modules which are not available on the public bot
if(isPublicNadeko)
@ -256,6 +252,7 @@ namespace NadekoBot
.ForEach(x => CommandService.RemoveModuleAsync(x));
Ready.TrySetResult(true);
HandleStatusChanges();
_log.Info($"Shard {Client.ShardId} ready.");
//_log.Info(await stats.Print().ConfigureAwait(false));
}
@ -318,5 +315,51 @@ namespace NadekoBot
}
})).Start();
}
private void HandleStatusChanges()
{
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
sub.Subscribe("status.game_set", async (ch, game) =>
{
try
{
var obj = new { Name = default(string) };
obj = JsonConvert.DeserializeAnonymousType(game, obj);
await Client.SetGameAsync(obj.Name).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}, CommandFlags.FireAndForget);
sub.Subscribe("status.stream_set", async (ch, streamData) =>
{
try
{
var obj = new { Name = "", Url = "" };
obj = JsonConvert.DeserializeAnonymousType(streamData, obj);
await Client.SetGameAsync(obj.Name, obj.Url, StreamType.Twitch).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}, CommandFlags.FireAndForget);
}
public Task SetGameAsync(string game)
{
var obj = new { Name = game };
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
return sub.PublishAsync("status.game_set", JsonConvert.SerializeObject(obj));
}
public Task SetStreamAsync(string name, string url)
{
var obj = new { Name = name, Url = url };
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
return sub.PublishAsync("status.game_set", JsonConvert.SerializeObject(obj));
}
}
}

View File

@ -21,6 +21,7 @@
<FileVersion>1.0.0.0</FileVersion>
<ApplicationIcon>nadeko_icon.ico</ApplicationIcon>
<RuntimeIdentifiers>win7-x64<!--;ubuntu.14.04-x64;osx.10.10-x64 --></RuntimeIdentifiers>
<Configurations>Debug;Release;global_nadeko</Configurations>
</PropertyGroup>
<PropertyGroup Condition=" '$(Version)' == '' ">
@ -78,6 +79,7 @@
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="NLog" Version="5.0.0-beta03" />
<PackageReference Include="NYoutubeDL" Version="0.4.4" />
<PackageReference Include="StackExchange.Redis" Version="1.2.6" />
<PackageReference Include="System.ValueTuple" Version="4.4.0-preview1-25305-02" />
<PackageReference Include="System.Xml.XPath" Version="4.3.0" />
</ItemGroup>
@ -95,6 +97,10 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='global_nadeko|AnyCPU'">
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />

View File

@ -3589,7 +3589,7 @@
<value>`{0}xpex Role Excluded-Role` `{0}xpex Server`</value>
</data>
<data name="xpexclude_desc" xml:space="preserve">
<value>Exclude a user or a role from the xp system, or whole current server.</value>
<value>Exclude a channel, role or current server from the xp system.</value>
</data>
<data name="xpnotify_cmd" xml:space="preserve">
<value>xpnotify xpn</value>
@ -3780,4 +3780,31 @@
<data name="nsfwclearcache_desc" xml:space="preserve">
<value>Clears nsfw cache.</value>
</data>
<data name="clubadmin_cmd" xml:space="preserve">
<value>clubadmin</value>
</data>
<data name="clubadmin_usage" xml:space="preserve">
<value>`{0}clubadmin`</value>
</data>
<data name="clubadmin_desc" xml:space="preserve">
<value>Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications.</value>
</data>
<data name="autoboobs_cmd" xml:space="preserve">
<value>autoboobs</value>
</data>
<data name="autoboobs_desc" xml:space="preserve">
<value>Posts a boobs every X seconds. 20 seconds minimum. Provide no arguments to disable.</value>
</data>
<data name="autoboobs_usage" xml:space="preserve">
<value>`{0}autoboobs 30` or `{0}autoboobs`</value>
</data>
<data name="autobutts_cmd" xml:space="preserve">
<value>autobutts</value>
</data>
<data name="autobutts_desc" xml:space="preserve">
<value>Posts a butts every X seconds. 20 seconds minimum. Provide no arguments to disable.</value>
</data>
<data name="autobutts_usage" xml:space="preserve">
<value>`{0}autobutts 30` or `{0}autobutts`</value>
</data>
</root>

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.RegularExpressions;
namespace NadekoBot.Services.Database.Models
@ -6,7 +7,9 @@ namespace NadekoBot.Services.Database.Models
public class CustomReaction : DbEntity
{
public ulong? GuildId { get; set; }
[NotMapped]
[JsonIgnore]
public Regex Regex { get; set; }
public string Response { get; set; }
public string Trigger { get; set; }
@ -16,6 +19,7 @@ namespace NadekoBot.Services.Database.Models
public bool AutoDeleteTrigger { get; set; }
public bool DmResponse { get; set; }
[JsonIgnore]
public bool IsGlobal => !GuildId.HasValue;
public bool ContainsAnywhere { get; set; }

View File

@ -10,6 +10,9 @@ namespace NadekoBot.Services.Database.Models
public string AvatarId { get; set; }
public ClubInfo Club { get; set; }
public bool IsClubAdmin { get; set; }
public int TotalXp { get; set; }
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
public DateTime LastXpGain { get; set; } = DateTime.MinValue;
public XpNotificationType NotifyOnLevelUp { get; set; }

View File

@ -329,7 +329,7 @@ namespace NadekoBot.Services.Database
#region ClubManytoMany
modelBuilder.Entity<ClubApplicants>()
.HasKey(t => new { t.ClubId, t.UserId });
.HasKey(t => new { t.ClubId, t.UserId });
modelBuilder.Entity<ClubApplicants>()
.HasOne(pt => pt.User)
@ -340,7 +340,7 @@ namespace NadekoBot.Services.Database
.WithMany(x => x.Applicants);
modelBuilder.Entity<ClubBans>()
.HasKey(t => new { t.ClubId, t.UserId });
.HasKey(t => new { t.ClubId, t.UserId });
modelBuilder.Entity<ClubBans>()
.HasOne(pt => pt.User)

View File

@ -10,6 +10,7 @@ namespace NadekoBot.Services.Database.Repositories
int GetNextDiscrim(string clubName);
ClubInfo GetByName(string v, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null);
ClubInfo GetByOwner(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null);
ClubInfo GetByOwnerOrAdmin(ulong userId);
ClubInfo GetByMember(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null);
ClubInfo[] GetClubLeaderboardPage(int page);
}

View File

@ -6,5 +6,7 @@ namespace NadekoBot.Services.Database.Repositories
public interface IDiscordUserRepository : IRepository<DiscordUser>
{
DiscordUser GetOrCreate(IUser original);
int GetUserGlobalRanking(ulong id);
DiscordUser[] GetUsersXpLeaderboardFor(int page);
}
}

View File

@ -5,10 +5,7 @@ namespace NadekoBot.Services.Database.Repositories
public interface IXpRepository : IRepository<UserXpStats>
{
UserXpStats GetOrCreateUser(ulong guildId, ulong userId);
int GetTotalUserXp(ulong userId);
UserXpStats[] GetUsersFor(ulong guildId, int page);
(ulong UserId, int TotalXp)[] GetUsersFor(int page);
int GetUserGlobalRanking(ulong userId);
int GetUserGuildRanking(ulong userId, ulong guildId);
UserXpStats[] GetUsersFor(ulong guildId, int page);
}
}

View File

@ -24,6 +24,30 @@ namespace NadekoBot.Services.Database.Repositories.Impl
return func(_set).FirstOrDefault(x => x.Owner.UserId == userId);
}
public ClubInfo GetByOwnerOrAdmin(ulong userId)
{
return _set
.Include(x => x.Bans)
.ThenInclude(x => x.User)
.Include(x => x.Applicants)
.ThenInclude(x => x.User)
.Include(x => x.Owner)
.FirstOrDefault(x => x.Owner.UserId == userId) ??
_context.Set<DiscordUser>()
.Include(x => x.Club)
.ThenInclude(x => x.Users)
.Include(x => x.Club)
.ThenInclude(x => x.Bans)
.ThenInclude(x => x.User)
.Include(x => x.Club)
.ThenInclude(x => x.Applicants)
.ThenInclude(x => x.User)
.Include(x => x.Club)
.ThenInclude(x => x.Owner)
.FirstOrDefault(x => x.UserId == userId && x.IsClubAdmin)
?.Club;
}
public ClubInfo GetByName(string name, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null)
{
if (func == null)

View File

@ -37,5 +37,23 @@ namespace NadekoBot.Services.Database.Repositories.Impl
return toReturn;
}
public int GetUserGlobalRanking(ulong id)
{
return _set.Count(x => x.TotalXp >
_set.Where(y => y.UserId == id)
.DefaultIfEmpty()
.Sum(y => y.TotalXp));
}
public DiscordUser[] GetUsersXpLeaderboardFor(int page)
{
return _set
.OrderByDescending(x => x.TotalXp)
.Skip(page * 9)
.Take(9)
.AsEnumerable()
.ToArray();
}
}
}

View File

@ -29,11 +29,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl
return usr;
}
public int GetTotalUserXp(ulong userId)
{
return _set.Where(x => x.UserId == userId).Sum(x => x.Xp);
}
public UserXpStats[] GetUsersFor(ulong guildId, int page)
{
return _set.Where(x => x.GuildId == guildId)
@ -43,15 +38,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl
.ToArray();
}
public int GetUserGlobalRanking(ulong userId)
{
return _set
.GroupBy(x => x.UserId)
.Count(x => x.Sum(y => y.Xp) > _set
.Where(y => y.UserId == userId)
.Sum(y => y.Xp)) + 1;
}
public int GetUserGuildRanking(ulong userId, ulong guildId)
{
return _set
@ -62,16 +48,5 @@ namespace NadekoBot.Services.Database.Repositories.Impl
.DefaultIfEmpty()
.Sum())) + 1;
}
public (ulong UserId, int TotalXp)[] GetUsersFor(int page)
{
return _set.GroupBy(x => x.UserId)
.OrderByDescending(x => x.Sum(y => y.Xp))
.Skip(page * 9)
.Take(9)
.AsEnumerable()
.Select(x => (x.Key, x.Sum(y => y.Xp)))
.ToArray();
}
}
}

View File

@ -0,0 +1,18 @@
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services
{
public interface IDataCache
{
ConnectionMultiplexer Redis { get; }
Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key);
Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key);
Task SetImageDataAsync(string key, byte[] data);
Task SetAnimeDataAsync(string link, string data);
}
}

View File

@ -0,0 +1,40 @@
using StackExchange.Redis;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
{
public class RedisCache : IDataCache
{
public ConnectionMultiplexer Redis { get; }
private readonly IDatabase _db;
public RedisCache()
{
Redis = ConnectionMultiplexer.Connect("127.0.0.1");
Redis.PreserveAsyncOrder = false;
_db = Redis.GetDatabase();
}
public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key)
{
byte[] x = await _db.StringGetAsync("image_" + key);
return (x != null, x);
}
public Task SetImageDataAsync(string key, byte[] data)
{
return _db.StringSetAsync("image_" + key, data);
}
public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key)
{
string x = await _db.StringGetAsync("anime_" + key);
return (x != null, x);
}
public Task SetAnimeDataAsync(string key, string data)
{
return _db.StringSetAsync("anime_" + key, data);
}
}
}

View File

@ -20,7 +20,7 @@ namespace NadekoBot.Services.Impl
private readonly IBotCredentials _creds;
private readonly DateTime _started;
public const string BotVersion = "1.8.4";
public const string BotVersion = "1.8.5";
public string Author => "Kwoth#2560";
public string Library => "Discord.Net";

View File

@ -13,7 +13,6 @@
"customreactions_stats_not_found": "No stats for that trigger found, no action taken.",
"customreactions_trigger": "Trigger",
"customreactions_redacted_too_long": "Redecated because it's too long.",
"nsfw_autohentai_stopped": "Autohentai stopped.",
"nsfw_not_found": "No results found.",
"nsfw_blacklisted_tag_list": "List of blacklisted tags:",
"nsfw_blacklisted_tag": "One or more tags you've used are blacklisted",
@ -868,5 +867,10 @@
"xp_club_icon_set": "New club icon set.",
"xp_club_bans_for": "Bans for {0} club",
"xp_club_apps_for": "Applicants for {0} club",
"xp_club_leaderboard": "Club leaderboard - page {0}"
"xp_club_leaderboard": "Club leaderboard - page {0}",
"xp_club_admin_add": "{0} is now a club admin.",
"xp_club_admin_remove": "{0} is no longer club admin.",
"xp_club_admin_error": "Error. You are either not the owner of the club, or that user is not in your club.",
"nsfw_started": "Started. Reposting every {0}s.",
"nsfw_stopped": "Stopped reposting."
}