Merge pull request #462 from Kwoth/dev

0.99.9c
This commit is contained in:
Master Kwoth 2016-07-27 07:24:56 +02:00 committed by GitHub
commit ebdb265dc4
36 changed files with 1259 additions and 689 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "discord.net"] [submodule "discord.net"]
path = discord.net path = discord.net
url = git://github.com/kwoth/discord.net.git url = git://github.com/kwoth/discord.net.git
[submodule "ffmpeg-installer"]
path = ffmpeg-installer
url = https://github.com/kwoth/ffmpeg-installer

View File

@ -36,9 +36,7 @@ ________________________________________________________________________________
###### Setting up `ffmpeg` with installer: ###### Setting up `ffmpeg` with installer:
1) Google Account 1) Google Account
2) Soundcloud Account (if you want soundcloud support) 2) Soundcloud Account (if you want soundcloud support)
3) Download installer here: https://goo.gl/lQZnsH (pick the one for your system, either 32 or 64bit and then click 'download')
3) Download installer here: https://mega.nz/#!O1F3HZDa!EfHsy_89vy0zyQV_L28S3Gizsd0KD5EN1OyWpd55ep0
4) Run the installer 4) Run the installer
5) Follow these steps on how to setup API keys: 5) Follow these steps on how to setup API keys:

View File

@ -1,104 +1,91 @@
#SETTING UP NADEKO ON LINUX UBUNTU 14+ #Setting up NadekoBot on Linux
######If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link http://m.do.co/c/46b4d3d44795/ (and using this link will be supporting Nadeko and will give you **$10 credit**) ####Setting up NadekoBot on Linux Digital Ocean Droplet
######If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean][DigitalOcean] (and using this link will be supporting Nadeko and will give you **$10 credit**)
######Keep this helpful video handy https://www.youtube.com/watch?v=icV4_WPqPQk&feature=youtu.be (thanks to klincheR) it contains how to set up the Digital Ocean droplet aswell. ######Keep this helpful video handy [Linux Setup Video][Linux Setup Video] (thanks to klincheR) it contains how to set up the Digital Ocean droplet aswell.
####Setting up NadekoBot
Assuming you have followed the link above to created an account in Digital Ocean and video to set up the bot until you get the `IP address and root password (in email)` to login, its time to begin.
Assuming you have followed the link above to created an account in Digital Ocean and video to set up the bot until you get the `IP address and root password (in email)` to login, its time to begin: #### Prerequisites
- Download [PuTTY][PuTTY]
- Download [CyberDuck][CyberDuck]
**DOWNLOAD PuTTY** #### Follow these steps
http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
**DOWNLOAD and INSTALL CyberDuck** `(for accessing filesystem using SFTP)`
https://cyberduck.io
**Follow the steps below:**
**Open PuTTY.exe** that you downloaded before, and paste or enter your `IP address` and then click **Open**
- **Open PuTTY.exe** that you downloaded before, and paste or enter your `IP address` and then click **Open**.
If you entered your Droplets IP address correctly, it should show **login as:** in a newly opened window. If you entered your Droplets IP address correctly, it should show **login as:** in a newly opened window.
- Now for **login as:**, type `root` and hit enter.
Now for **login as:**, type `root` and hit enter. - It should then, ask for password, type the `root password` you have received in your **email address registered with Digital Ocean**, then hit Enter.
It should then, ask for password, type the `root password` you have received in your **email address registered with Digital Ocean**, then hit Enter
*(as you are running it for the first time, it will most likely to ask you to change your root password, for that, type the "password you received through email", hit Enter, enter a "new password", hit Enter and confirm that "new password" again.* *(as you are running it for the first time, it will most likely to ask you to change your root password, for that, type the "password you received through email", hit Enter, enter a "new password", hit Enter and confirm that "new password" again.*
**SAVE that new password somewhere safe not just in mind** **SAVE that new password somewhere safe not just in mind**. After you done that, you are ready to write commands.
After you done that, you are ready to write commands.
**Copy and just paste** using **mouse right-click** (it should paste automatically) **Copy and just paste** using **mouse right-click** (it should paste automatically)
######MONO (Source: http://www.mono-project.com/docs/getting-started/install/linux/) ######MONO (Source: [Mono Source][Mono Source])
**1)** **1)**
<pre><code class="language-bash">sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF `sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF`
echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list `echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list`
sudo apt-get update `sudo apt-get update`
</code></pre>
Note if the command is not be initiated, hit **Enter** Note if the command is not being initiated, hit **Enter**
**2)** **2)**
<pre><code class="language-bash">echo "deb http://download.mono-project.com/repo/debian wheezy-apache24-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list
</code></pre> `echo "deb http://download.mono-project.com/repo/debian wheezy-apache24-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list`
**2.5)** **2.5)**
*ONLY DEBIAN 8 and later* *ONLY DEBIAN 8 and later*
<pre><code class="language-bash">echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list
</code></pre> `echo "deb http://download.mono-project.com/repo/debian wheezy-libjpeg62-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list`
**2.6)** **2.6)**
*ONLY CentOS 7, Fedora 19 (and later)* *ONLY CentOS 7, Fedora 19 (and later)*
<pre><code class="language-bash">yum install yum-util
</code></pre> `yum install yum-util`
<pre><code class="language-bash">rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF" `rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF"`
</code></pre> `yum-config-manager --add-repo http://download.mono-project.com/repo/centos/`
<pre><code class="language-bash">yum-config-manager --add-repo http://download.mono-project.com/repo/centos/
</code></pre>
**3)** **3)**
<pre><code class="language-bash">apt-get install mono-devel *Mono Devel*
</code></pre>
`apt-get install mono-devel`
**Type** `y` **hit Enter** **Type** `y` **hit Enter**
######Opus Voice Codec
**4)** **4)**
<pre><code class="language-bash">sudo apt-get install libopus0 opus-tools Opus Voice Codec
</code></pre>
`sudo apt-get install libopus0 opus-tools`
**Type** `y` **hit Enter** **Type** `y` **hit Enter**
**5)** **5)**
<pre><code class="language-bash">sudo apt-get install libopus-dev `sudo apt-get install libopus-dev`
</code></pre>
**In case you are having issues with Mono where you get a random string and the bot won't run, do this:** **In case you are having issues with Mono where you get a random string and the bot won't run, do this:**
<pre><code class="language-bash">sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF `sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF`
</code></pre> `echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list`
<pre><code class="language-bash">echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list `apt-get install ca-certificates-mono`
</code></pre> `mozroots --import --sync`
<pre><code class="language-bash">apt-get install ca-certificates-mono
</code></pre>
<pre><code class="language-bash">mozroots --import --sync
</code></pre>
######FFMPEG ####FFMPEG
**6)** **6)**
<pre><code class="language-bash">apt-get install ffmpeg `apt-get install ffmpeg`
</code></pre>
**Type** `y` **hit Enter** **Type** `y` **hit Enter**
`NOTE: if its "not installing" then, follow the guide here:` http://www.faqforge.com/linux/how-to-install-ffmpeg-on-ubuntu-14-04/ NOTE: if its "not installing" then, follow the guide here: [FFMPEG Help Guide][FFMPEG Help Guide]
*All you need to do, if you are running UBUNTU 14.04 is initiate these:* **All you need to do, if you are running UBUNTU 14.04 is initiate these:**
`sudo add-apt-repository ppa:mc3man/trusty-media` `sudo add-apt-repository ppa:mc3man/trusty-media`
@ -108,7 +95,7 @@ Note if the command is not be initiated, hit **Enter**
*Before executing* `sudo apt-get install ffmpeg` *Before executing* `sudo apt-get install ffmpeg`
*If you are running Debian 8 Jessie, please, follow these steps:* **If you are running Debian 8 Jessie, please, follow these steps:**
`wget http://luxcaeli.de/installer.sh && sudo bash installer.sh` (Thanks to Eleria<3) `wget http://luxcaeli.de/installer.sh && sudo bash installer.sh` (Thanks to Eleria<3)
@ -122,39 +109,39 @@ In case you are not able to install it with installer ^up there, follow these st
`sudo apt-get install ffmpeg -y` `sudo apt-get install ffmpeg -y`
######Uncomplicated Firewall UFW ####Uncomplicated Firewall UFW
**7)** **7)**
<pre><code class="language-bash">apt-get install ufw `apt-get install ufw`
</code></pre>
**it is most likely to have it already installed so if you see it is already installed, check with following command, and/or enable it** **it is most likely to have it already installed so if you see it is already installed, check with following command, and/or enable it**
**8)** **8)**
<pre><code class="language-bash">ufw status `ufw status`
</code></pre>
**9)** **9)**
<pre><code class="language-bash">ufw enable `ufw enable`
</code></pre>
**Type** `y` **hit Enter** **Type** `y` **hit Enter**
**10)** **10)**
<pre><code class="language-bash">sudo ufw allow ssh `sudo ufw allow ssh`
</code></pre>
######Unzip
**11)** **11)**
<pre><code class="language-bash">apt-get install unzip Unzip
</code></pre>
`apt-get install unzip`
######TMUX
**12)** **12)**
<pre><code class="language-bash">apt-get install tmux TMUX
</code></pre>
`apt-get install tmux`
**Type** `y` **hit Enter** **Type** `y` **hit Enter**
######NOW WE NEED TO IMPORT SOME DISCORD CERTS ####NOW WE NEED TO IMPORT SOME DISCORD CERTS
**13)** **13)**
`certmgr -ssl https://discordapp.com` `certmgr -ssl https://discordapp.com`
@ -165,91 +152,71 @@ Type `yes` and hit Enter **(three times - as it will ask for three times)**
**15)** **15)**
Create a new folder “nadeko” or anything you prefer Create a new folder “nadeko” or anything you prefer
<pre><code class="language-bash">mkdir nadeko
</code></pre> `mkdir nadeko`
**16)** **16)**
Move to “nadeko” folder (note `cd --` to go back the directory) Move to “nadeko” folder (note `cd --` to go back the directory)
<pre><code class="language-bash">cd nadeko
</code></pre> `cd nadeko`
**NOW WE NEED TO GET NADEKO FROM RELEASES** **NOW WE NEED TO GET NADEKO FROM RELEASES**
Go to this link: [Releases][Releases] and **copy the zip file address** of the lalest version available,
Go to this link: https://github.com/Kwoth/NadekoBot/releases and **copy the zip file address** of the lalest version available,
it should look like `https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip` it should look like `https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip`
**17)** **17)**
Get the correct link, type `wget`, then *paste the link*, then hit **Enter**. Get the correct link, type `wget`, then *paste the link*, then hit **Enter**.
<pre><code class="language-bash">wget https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip
</code></pre> `wget https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip`
**^Do not copy-paste it** **^Do not copy-paste it**
**18)** **18)**
Now we need to `unzip` the downloaded zip file and to do that, type the file name as it showed in your screen or just copy from the screen, should be like ` NadekoBot.vx.x.zip` Now we need to `unzip` the downloaded zip file and to do that, type the file name as it showed in your screen or just copy from the screen, should be like ` NadekoBot.vx.x.zip`
<pre><code class="language-bash">unzip NadekoBot.vx.x.zip
</code></pre> `unzip NadekoBot.vx.x.zip`
**^Do not copy-paste it** **^Do not copy-paste it**
######NOW TO SETUP NADEKO #####NOW TO SETUP NADEKO
Open **CyberDuck** - Open **CyberDuck**
- Click on **Open Connection** (top-left corner), a new window should appear.
- You should see **FTP (File Transfer Protocol)** in drop-down.
- Change it to **SFTP (SSH File Transfer Protocol)**
- Now, in **Server:** paste or type in your `Digital Ocean Droplets IP address`, leave `Port: 22` (no need to change it)
- In **Username:** type `root`
- In **Password:** type `the new root password (you changed at the start)`
- Click on **Connect**
- It should show you the new folder you created.
- Open it.
Click on **Open Connection** (top-left corner), a new window should appear. #####MAKE SURE YOU READ THE README BEFORE PROCEEDING
You should see **FTP (File Transfer Protocol)** in drop-down. - Copy the `credentials_example.json` to desktop
- EDIT it as it is guided here: [Readme][Readme]
Change it to **SFTP (SSH File Transfer Protocol)** - Rename it to `credentials.json` and paste/put it back in the folder. `(Yes, using CyberDuck)`
- You should see two files `credentials_example.json` and `credentials.json`
Now, in **Server:** paste or type in your `Digital Ocean Droplets IP address`, leave `Port: 22` (no need to change it) - Also if you already have nadeko setup and have `credentials.json`, `config.json`, `nadekobot.sqlite`, and `"permissions" folder`, you can just copy and paste it to the Droplets folder using CyberDuck.
In **Username:** type `root`
In **Password:** type `the new root password (you changed at the start)`
Click on **Connect**
It should show you the new folder you created.
Open it.
######MAKE SURE YOU READ THE README BEFORE PROCEEDING
Copy the `credentials_example.json` to desktop
EDIT it as it is guided here: https://github.com/Kwoth/NadekoBot/blob/master/README.md
Rename it to `credentials.json` and paste/put it back in the folder. `(Yes, using CyberDuck)`
You should see two files `credentials_example.json` and `credentials.json`
Also if you already have nadeko setup and have `credentials.json`, `config.json`, `nadekobot.sqlite`, and `"permissions" folder`, you can just copy and paste it to the Droplets folder using CyberDuck.
######TIME TO RUN ######TIME TO RUN
Go back to **PuTTY**, `(hope its still running xD)` Go back to **PuTTY**, `(hope its still running xD)`
**19)** **19)**
Type/ Copy and hit **Enter**. Type/ Copy and hit **Enter**.
<pre><code class="language-bash">tmux new -s nadeko
</code></pre> `tmux new -s nadeko`
**^this will create a new session named “nadeko”** `(you can replace “nadeko” with anything you prefer and remember its your session name) so you can run the bot in background without having to keep running PuTTY in the background.` **^this will create a new session named “nadeko”** `(you can replace “nadeko” with anything you prefer and remember its your session name) so you can run the bot in background without having to keep running PuTTY in the background.`
`cd nadeko`
<pre><code class="language-bash">cd nadeko
</code></pre>
**20)** **20)**
`mono NadekoBot.exe`
<pre><code class="language-bash">mono NadekoBot.exe
</code></pre>
**CHECK THE BOT IN DISCORD, IF EVERYTHING IS WORKING** **CHECK THE BOT IN DISCORD, IF EVERYTHING IS WORKING**
@ -260,35 +227,35 @@ Now time to **move bot to background** and to do that, press **CTRL+B+D** (this
######SOME MORE INFO (JUST TO KNOW): ######SOME MORE INFO (JUST TO KNOW):
-If you want to **see the sessions** after logging back again, type `tmux ls`, and that will give you the list of sessions running. -If you want to **see the sessions** after logging back again, type `tmux ls`, and that will give you the list of sessions running.
-If you want to **switch to/ see that session**, type `tmux a -t nadeko` (**nadeko** is the name of the session we created before so, replace **“nadeko”** with the session name you created.) -If you want to **switch to/ see that session**, type `tmux a -t nadeko` (**nadeko** is the name of the session we created before so, replace **“nadeko”** with the session name you created.)
**21)** **21)**
-If you want to **kill** NadekoBot **session**, type `tmux kill-session -t nadeko` -If you want to **kill** NadekoBot **session**, type `tmux kill-session -t nadeko`
######TO RESTART YOUR BOT ALONG WITH THE WHOLE SERVER (for science): ######TO RESTART YOUR BOT ALONG WITH THE WHOLE SERVER (for science):
**22)** **22)**
Open **PuTTY** and login as you have before, type `reboot` and hit Enter. Open **PuTTY** and login as you have before, type `reboot` and hit Enter.
######IF YOU WANT TO UPDATE YOUR BOT ######IF YOU WANT TO UPDATE YOUR BOT
**FOLLOW THESE STEPS SERIALLY** **FOLLOW THESE STEPS SERIALLY**
**-21 OR 22** - **-21 OR 22**
- **-19**
**-19** - **-16**
- **-17**
**-16** - **-18**
- **-20**
**-17**
**-18**
**-20**
HIT **CTRL+B+D** and close **PuTTY** HIT **CTRL+B+D** and close **PuTTY**
`IF YOU FACE ANY TROUBLE ANYWHERE IN THE GUIDE JUST FIND US IN NADEKO'S DISCORD SERVER` `IF YOU FACE ANY TROUBLE ANYWHERE IN THE GUIDE JUST FIND US IN NADEKO'S DISCORD SERVER`
[PuTTY]: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
[CyberDuck]: https://cyberduck.io
[Linux Setup Video]: https://www.youtube.com/watch?v=icV4_WPqPQk&feature=youtu.be
[Releases]: https://github.com/Kwoth/NadekoBot/releases
[Readme]: https://github.com/Kwoth/NadekoBot/blob/master/README.md
[FFMPEG Help Guide]: http://www.faqforge.com/linux/how-to-install-ffmpeg-on-ubuntu-14-04/
[Mono Source]: http://www.mono-project.com/docs/getting-started/install/linux/
[DigitalOcean]: http://m.do.co/c/46b4d3d44795/

View File

@ -138,7 +138,7 @@ namespace NadekoBot.Extensions
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="list"></param> /// <param name="list"></param>
public static void Shuffle<T>(this IList<T> list) public static IList<T> Shuffle<T>(this IList<T> list)
{ {
// Thanks to @Joe4Evr for finding a bug in the old version of the shuffle // Thanks to @Joe4Evr for finding a bug in the old version of the shuffle
@ -160,6 +160,7 @@ namespace NadekoBot.Extensions
list[k] = list[n]; list[k] = list[n];
list[n] = value; list[n] = value;
} }
return list;
} }
/// <summary> /// <summary>
@ -303,6 +304,15 @@ namespace NadekoBot.Extensions
public static int GiB(this int value) => value.MiB() * 1024; public static int GiB(this int value) => value.MiB() * 1024;
public static int GB(this int value) => value.MB() * 1000; public static int GB(this int value) => value.MB() * 1000;
public static ulong KiB(this ulong value) => value * 1024;
public static ulong KB(this ulong value) => value * 1000;
public static ulong MiB(this ulong value) => value.KiB() * 1024;
public static ulong MB(this ulong value) => value.KB() * 1000;
public static ulong GiB(this ulong value) => value.MiB() * 1024;
public static ulong GB(this ulong value) => value.MB() * 1000;
public static Stream ToStream(this Image img, System.Drawing.Imaging.ImageFormat format = null) public static Stream ToStream(this Image img, System.Drawing.Imaging.ImageFormat format = null)
{ {
if (format == null) if (format == null)
@ -362,5 +372,7 @@ namespace NadekoBot.Extensions
return sw.BaseStream; return sw.BaseStream;
} }
public static double UnixTimestamp(this DateTime dt) => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
} }
} }

View File

@ -4,8 +4,10 @@ using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Commands; using NadekoBot.Modules.Administration.Commands;
using NadekoBot.Modules.Music; using NadekoBot.Modules.Music;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;
@ -41,7 +43,23 @@ namespace NadekoBot
var commandService = NadekoBot.Client.GetService<CommandService>(); var commandService = NadekoBot.Client.GetService<CommandService>();
statsStopwatch.Start(); statsStopwatch.Start();
commandService.CommandExecuted += StatsCollector_RanCommand; commandService.CommandExecuted += StatsCollector_RanCommand;
commandService.CommandFinished += CommandService_CommandFinished;
commandService.CommandErrored += (s, e) =>
{
try
{
if (e.ErrorType == CommandErrorType.Exception)
File.AppendAllText("errors.txt", $@"Command: {e.Command}
{e.Exception}
-------------------------------------
");
}
catch {
Console.WriteLine("Command errored errorring");
}
};
Task.Run(StartCollecting); Task.Run(StartCollecting);
@ -97,7 +115,7 @@ namespace NadekoBot
if (e.Channel.IsPrivate) if (e.Channel.IsPrivate)
return; return;
if (e.Channel.Type == ChannelType.Text) if (e.Channel.Type == ChannelType.Text)
VoiceChannelsCount++; TextChannelsCount--;
else if (e.Channel.Type == ChannelType.Voice) else if (e.Channel.Type == ChannelType.Voice)
VoiceChannelsCount--; VoiceChannelsCount--;
} }
@ -106,6 +124,7 @@ namespace NadekoBot
carbonStatusTimer.Elapsed += async (s, e) => await SendUpdateToCarbon().ConfigureAwait(false); carbonStatusTimer.Elapsed += async (s, e) => await SendUpdateToCarbon().ConfigureAwait(false);
carbonStatusTimer.Start(); carbonStatusTimer.Start();
} }
HttpClient carbonClient = new HttpClient(); HttpClient carbonClient = new HttpClient();
private async Task SendUpdateToCarbon() private async Task SendUpdateToCarbon()
{ {
@ -176,9 +195,11 @@ namespace NadekoBot
private async Task StartCollecting() private async Task StartCollecting()
{ {
var statsSw = new Stopwatch();
while (true) while (true)
{ {
await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false); await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false);
statsSw.Start();
try try
{ {
var onlineUsers = await Task.Run(() => NadekoBot.Client.Servers.Sum(x => x.Users.Count())).ConfigureAwait(false); var onlineUsers = await Task.Run(() => NadekoBot.Client.Servers.Sum(x => x.Users.Count())).ConfigureAwait(false);
@ -195,6 +216,13 @@ namespace NadekoBot
ConnectedServers = connectedServers, ConnectedServers = connectedServers,
DateAdded = DateTime.Now DateAdded = DateTime.Now
}); });
statsSw.Stop();
var clr = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($"--------------\nCollecting stats finished in {statsSw.Elapsed.TotalSeconds}s\n-------------");
Console.ForegroundColor = clr;
statsSw.Reset();
} }
catch catch
{ {
@ -204,9 +232,21 @@ namespace NadekoBot
} }
} }
private static ConcurrentDictionary<ulong, DateTime> commandTracker = new ConcurrentDictionary<ulong, DateTime>();
private void CommandService_CommandFinished(object sender, CommandEventArgs e)
{
DateTime dt;
if (!commandTracker.TryGetValue(e.Message.Id, out dt))
return;
Console.WriteLine($">>COMMAND ENDED after *{(DateTime.UtcNow - dt).TotalSeconds}s*\nCmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {e.User.Name} [{e.User.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----");
}
private async void StatsCollector_RanCommand(object sender, CommandEventArgs e) private async void StatsCollector_RanCommand(object sender, CommandEventArgs e)
{ {
Console.WriteLine($">> Cmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {e.User.Name} [{e.User.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----"); commandTracker.TryAdd(e.Message.Id, DateTime.UtcNow);
Console.WriteLine($">>COMMAND STARTED\nCmd: {e.Command.Text}\nMsg: {e.Message.Text}\nUsr: {e.User.Name} [{e.User.Id}]\nSrvr: {e.Server?.Name ?? "PRIVATE"} [{e.Server?.Id}]\n-----");
#if !NADEKO_RELEASE
await Task.Run(() => await Task.Run(() =>
{ {
try try
@ -230,6 +270,7 @@ namespace NadekoBot
Console.WriteLine(ex); Console.WriteLine(ex);
} }
}).ConfigureAwait(false); }).ConfigureAwait(false);
#endif
} }
} }
} }

View File

@ -364,9 +364,7 @@ namespace NadekoBot.Classes
} }
var httpResponse = (await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) as HttpWebResponse; var httpResponse = (await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) as HttpWebResponse;
if (httpResponse == null) return "HTTP_RESPONSE_ERROR";
var responseStream = httpResponse.GetResponseStream(); var responseStream = httpResponse.GetResponseStream();
if (responseStream == null) return "RESPONSE_STREAM ERROR";
using (var streamReader = new StreamReader(responseStream)) using (var streamReader = new StreamReader(responseStream))
{ {
var responseText = await streamReader.ReadToEndAsync().ConfigureAwait(false); var responseText = await streamReader.ReadToEndAsync().ConfigureAwait(false);
@ -375,6 +373,7 @@ namespace NadekoBot.Classes
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Shortening of this url failed: " + url);
Console.WriteLine(ex.ToString()); Console.WriteLine(ex.ToString());
return url; return url;
} }

View File

@ -6,6 +6,8 @@ using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Classes namespace NadekoBot.Classes
{ {
@ -27,9 +29,12 @@ namespace NadekoBot.Classes
{ {
configs = JsonConvert configs = JsonConvert
.DeserializeObject<ConcurrentDictionary<ulong, ServerSpecificConfig>>( .DeserializeObject<ConcurrentDictionary<ulong, ServerSpecificConfig>>(
File.ReadAllText(filePath), new JsonSerializerSettings() { File.ReadAllText(filePath), new JsonSerializerSettings()
Error = (s,e) => { {
if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels") { Error = (s, e) =>
{
if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels")
{
e.ErrorContext.Handled = true; e.ErrorContext.Handled = true;
} }
} }
@ -52,14 +57,19 @@ namespace NadekoBot.Classes
public ServerSpecificConfig Of(ulong id) => public ServerSpecificConfig Of(ulong id) =>
configs.GetOrAdd(id, _ => new ServerSpecificConfig()); configs.GetOrAdd(id, _ => new ServerSpecificConfig());
private readonly object saveLock = new object(); private readonly SemaphoreSlim saveLock = new SemaphoreSlim(1, 1);
public void Save() public async Task Save()
{ {
lock (saveLock) await saveLock.WaitAsync();
try
{ {
File.WriteAllText(filePath, JsonConvert.SerializeObject(configs, Formatting.Indented)); File.WriteAllText(filePath, JsonConvert.SerializeObject(configs, Formatting.Indented));
} }
finally
{
saveLock.Release();
}
} }
} }
@ -245,7 +255,7 @@ namespace NadekoBot.Classes
LogserverIgnoreChannels = new ObservableCollection<ulong>(); LogserverIgnoreChannels = new ObservableCollection<ulong>();
} }
public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); }; public event PropertyChangedEventHandler PropertyChanged = async delegate { await SpecificConfigurations.Default.Save().ConfigureAwait(false); };
private void OnPropertyChanged([CallerMemberName] string propertyName = null) private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{ {

View File

@ -70,7 +70,7 @@ namespace NadekoBot.Modules.Administration
{ {
var conf = SpecificConfigurations.Default.Of(e.Server.Id); var conf = SpecificConfigurations.Default.Of(e.Server.Id);
conf.AutoDeleteMessagesOnCommand = !conf.AutoDeleteMessagesOnCommand; conf.AutoDeleteMessagesOnCommand = !conf.AutoDeleteMessagesOnCommand;
Classes.JSONModels.ConfigHandler.SaveConfig(); await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false);
if (conf.AutoDeleteMessagesOnCommand) if (conf.AutoDeleteMessagesOnCommand)
await e.Channel.SendMessage("❗`Now automatically deleting successfull command invokations.`"); await e.Channel.SendMessage("❗`Now automatically deleting successfull command invokations.`");
else else
@ -636,7 +636,8 @@ namespace NadekoBot.Modules.Administration
Message[] msgs; Message[] msgs;
if (string.IsNullOrWhiteSpace(e.GetArg("user_or_num"))) // if nothing is set, clear nadeko's messages, no permissions required if (string.IsNullOrWhiteSpace(e.GetArg("user_or_num"))) // if nothing is set, clear nadeko's messages, no permissions required
{ {
msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == e.Server.CurrentUser.Id).ToArray(); msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false));//.Where(m => m.User.Id == e.Server.CurrentUser.Id).ToArray();
msgs = msgs.Where(m => m.User.Id == e.Server.CurrentUser.Id).ToArray();
if (!msgs.Any()) if (!msgs.Any())
return; return;
await e.Channel.DeleteMessages(msgs).ConfigureAwait(false); await e.Channel.DeleteMessages(msgs).ConfigureAwait(false);

View File

@ -40,7 +40,7 @@ namespace NadekoBot.Modules.Administration.Commands
NadekoBot.Config.CustomReactions[name].Add(message); NadekoBot.Config.CustomReactions[name].Add(message);
else else
NadekoBot.Config.CustomReactions.Add(name, new System.Collections.Generic.List<string>() { message }); NadekoBot.Config.CustomReactions.Add(name, new System.Collections.Generic.List<string>() { message });
await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage($"Added {name} : {message}").ConfigureAwait(false); await e.Channel.SendMessage($"Added {name} : {message}").ConfigureAwait(false);
}); });
@ -140,7 +140,7 @@ namespace NadekoBot.Modules.Administration.Commands
index = index - 1; index = index - 1;
NadekoBot.Config.CustomReactions[name][index] = msg; NadekoBot.Config.CustomReactions[name][index] = msg;
await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage($"Edited response #{index + 1} from `{name}`").ConfigureAwait(false); await e.Channel.SendMessage($"Edited response #{index + 1} from `{name}`").ConfigureAwait(false);
}); });
@ -183,7 +183,7 @@ namespace NadekoBot.Modules.Administration.Commands
NadekoBot.Config.CustomReactions.Remove(name); NadekoBot.Config.CustomReactions.Remove(name);
message = $"Deleted custom reaction: `{name}`"; message = $"Deleted custom reaction: `{name}`";
} }
await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()).ConfigureAwait(false); await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage(message).ConfigureAwait(false); await e.Channel.SendMessage(message).ConfigureAwait(false);
}); });
} }

View File

@ -7,8 +7,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
using Timer = System.Timers.Timer;
namespace NadekoBot.Modules.Administration.Commands namespace NadekoBot.Modules.Administration.Commands
{ {
@ -36,7 +38,7 @@ namespace NadekoBot.Modules.Administration.Commands
{"%trivia%", () => Games.Commands.TriviaCommands.RunningTrivias.Count.ToString()} {"%trivia%", () => Games.Commands.TriviaCommands.RunningTrivias.Count.ToString()}
}; };
private readonly object playingPlaceholderLock = new object(); private readonly SemaphoreSlim playingPlaceholderLock = new SemaphoreSlim(1,1);
public PlayingRotate(DiscordModule module) : base(module) public PlayingRotate(DiscordModule module) : base(module)
{ {
@ -47,7 +49,9 @@ namespace NadekoBot.Modules.Administration.Commands
{ {
i++; i++;
var status = ""; var status = "";
lock (playingPlaceholderLock) //wtf am i doing, just use a queue ffs
await playingPlaceholderLock.WaitAsync().ConfigureAwait(false);
try
{ {
if (PlayingPlaceholders.Count == 0 if (PlayingPlaceholders.Count == 0
|| NadekoBot.Config.RotatingStatuses.Count == 0 || NadekoBot.Config.RotatingStatuses.Count == 0
@ -59,6 +63,7 @@ namespace NadekoBot.Modules.Administration.Commands
status = PlayingPlaceholders.Aggregate(status, status = PlayingPlaceholders.Aggregate(status,
(current, kvp) => current.Replace(kvp.Key, kvp.Value())); (current, kvp) => current.Replace(kvp.Key, kvp.Value()));
} }
finally { playingPlaceholderLock.Release(); }
if (string.IsNullOrWhiteSpace(status)) if (string.IsNullOrWhiteSpace(status))
return; return;
await Task.Run(() => { NadekoBot.Client.SetGame(status); }); await Task.Run(() => { NadekoBot.Client.SetGame(status); });
@ -71,14 +76,18 @@ namespace NadekoBot.Modules.Administration.Commands
public Func<CommandEventArgs, Task> DoFunc() => async e => public Func<CommandEventArgs, Task> DoFunc() => async e =>
{ {
lock (playingPlaceholderLock) await playingPlaceholderLock.WaitAsync().ConfigureAwait(false);
try
{ {
if (timer.Enabled) if (timer.Enabled)
timer.Stop(); timer.Stop();
else else
timer.Start(); timer.Start();
NadekoBot.Config.IsRotatingStatus = timer.Enabled; NadekoBot.Config.IsRotatingStatus = timer.Enabled;
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
}
finally {
playingPlaceholderLock.Release();
} }
await e.Channel.SendMessage($"❗`Rotating playing status has been {(timer.Enabled ? "enabled" : "disabled")}.`").ConfigureAwait(false); await e.Channel.SendMessage($"❗`Rotating playing status has been {(timer.Enabled ? "enabled" : "disabled")}.`").ConfigureAwait(false);
}; };
@ -102,10 +111,15 @@ namespace NadekoBot.Modules.Administration.Commands
var arg = e.GetArg("text"); var arg = e.GetArg("text");
if (string.IsNullOrWhiteSpace(arg)) if (string.IsNullOrWhiteSpace(arg))
return; return;
lock (playingPlaceholderLock) await playingPlaceholderLock.WaitAsync().ConfigureAwait(false);
try
{ {
NadekoBot.Config.RotatingStatuses.Add(arg); NadekoBot.Config.RotatingStatuses.Add(arg);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig();
}
finally
{
playingPlaceholderLock.Release();
} }
await e.Channel.SendMessage("🆗 `Added a new playing string.`").ConfigureAwait(false); await e.Channel.SendMessage("🆗 `Added a new playing string.`").ConfigureAwait(false);
}); });
@ -137,14 +151,15 @@ namespace NadekoBot.Modules.Administration.Commands
var arg = e.GetArg("number"); var arg = e.GetArg("number");
int num; int num;
string str; string str;
lock (playingPlaceholderLock) await playingPlaceholderLock.WaitAsync().ConfigureAwait(false);
{ try {
if (!int.TryParse(arg.Trim(), out num) || num <= 0 || num > NadekoBot.Config.RotatingStatuses.Count) if (!int.TryParse(arg.Trim(), out num) || num <= 0 || num > NadekoBot.Config.RotatingStatuses.Count)
return; return;
str = NadekoBot.Config.RotatingStatuses[num - 1]; str = NadekoBot.Config.RotatingStatuses[num - 1];
NadekoBot.Config.RotatingStatuses.RemoveAt(num - 1); NadekoBot.Config.RotatingStatuses.RemoveAt(num - 1);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
} }
finally { playingPlaceholderLock.Release(); }
await e.Channel.SendMessage($"🆗 `Removed playing string #{num}`({str})").ConfigureAwait(false); await e.Channel.SendMessage($"🆗 `Removed playing string #{num}`({str})").ConfigureAwait(false);
}); });
} }

View File

@ -1,9 +1,8 @@
using Discord.Commands; using Newtonsoft.Json;
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; //using Manatee.Json.Serialization;
using System.Threading.Tasks;
namespace NadekoBot.Classes.ClashOfClans namespace NadekoBot.Classes.ClashOfClans
{ {
@ -11,16 +10,22 @@ namespace NadekoBot.Classes.ClashOfClans
{ {
One, Two, Three One, Two, Three
} }
public enum WarState
{
Started, Ended, Created
}
[System.Serializable]
internal class Caller internal class Caller
{ {
public string CallUser { get; } public string CallUser { get; set; }
public DateTime TimeAdded { get; private set; } public DateTime TimeAdded { get; set; }
public bool BaseDestroyed { get; internal set; } public bool BaseDestroyed { get; set; }
public int Stars { get; set; } = 3; public int Stars { get; set; } = 3;
public Caller() { }
public Caller(string callUser, DateTime timeAdded, bool baseDestroyed) public Caller(string callUser, DateTime timeAdded, bool baseDestroyed)
{ {
@ -31,7 +36,7 @@ namespace NadekoBot.Classes.ClashOfClans
public void ResetTime() public void ResetTime()
{ {
TimeAdded = DateTime.Now; TimeAdded = DateTime.UtcNow;
} }
public void Destroy() public void Destroy()
@ -44,97 +49,84 @@ namespace NadekoBot.Classes.ClashOfClans
{ {
private static TimeSpan callExpire => new TimeSpan(2, 0, 0); private static TimeSpan callExpire => new TimeSpan(2, 0, 0);
public string EnemyClan { get; } public string EnemyClan { get; set; }
public int Size { get; } public int Size { get; set; }
private Caller[] bases { get; } public Caller[] Bases { get; set; }
private CancellationTokenSource[] baseCancelTokens; public WarState WarState { get; set; } = WarState.Created;
private CancellationTokenSource endTokenSource { get; } = new CancellationTokenSource(); //public bool Started { get; set; } = false;
public event Action<string> OnUserTimeExpired = delegate { }; public DateTime StartedAt { get; set; }
public event Action OnWarEnded = delegate { }; //public bool Ended { get; private set; } = false;
public bool Started { get; set; } = false;
public ClashWar(string enemyClan, int size, CommandEventArgs e) public ulong ServerId { get; set; }
public ulong ChannelId { get; set; }
[JsonIgnore]
public Discord.Channel Channel { get; internal set; }
/// <summary>
/// This init is purely for the deserialization
/// </summary>
public ClashWar() { }
public ClashWar(string enemyClan, int size, ulong serverId, ulong channelId)
{ {
this.EnemyClan = enemyClan; this.EnemyClan = enemyClan;
this.Size = size; this.Size = size;
this.bases = new Caller[size]; this.Bases = new Caller[size];
this.baseCancelTokens = new CancellationTokenSource[size]; this.ServerId = serverId;
this.ChannelId = channelId;
this.Channel = NadekoBot.Client.Servers.FirstOrDefault(s => s.Id == serverId)?.TextChannels.FirstOrDefault(c => c.Id == channelId);
} }
internal void End() internal void End()
{ {
if (endTokenSource.Token.IsCancellationRequested) return; //Ended = true;
endTokenSource.Cancel(); WarState = WarState.Ended;
OnWarEnded();
} }
internal void Call(string u, int baseNumber) internal void Call(string u, int baseNumber)
{ {
if (baseNumber < 0 || baseNumber >= bases.Length) if (baseNumber < 0 || baseNumber >= Bases.Length)
throw new ArgumentException("Invalid base number"); throw new ArgumentException("Invalid base number");
if (bases[baseNumber] != null) if (Bases[baseNumber] != null)
throw new ArgumentException("That base is already claimed."); throw new ArgumentException("That base is already claimed.");
for (var i = 0; i < bases.Length; i++) for (var i = 0; i < Bases.Length; i++)
{ {
if (bases[i]?.BaseDestroyed == false && bases[i]?.CallUser == u) if (Bases[i]?.BaseDestroyed == false && Bases[i]?.CallUser == u)
throw new ArgumentException($"@{u} You already claimed a base #{i + 1}. You can't claim a new one."); throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one.");
} }
bases[baseNumber] = new Caller(u.Trim(), DateTime.Now, false); Bases[baseNumber] = new Caller(u.Trim(), DateTime.UtcNow, false);
} }
internal async Task Start() internal void Start()
{ {
if (Started) if (WarState == WarState.Started)
throw new InvalidOperationException(); throw new InvalidOperationException("War already started");
try //if (Started)
{ // throw new InvalidOperationException();
Started = true; //Started = true;
foreach (var b in bases.Where(b => b != null)) WarState = WarState.Started;
StartedAt = DateTime.UtcNow;
foreach (var b in Bases.Where(b => b != null))
{ {
b.ResetTime(); b.ResetTime();
} }
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => await ClearArray()).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
await Task.Delay(new TimeSpan(24, 0, 0), endTokenSource.Token).ConfigureAwait(false);
}
catch { }
finally
{
End();
}
} }
internal int Uncall(string user) internal int Uncall(string user)
{ {
user = user.Trim(); user = user.Trim();
for (var i = 0; i < bases.Length; i++) for (var i = 0; i < Bases.Length; i++)
{ {
if (bases[i]?.CallUser != user) continue; if (Bases[i]?.CallUser != user) continue;
bases[i] = null; Bases[i] = null;
return i; return i;
} }
throw new InvalidOperationException("You are not participating in that war."); throw new InvalidOperationException("You are not participating in that war.");
} }
private async Task ClearArray()
{
while (!endTokenSource.IsCancellationRequested)
{
await Task.Delay(5000).ConfigureAwait(false);
for (var i = 0; i < bases.Length; i++)
{
if (bases[i] == null) continue;
if (!bases[i].BaseDestroyed && DateTime.Now - bases[i].TimeAdded >= callExpire)
{
OnUserTimeExpired(bases[i].CallUser);
bases[i] = null;
}
}
}
}
public string ShortPrint() => public string ShortPrint() =>
$"`{EnemyClan}` ({Size} v {Size})"; $"`{EnemyClan}` ({Size} v {Size})";
@ -143,24 +135,24 @@ namespace NadekoBot.Classes.ClashOfClans
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine($"🔰**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**"); sb.AppendLine($"🔰**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**");
if (!Started) if (WarState == WarState.Created)
sb.AppendLine("`not started`"); sb.AppendLine("`not started`");
for (var i = 0; i < bases.Length; i++) for (var i = 0; i < Bases.Length; i++)
{ {
if (bases[i] == null) if (Bases[i] == null)
{ {
sb.AppendLine($"`{i + 1}.` ❌*unclaimed*"); sb.AppendLine($"`{i + 1}.` ❌*unclaimed*");
} }
else else
{ {
if (bases[i].BaseDestroyed) if (Bases[i].BaseDestroyed)
{ {
sb.AppendLine($"`{i + 1}.` ✅ `{bases[i].CallUser}` {new string('⭐', bases[i].Stars)}"); sb.AppendLine($"`{i + 1}.` ✅ `{Bases[i].CallUser}` {new string('⭐', Bases[i].Stars)}");
} }
else else
{ {
var left = Started ? callExpire - (DateTime.Now - bases[i].TimeAdded) : callExpire; var left = (WarState == WarState.Started) ? callExpire - (DateTime.UtcNow - Bases[i].TimeAdded) : callExpire;
sb.AppendLine($"`{i + 1}.` ✅ `{bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left"); sb.AppendLine($"`{i + 1}.` ✅ `{Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left");
} }
} }
@ -171,11 +163,11 @@ namespace NadekoBot.Classes.ClashOfClans
internal int FinishClaim(string user, int stars = 3) internal int FinishClaim(string user, int stars = 3)
{ {
user = user.Trim(); user = user.Trim();
for (var i = 0; i < bases.Length; i++) for (var i = 0; i < Bases.Length; i++)
{ {
if (bases[i]?.BaseDestroyed != false || bases[i]?.CallUser != user) continue; if (Bases[i]?.BaseDestroyed != false || Bases[i]?.CallUser != user) continue;
bases[i].BaseDestroyed = true; Bases[i].BaseDestroyed = true;
bases[i].Stars = stars; Bases[i].Stars = stars;
return i; return i;
} }
throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base."); throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base.");

View File

@ -1,12 +1,15 @@
using Discord.Commands; using Discord.Commands;
using Discord.Modules; using Discord.Modules;
using NadekoBot.Classes.ClashOfClans; using NadekoBot.Classes.ClashOfClans;
using NadekoBot.Modules.Permissions.Classes;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Modules.Permissions.Classes;
namespace NadekoBot.Modules.ClashOfClans namespace NadekoBot.Modules.ClashOfClans
{ {
@ -14,10 +17,128 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans; public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans;
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; } = new ConcurrentDictionary<ulong, List<ClashWar>>(); public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
private readonly object writeLock = new object(); private readonly object writeLock = new object();
public ClashOfClansModule()
{
NadekoBot.OnReady += () => Task.Run(async () =>
{
if (File.Exists("data/clashofclans/wars.json"))
{
try
{
var content = File.ReadAllText("data/clashofclans/wars.json");
var dict = JsonConvert.DeserializeObject<Dictionary<ulong, List<ClashWar>>>(content);
foreach (var cw in dict)
{
cw.Value.ForEach(war =>
{
war.Channel = NadekoBot.Client.GetServer(war.ServerId)?.GetChannel(war.ChannelId);
if (war.Channel == null)
{
cw.Value.Remove(war);
}
}
);
}
//urgh
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(dict);
}
catch (Exception e)
{
Console.WriteLine("Could not load coc wars: " + e.Message);
}
}
//Can't this be disabled if the modules is disabled too :)
var callExpire = new TimeSpan(2, 0, 0);
var warExpire = new TimeSpan(23, 0, 0);
while (true)
{
try
{
var hash = ClashWars.GetHashCode();
foreach (var cw in ClashWars)
{
foreach (var war in cw.Value)
{
await CheckWar(callExpire, war);
}
List<ClashWar> newVal = new List<ClashWar>();
foreach (var w in cw.Value)
{
//We add when A: the war is not ended
if (w.WarState != WarState.Ended)
{
//and B: the war has not expired
if ((w.WarState == WarState.Started && DateTime.UtcNow - w.StartedAt <= warExpire) || w.WarState == WarState.Created)
{
newVal.Add(w);
}
}
}
//var newVal = cw.Value.Where(w => !(w.Ended || DateTime.UtcNow - w.StartedAt >= warExpire)).ToList();
foreach (var exWar in cw.Value.Except(newVal))
{
await exWar.Channel.SendMessage($"War against {exWar.EnemyClan} ({exWar.Size}v{exWar.Size}) has ended");
}
if (newVal.Count == 0)
{
List<ClashWar> obj;
ClashWars.TryRemove(cw.Key, out obj);
}
else
{
ClashWars.AddOrUpdate(cw.Key, newVal, (x, s) => newVal);
}
}
if (hash != ClashWars.GetHashCode()) //something changed
{
Save();
}
}
catch { }
await Task.Delay(5000);
}
});
}
private static void Save()
{
try
{
Directory.CreateDirectory("data/clashofclans");
File.WriteAllText("data/clashofclans/wars.json", JsonConvert.SerializeObject(ClashWars, Formatting.Indented));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
private static async Task CheckWar(TimeSpan callExpire, ClashWar war)
{
var Bases = war.Bases;
for (var i = 0; i < Bases.Length; i++)
{
if (Bases[i] == null) continue;
if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
{
await war.Channel.SendMessage($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false);
Bases[i] = null;
}
}
}
#region commands
public override void Install(ModuleManager manager) public override void Install(ModuleManager manager)
{ {
manager.CreateCommands("", cgb => manager.CreateCommands("", cgb =>
@ -34,13 +155,6 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
if (!e.User.ServerPermissions.ManageChannels) if (!e.User.ServerPermissions.ManageChannels)
return; return;
List<ClashWar> wars;
if (!ClashWars.TryGetValue(e.Server.Id, out wars))
{
wars = new List<ClashWar>();
if (!ClashWars.TryAdd(e.Server.Id, wars))
return;
}
var enemyClan = e.GetArg("enemy_clan"); var enemyClan = e.GetArg("enemy_clan");
if (string.IsNullOrWhiteSpace(enemyClan)) if (string.IsNullOrWhiteSpace(enemyClan))
{ {
@ -52,35 +166,27 @@ namespace NadekoBot.Modules.ClashOfClans
await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false); await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false);
return; return;
} }
var cw = new ClashWar(enemyClan, size, e); List<ClashWar> wars;
if (!ClashWars.TryGetValue(e.Server.Id, out wars))
{
wars = new List<ClashWar>();
if (!ClashWars.TryAdd(e.Server.Id, wars))
return;
}
var cw = new ClashWar(enemyClan, size, e.Server.Id, e.Channel.Id);
//cw.Start(); //cw.Start();
wars.Add(cw); wars.Add(cw);
cw.OnUserTimeExpired += async (u) =>
{
try
{
await
e.Channel.SendMessage(
$"❗🔰**Claim from @{u} for a war against {cw.ShortPrint()} has expired.**")
.ConfigureAwait(false);
}
catch { }
};
cw.OnWarEnded += async () =>
{
try
{
await e.Channel.SendMessage($"❗🔰**War against {cw.ShortPrint()} ended.**").ConfigureAwait(false);
}
catch { }
};
await e.Channel.SendMessage($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); await e.Channel.SendMessage($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false);
Save();
//war with the index X started. //war with the index X started.
}); });
cgb.CreateCommand(Prefix + "startwar") cgb.CreateCommand(Prefix + "startwar")
.Alias(Prefix + "sw") .Alias(Prefix + "sw")
.Description($"Starts a war with a given number. | `{Prefix}sw 1`") .Description("Starts a war with a given number.")
.Parameter("number", ParameterType.Required) .Parameter("number", ParameterType.Required)
.Do(async e => .Do(async e =>
{ {
@ -93,14 +199,14 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
try try
{ {
var startTask = war.Start(); war.Start();
await e.Channel.SendMessage($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false); await e.Channel.SendMessage($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false);
await startTask.ConfigureAwait(false);
} }
catch catch
{ {
await e.Channel.SendMessage($"🔰**WAR AGAINST {war.ShortPrint()} IS ALREADY STARTED**").ConfigureAwait(false); await e.Channel.SendMessage($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false);
} }
Save();
}); });
cgb.CreateCommand(Prefix + "listwar") cgb.CreateCommand(Prefix + "listwar")
@ -132,6 +238,7 @@ namespace NadekoBot.Modules.ClashOfClans
} }
await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false);
return; return;
} }
//if number is not null, print the war needed //if number is not null, print the war needed
var warsInfo = GetInfo(e); var warsInfo = GetInfo(e);
@ -173,6 +280,7 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
war.Call(usr, baseNum - 1); war.Call(usr, baseNum - 1);
await e.Channel.SendMessage($"🔰**{usr}** claimed a base #{baseNum} for a war against {war.ShortPrint()}").ConfigureAwait(false); await e.Channel.SendMessage($"🔰**{usr}** claimed a base #{baseNum} for a war against {war.ShortPrint()}").ConfigureAwait(false);
Save();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -206,7 +314,7 @@ namespace NadekoBot.Modules.ClashOfClans
cgb.CreateCommand(Prefix + "unclaim") cgb.CreateCommand(Prefix + "unclaim")
.Alias(Prefix + "uncall") .Alias(Prefix + "uncall")
.Alias(Prefix + "uc") .Alias(Prefix + "uc")
.Description($"Removes your claim from a certain war. Optional second argument denotes a person in whos place to unclaim | {Prefix}uc [war_number] [optional_other_name]") .Description($"Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | {Prefix}uc [war_number] [optional_other_name]")
.Parameter("number", ParameterType.Required) .Parameter("number", ParameterType.Required)
.Parameter("other_name", ParameterType.Unparsed) .Parameter("other_name", ParameterType.Unparsed)
.Do(async e => .Do(async e =>
@ -226,6 +334,7 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
var baseNumber = war.Uncall(usr); var baseNumber = war.Uncall(usr);
await e.Channel.SendMessage($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false); await e.Channel.SendMessage($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false);
Save();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -246,12 +355,17 @@ namespace NadekoBot.Modules.ClashOfClans
return; return;
} }
warsInfo.Item1[warsInfo.Item2].End(); warsInfo.Item1[warsInfo.Item2].End();
await e.Channel.SendMessage($"❗🔰**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false);
var size = warsInfo.Item1[warsInfo.Item2].Size; var size = warsInfo.Item1[warsInfo.Item2].Size;
warsInfo.Item1.RemoveAt(warsInfo.Item2); warsInfo.Item1.RemoveAt(warsInfo.Item2);
Save();
}); });
}); });
} }
#endregion
private async Task FinishClaim(CommandEventArgs e, int stars = 3) private async Task FinishClaim(CommandEventArgs e, int stars = 3)
{ {
@ -271,6 +385,7 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
var baseNum = war.FinishClaim(usr, stars); var baseNum = war.FinishClaim(usr, stars);
await e.Channel.SendMessage($"❗🔰{e.User.Mention} **DESTROYED** a base #{baseNum + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false); await e.Channel.SendMessage($"❗🔰{e.User.Mention} **DESTROYED** a base #{baseNum + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false);
Save();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -0,0 +1,289 @@
using NadekoBot.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using System.Collections.Concurrent;
using Discord;
using NadekoBot.Extensions;
using System.Threading;
namespace NadekoBot.Modules.Gambling.Commands
{
class AnimalRacing : DiscordCommand
{
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces = new ConcurrentDictionary<ulong, AnimalRace>();
public AnimalRacing(DiscordModule module) : base(module)
{
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Prefix + "race")
.Description("Starts a new animal race.")
.Do(e => {
var ar = new AnimalRace(e.Server.Id, e.Channel);
if (ar.Fail)
{
return;
}
});
cgb.CreateCommand(Prefix + "joinrace")
.Alias(Prefix + "jr")
.Description("Joins a new race. You can specify an amount of flowers for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`")
.Parameter("amount", ParameterType.Optional)
.Do(async e => {
int amount;
if (!int.TryParse(e.GetArg("amount"), out amount) || amount < 0)
amount = 0;
var userFlowers = GamblingModule.GetUserFlowers(e.User.Id);
if (userFlowers < amount)
{
await e.Channel.SendMessage($"{e.User.Mention} You don't have enough {NadekoBot.Config.CurrencyName}s. You only have {userFlowers}{NadekoBot.Config.CurrencySign}.").ConfigureAwait(false);
return;
}
if (amount > 0)
await FlowersHandler.RemoveFlowers(e.User, "BetRace", (int)amount, true).ConfigureAwait(false);
AnimalRace ar;
if (!AnimalRaces.TryGetValue(e.Server.Id, out ar)) {
await e.Channel.SendMessage("No race exists on this server");
}
await ar.JoinRace(e.User, amount);
});
}
public class AnimalRace
{
private ConcurrentQueue<string> animals = new ConcurrentQueue<string>(NadekoBot.Config.RaceAnimals.Shuffle());
public bool Fail { get; internal set; }
public List<Participant> participants = new List<Participant>();
private ulong serverId;
private int messagesSinceGameStarted = 0;
public Channel raceChannel { get; set; }
public bool Started { get; private set; } = false;
public AnimalRace(ulong serverId, Channel ch)
{
this.serverId = serverId;
this.raceChannel = ch;
if (!AnimalRaces.TryAdd(serverId, this))
{
Fail = true;
return;
}
var cancelSource = new CancellationTokenSource();
var token = cancelSource.Token;
var fullgame = CheckForFullGameAsync(token);
Task.Run(async () =>
{
try
{
await raceChannel.SendMessage($"🏁`Race is starting in 20 seconds or when the room is full. Type $jr to join the race.`");
var t = await Task.WhenAny(Task.Delay(20000, token), fullgame);
Started = true;
cancelSource.Cancel();
if (t == fullgame)
{
await raceChannel.SendMessage("🏁`Race full, starting right now!`");
}
else if (participants.Count > 1)
{
await raceChannel.SendMessage("🏁`Game starting with " + participants.Count + " praticipants.`");
}
else
{
await raceChannel.SendMessage("🏁`Race failed to start since there was not enough participants.`");
var p = participants.FirstOrDefault();
if (p != null)
await FlowersHandler.AddFlowersAsync(p.User, "BetRace", p.AmountBet, true).ConfigureAwait(false);
End();
return;
}
await Task.Run(StartRace);
End();
}
catch { }
});
}
private void End()
{
AnimalRace throwaway;
AnimalRaces.TryRemove(serverId, out throwaway);
}
private async Task StartRace() {
var rng = new Random();
Participant winner = null;
Message msg = null;
int place = 1;
try
{
NadekoBot.Client.MessageReceived += Client_MessageReceived;
while (!participants.All(p => p.Total >= 60))
{
//update the state
participants.ForEach(p =>
{
p.Total += 1 + rng.Next(0, 10);
if (p.Total > 60)
{
p.Total = 60;
if (winner == null)
{
winner = p;
}
if (p.Place == 0)
p.Place = place++;
}
});
//draw the state
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
{String.Join("\n", participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))}
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
if (msg == null || messagesSinceGameStarted >= 10) // also resend the message if channel was spammed
{
if(msg != null)
try { await msg.Delete(); } catch { }
msg = await raceChannel.SendMessage(text);
messagesSinceGameStarted = 0;
}
else
await msg.Edit(text);
await Task.Delay(2500);
}
}
finally
{
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
}
if (winner.AmountBet > 0)
{
var wonAmount = winner.AmountBet * (participants.Count - 1);
await FlowersHandler.AddFlowersAsync(winner.User, "Won a Race", wonAmount).ConfigureAwait(false);
await raceChannel.SendMessage($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{NadekoBot.Config.CurrencySign}!**");
}
else
{
await raceChannel.SendMessage($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race!**");
}
}
private void Client_MessageReceived(object sender, MessageEventArgs e)
{
if (e.Message.IsAuthor || e.Channel.IsPrivate || e.Channel != raceChannel)
return;
messagesSinceGameStarted++;
}
private async Task CheckForFullGameAsync(CancellationToken cancelToken) {
while (animals.Count > 0)
{
await Task.Delay(100,cancelToken);
}
}
public async Task<bool> JoinRace(User u, int amount = 0)
{
var animal = "";
if (!animals.TryDequeue(out animal))
{
await raceChannel.SendMessage($"{u.Mention} `There is no running race on this server.`");
return false;
}
var p = new Participant(u, animal, amount);
if (participants.Contains(p))
{
await raceChannel.SendMessage($"{u.Mention} `You already joined this race.`");
return false;
}
if (Started)
{
await raceChannel.SendMessage($"{u.Mention} `Race is already started`");
return false;
}
participants.Add(p);
await raceChannel.SendMessage($"{u.Mention} **joined the race as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {NadekoBot.Config.CurrencyName.SnPl(amount)}!**" : "**"));
return true;
}
}
public class Participant
{
public User User { get; set; }
public string Animal { get; set; }
public int AmountBet { get; set; }
public float Coeff { get; set; }
public int Total { get; set; }
public int Place { get; set; } = 0;
public Participant(User u, string a, int amount)
{
this.User = u;
this.Animal = a;
this.AmountBet = amount;
}
public override int GetHashCode()
{
return User.GetHashCode();
}
public override bool Equals(object obj)
{
var p = obj as Participant;
return p == null?
false:
p.User == User;
}
public override string ToString()
{
var str = new string('‣', Total) + Animal;
if (Place == 0)
return str;
if (Place == 1)
{
return str + "🏆";
}
else if (Place == 2)
{
return str + "`2nd`";
}
else if (Place == 3)
{
return str + "`3rd`";
}
else {
return str + $"`{Place}th`";
}
}
}
}
}

View File

@ -4,6 +4,7 @@ using Discord.Modules;
using NadekoBot.Classes; using NadekoBot.Classes;
using NadekoBot.DataModels; using NadekoBot.DataModels;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Commands;
using NadekoBot.Modules.Permissions.Classes; using NadekoBot.Modules.Permissions.Classes;
using System; using System;
using System.Linq; using System.Linq;
@ -18,6 +19,7 @@ namespace NadekoBot.Modules.Gambling
commands.Add(new DrawCommand(this)); commands.Add(new DrawCommand(this));
commands.Add(new FlipCoinCommand(this)); commands.Add(new FlipCoinCommand(this));
commands.Add(new DiceRollCommand(this)); commands.Add(new DiceRollCommand(this));
commands.Add(new AnimalRacing(this));
} }
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Gambling; public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Gambling;

View File

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands namespace NadekoBot.Modules.Games.Commands
@ -59,7 +60,7 @@ namespace NadekoBot.Modules.Games.Commands
//channelid/messageid pair //channelid/messageid pair
ConcurrentDictionary<ulong, IEnumerable<Message>> plantedFlowerChannels = new ConcurrentDictionary<ulong, IEnumerable<Message>>(); ConcurrentDictionary<ulong, IEnumerable<Message>> plantedFlowerChannels = new ConcurrentDictionary<ulong, IEnumerable<Message>>();
private object locker = new object(); private SemaphoreSlim locker = new SemaphoreSlim(1,1);
internal override void Init(CommandGroupBuilder cgb) internal override void Init(CommandGroupBuilder cgb)
{ {
@ -78,38 +79,47 @@ namespace NadekoBot.Modules.Games.Commands
await FlowersHandler.AddFlowersAsync(e.User, "Picked a flower.", 1, true).ConfigureAwait(false); await FlowersHandler.AddFlowersAsync(e.User, "Picked a flower.", 1, true).ConfigureAwait(false);
var msg = await e.Channel.SendMessage($"**{e.User.Name}** picked a {NadekoBot.Config.CurrencyName}!").ConfigureAwait(false); var msg = await e.Channel.SendMessage($"**{e.User.Name}** picked a {NadekoBot.Config.CurrencyName}!").ConfigureAwait(false);
ThreadPool.QueueUserWorkItem(async (state) =>
{
try
{
await Task.Delay(10000).ConfigureAwait(false); await Task.Delay(10000).ConfigureAwait(false);
await msg.Delete().ConfigureAwait(false); await msg.Delete().ConfigureAwait(false);
}
catch { }
});
}); });
cgb.CreateCommand(Module.Prefix + "plant") cgb.CreateCommand(Module.Prefix + "plant")
.Description("Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost)") .Description("Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost)")
.Do(e => .Do(async e =>
{ {
lock (locker) await locker.WaitAsync().ConfigureAwait(false);
try
{ {
if (plantedFlowerChannels.ContainsKey(e.Channel.Id)) if (plantedFlowerChannels.ContainsKey(e.Channel.Id))
{ {
e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel."); await e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel.").ConfigureAwait(false);
return; return;
} }
var removed = FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1, true).GetAwaiter().GetResult(); var removed = await FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1, true).ConfigureAwait(false);
if (!removed) if (!removed)
{ {
e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").Wait(); await e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").ConfigureAwait(false);
return; return;
} }
var file = GetRandomCurrencyImagePath(); var file = GetRandomCurrencyImagePath();
Message msg; Message msg;
if (file == null) if (file == null)
msg = e.Channel.SendMessage(NadekoBot.Config.CurrencySign).GetAwaiter().GetResult(); msg = await e.Channel.SendMessage(NadekoBot.Config.CurrencySign).ConfigureAwait(false);
else else
msg = e.Channel.SendFile(file).GetAwaiter().GetResult(); msg = await e.Channel.SendFile(file).ConfigureAwait(false);
var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.Config.CurrencyName[0]); var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.Config.CurrencyName[0]);
var msg2 = e.Channel.SendMessage($"Oh how Nice! **{e.User.Name}** planted {(vowelFirst ? "an" : "a")} {NadekoBot.Config.CurrencyName}. Pick it using {Module.Prefix}pick").GetAwaiter().GetResult(); var msg2 = await e.Channel.SendMessage($"Oh how Nice! **{e.User.Name}** planted {(vowelFirst ? "an" : "a")} {NadekoBot.Config.CurrencyName}. Pick it using {Module.Prefix}pick").ConfigureAwait(false);
plantedFlowerChannels.TryAdd(e.Channel.Id, new[] { msg, msg2 }); plantedFlowerChannels.TryAdd(e.Channel.Id, new[] { msg, msg2 });
} }
finally { locker.Release(); }
}); });
cgb.CreateCommand(Prefix + "gencurrency") cgb.CreateCommand(Prefix + "gencurrency")
@ -129,12 +139,12 @@ namespace NadekoBot.Modules.Games.Commands
int throwaway; int throwaway;
if (config.GenerateCurrencyChannels.TryRemove(e.Channel.Id, out throwaway)) if (config.GenerateCurrencyChannels.TryRemove(e.Channel.Id, out throwaway))
{ {
await e.Channel.SendMessage("`Currency generation disabled on this channel.`"); await e.Channel.SendMessage("`Currency generation disabled on this channel.`").ConfigureAwait(false);
} }
else else
{ {
if (config.GenerateCurrencyChannels.TryAdd(e.Channel.Id, cd)) if (config.GenerateCurrencyChannels.TryAdd(e.Channel.Id, cd))
await e.Channel.SendMessage($"`Currency generation enabled on this channel. Cooldown is {cd} minutes.`"); await e.Channel.SendMessage($"`Currency generation enabled on this channel. Cooldown is {cd} minutes.`").ConfigureAwait(false);
} }
}); });
} }

View File

@ -15,11 +15,6 @@ namespace NadekoBot.Modules.Games.Commands
public static ConcurrentDictionary<Server, Poll> ActivePolls = new ConcurrentDictionary<Server, Poll>(); public static ConcurrentDictionary<Server, Poll> ActivePolls = new ConcurrentDictionary<Server, Poll>();
public Func<CommandEventArgs, Task> DoFunc()
{
throw new NotImplementedException();
}
internal override void Init(CommandGroupBuilder cgb) internal override void Init(CommandGroupBuilder cgb)
{ {
cgb.CreateCommand(Module.Prefix + "poll") cgb.CreateCommand(Module.Prefix + "poll")

View File

@ -13,7 +13,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia
{ {
internal class TriviaGame internal class TriviaGame
{ {
private readonly object _guessLock = new object(); private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1,1);
private Server server { get; } private Server server { get; }
private Channel channel { get; } private Channel channel { get; }
@ -113,7 +113,8 @@ namespace NadekoBot.Modules.Games.Commands.Trivia
if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return;
var guess = false; var guess = false;
lock (_guessLock) await _guessLock.WaitAsync().ConfigureAwait(false);
try
{ {
if (GameActive && CurrentQuestion.IsAnswerCorrect(e.Message.Text) && !triviaCancelSource.IsCancellationRequested) if (GameActive && CurrentQuestion.IsAnswerCorrect(e.Message.Text) && !triviaCancelSource.IsCancellationRequested)
{ {
@ -122,6 +123,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia
guess = true; guess = true;
} }
} }
finally { _guessLock.Release(); }
if (!guess) return; if (!guess) return;
triviaCancelSource.Cancel(); triviaCancelSource.Cancel();
await channel.SendMessage($"☑️ {e.User.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); await channel.SendMessage($"☑️ {e.User.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false);

View File

@ -113,17 +113,10 @@ namespace NadekoBot.Modules.Music.Classes
if (CurrentSong == null) if (CurrentSong == null)
continue; continue;
try
{
OnStarted(this, CurrentSong); OnStarted(this, CurrentSong);
await CurrentSong.Play(audioClient, cancelToken); await CurrentSong.Play(audioClient, cancelToken);
}
catch (OperationCanceledException)
{
Console.WriteLine("Song canceled");
SongCancelSource = new CancellationTokenSource();
cancelToken = SongCancelSource.Token;
}
OnCompleted(this, CurrentSong); OnCompleted(this, CurrentSong);
if (RepeatPlaylist) if (RepeatPlaylist)
@ -135,8 +128,14 @@ namespace NadekoBot.Modules.Music.Classes
} }
finally finally
{ {
await Task.Delay(300).ConfigureAwait(false); if (!cancelToken.IsCancellationRequested)
{
SongCancelSource.Cancel();
}
SongCancelSource = new CancellationTokenSource();
cancelToken = SongCancelSource.Token;
CurrentSong = null; CurrentSong = null;
await Task.Delay(300).ConfigureAwait(false);
} }
} }
} }

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Music.Classes
public int BufferSize { get; } public int BufferSize { get; }
private readonly object readWriteLock = new object(); private readonly SemaphoreSlim readWriteLock = new SemaphoreSlim(1, 1);
public PoopyBuffer(int size) public PoopyBuffer(int size)
{ {
@ -32,12 +32,15 @@ namespace NadekoBot.Modules.Music.Classes
ringBuffer = new byte[size]; ringBuffer = new byte[size];
} }
public int Read(byte[] buffer, int count) public Task<int> ReadAsync(byte[] buffer, int count)
{
return Task.Run(async () =>
{ {
if (buffer.Length < count) if (buffer.Length < count)
throw new ArgumentException(); throw new ArgumentException();
//Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***");
lock (readWriteLock) await readWriteLock.WaitAsync().ConfigureAwait(false);
try
{ {
//read as much as you can if you're reading too much //read as much as you can if you're reading too much
if (count > ContentLength) if (count > ContentLength)
@ -77,6 +80,9 @@ namespace NadekoBot.Modules.Music.Classes
ReadPosition = readFromStart; ReadPosition = readFromStart;
return count; return count;
} }
finally { readWriteLock.Release(); }
});
} }
public async Task WriteAsync(byte[] buffer, int count, CancellationToken cancelToken) public async Task WriteAsync(byte[] buffer, int count, CancellationToken cancelToken)
@ -89,9 +95,12 @@ namespace NadekoBot.Modules.Music.Classes
if (cancelToken.IsCancellationRequested) if (cancelToken.IsCancellationRequested)
return; return;
} }
await Task.Run(async () =>
{
//the while above assures that i cannot write past readposition with my write, so i don't have to check //the while above assures that i cannot write past readposition with my write, so i don't have to check
// *unless its multithreaded or task is not awaited // *unless its multithreaded or task is not awaited
lock (readWriteLock) await readWriteLock.WaitAsync().ConfigureAwait(false);
try
{ {
// if i can just write without hitting buffer.length, do it // if i can just write without hitting buffer.length, do it
if (WritePosition + count < BufferSize) if (WritePosition + count < BufferSize)
@ -115,6 +124,8 @@ namespace NadekoBot.Modules.Music.Classes
WritePosition = wroteFromStart; WritePosition = wroteFromStart;
} }
finally { readWriteLock.Release(); }
});
} }
} }
} }

View File

@ -3,6 +3,7 @@ using NadekoBot.Classes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -31,9 +32,7 @@ namespace NadekoBot.Modules.Music.Classes
public SongInfo SongInfo { get; } public SongInfo SongInfo { get; }
public string QueuerName { get; set; } public string QueuerName { get; set; }
private PoopyBuffer songBuffer { get; set; } private bool bufferingCompleted { get; set; } = false;
private bool prebufferingComplete { get; set; } = false;
public MusicPlayer MusicPlayer { get; set; } public MusicPlayer MusicPlayer { get; set; }
public string PrettyCurrentTime() public string PrettyCurrentTime()
@ -74,7 +73,7 @@ namespace NadekoBot.Modules.Music.Classes
return this; return this;
} }
private Task BufferSong(CancellationToken cancelToken) => private Task BufferSong(string filename, CancellationToken cancelToken) =>
Task.Factory.StartNew(async () => Task.Factory.StartNew(async () =>
{ {
Process p = null; Process p = null;
@ -89,40 +88,38 @@ namespace NadekoBot.Modules.Music.Classes
RedirectStandardError = false, RedirectStandardError = false,
CreateNoWindow = true, CreateNoWindow = true,
}); });
const int blockSize = 3840; var prebufferSize = 100ul.MiB();
var buffer = new byte[blockSize]; using (var outStream = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read))
var attempt = 0;
while (!cancelToken.IsCancellationRequested)
{ {
var read = 0; byte[] buffer = new byte[81920];
try int bytesRead;
while ((bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) != 0)
{ {
read = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, blockSize, cancelToken) await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
.ConfigureAwait(false); while ((ulong)outStream.Length - bytesSent > prebufferSize)
await Task.Delay(100, cancelToken);
} }
catch
{
return;
} }
if (read == 0)
if (attempt++ == 50) bufferingCompleted = true;
break;
else
await Task.Delay(100, cancelToken).ConfigureAwait(false);
else
attempt = 0;
await songBuffer.WriteAsync(buffer, read, cancelToken).ConfigureAwait(false);
if (songBuffer.ContentLength > 2.MB())
prebufferingComplete = 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/SCv72y
Linux Guide: https://goo.gl/rRhjCp");
Console.ForegroundColor = oldclr;
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Buffering errored: {ex.Message}"); Console.WriteLine($"Buffering stopped: {ex.Message}");
} }
finally finally
{ {
Console.WriteLine($"Buffering done." + $" [{songBuffer.ContentLength}]"); Console.WriteLine($"Buffering done.");
if (p != null) if (p != null)
{ {
try try
@ -137,25 +134,41 @@ namespace NadekoBot.Modules.Music.Classes
internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{ {
// initialize the buffer here because if this song was playing before (requeued), we must delete old buffer data var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
songBuffer = new PoopyBuffer(NadekoBot.Config.BufferSize);
var bufferTask = BufferSong(cancelToken).ConfigureAwait(false); var bufferTask = BufferSong(filename, cancelToken).ConfigureAwait(false);
var bufferAttempts = 0;
const int waitPerAttempt = 500; var inStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
var toAttemptTimes = SongInfo.ProviderType != MusicType.Normal ? 5 : 9;
while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) bytesSent = 0;
try
{ {
await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false); var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken);
var sw = new Stopwatch();
sw.Start();
var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken));
if (t != prebufferingTask)
{
Console.WriteLine("Prebuffering timed out or canceled. Cannot get any data from the stream.");
return;
} }
Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}"); else if(prebufferingTask.IsCanceled)
{
Console.WriteLine("Prebuffering timed out. Cannot get any data from the stream.");
return;
}
sw.Stop();
Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed);
const int blockSize = 3840; const int blockSize = 3840;
var attempt = 0; var attempt = 0;
byte[] buffer = new byte[blockSize];
while (!cancelToken.IsCancellationRequested) while (!cancelToken.IsCancellationRequested)
{ {
//Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
byte[] buffer = new byte[blockSize]; var read = inStream.Read(buffer, 0, buffer.Length);
var read = songBuffer.Read(buffer, blockSize); //await inStream.CopyToAsync(voiceClient.OutputStream);
unchecked unchecked
{ {
bytesSent += (ulong)read; bytesSent += (ulong)read;
@ -164,7 +177,6 @@ namespace NadekoBot.Modules.Music.Classes
if (attempt++ == 20) if (attempt++ == 20)
{ {
voiceClient.Wait(); voiceClient.Wait();
Console.WriteLine($"Song finished. [{songBuffer.ContentLength}]");
break; break;
} }
else else
@ -174,16 +186,30 @@ namespace NadekoBot.Modules.Music.Classes
while (this.MusicPlayer.Paused) while (this.MusicPlayer.Paused)
await Task.Delay(200, cancelToken).ConfigureAwait(false); await Task.Delay(200, cancelToken).ConfigureAwait(false);
buffer = AdjustVolume(buffer, MusicPlayer.Volume); buffer = AdjustVolume(buffer, MusicPlayer.Volume);
voiceClient.Send(buffer, 0, read); voiceClient.Send(buffer, 0, read);
} }
Console.WriteLine("Awiting buffer task"); }
finally
{
await bufferTask; await bufferTask;
Console.WriteLine("Buffer task done."); await Task.Run(() => voiceClient.Clear());
voiceClient.Clear(); inStream.Dispose();
cancelToken.ThrowIfCancellationRequested(); try { File.Delete(filename); } catch { }
}
} }
private async Task CheckPrebufferingAsync(Stream inStream, CancellationToken cancelToken)
{
while (!bufferingCompleted && inStream.Length < 2.MiB())
{
await Task.Delay(100, cancelToken);
}
Console.WriteLine("Buffering successfull");
}
/*
//stackoverflow ftw //stackoverflow ftw
private static byte[] AdjustVolume(byte[] audioSamples, float volume) private static byte[] AdjustVolume(byte[] audioSamples, float volume)
{ {
@ -210,6 +236,33 @@ namespace NadekoBot.Modules.Music.Classes
} }
return array; return array;
} }
*/
//aidiakapi ftw
public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume)
{
Contract.Requires(audioSamples != null);
Contract.Requires(audioSamples.Length % 2 == 0);
Contract.Requires(volume >= 0f && volume <= 1f);
Contract.Assert(BitConverter.IsLittleEndian);
if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples;
// 16-bit precision for the multiplication
int volumeFixed = (int)Math.Round(volume * 65536d);
int count = audioSamples.Length / 2;
fixed (byte* srcBytes = audioSamples)
{
short* src = (short*)srcBytes;
for (int i = count; i != 0; i--, src++)
*src = (short)(((*src) * volumeFixed) >> 16);
}
return audioSamples;
}
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal) public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
{ {

View File

@ -18,11 +18,16 @@ namespace NadekoBot.Modules.Music
{ {
internal class MusicModule : DiscordModule internal class MusicModule : DiscordModule
{ {
public static ConcurrentDictionary<Server, MusicPlayer> MusicPlayers = new ConcurrentDictionary<Server, MusicPlayer>(); public static ConcurrentDictionary<Server, MusicPlayer> MusicPlayers = new ConcurrentDictionary<Server, MusicPlayer>();
public const string MusicDataPath = "data/musicdata";
public MusicModule() public MusicModule()
{ {
//it can fail if its currenctly opened or doesn't exist. Either way i don't care
try { Directory.Delete(MusicDataPath, true); } catch { }
Directory.CreateDirectory(MusicDataPath);
} }
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Music; public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Music;
@ -856,7 +861,7 @@ namespace NadekoBot.Modules.Music
} }
if (!silent) if (!silent)
{ {
var queuedMessage = await textCh.SendMessage($"🎵`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count}`").ConfigureAwait(false); var queuedMessage = await textCh.SendMessage($"🎵`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => Task.Run(async () =>
{ {

View File

@ -150,21 +150,21 @@ namespace NadekoBot.Modules.Permissions.Classes
return PermissionBanType.None; return PermissionBanType.None;
} }
private static void WriteServerToJson(ServerPermissions serverPerms) private static Task WriteServerToJson(ServerPermissions serverPerms) => Task.Run(() =>
{ {
string pathToFile = $"data/permissions/{serverPerms.Id}.json"; string pathToFile = $"data/permissions/{serverPerms.Id}.json";
File.WriteAllText(pathToFile, File.WriteAllText(pathToFile,
Newtonsoft.Json.JsonConvert.SerializeObject(serverPerms, Newtonsoft.Json.Formatting.Indented)); Newtonsoft.Json.JsonConvert.SerializeObject(serverPerms, Newtonsoft.Json.Formatting.Indented));
} });
public static void WriteToJson() public static Task WriteToJson() => Task.Run(() =>
{ {
Directory.CreateDirectory("data/permissions/"); Directory.CreateDirectory("data/permissions/");
foreach (var kvp in PermissionsDict) foreach (var kvp in PermissionsDict)
{ {
WriteServerToJson(kvp.Value); WriteServerToJson(kvp.Value);
} }
} });
public static string GetServerPermissionsRoleName(Server server) public static string GetServerPermissionsRoleName(Server server)
{ {
@ -174,25 +174,25 @@ namespace NadekoBot.Modules.Permissions.Classes
return serverPerms.PermissionsControllerRole; return serverPerms.PermissionsControllerRole;
} }
internal static void SetPermissionsRole(Server server, string roleName) internal static async Task SetPermissionsRole(Server server, string roleName)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
serverPerms.PermissionsControllerRole = roleName; serverPerms.PermissionsControllerRole = roleName;
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
internal static void SetVerbosity(Server server, bool val) internal static async Task SetVerbosity(Server server, bool val)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
serverPerms.Verbose = val; serverPerms.Verbose = val;
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
internal static void CopyRolePermissions(Role fromRole, Role toRole) internal static async Task CopyRolePermissions(Role fromRole, Role toRole)
{ {
var server = fromRole.Server; var server = fromRole.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -207,10 +207,10 @@ namespace NadekoBot.Modules.Permissions.Classes
to.CopyFrom(from); to.CopyFrom(from);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
internal static void CopyChannelPermissions(Channel fromChannel, Channel toChannel) internal static async Task CopyChannelPermissions(Channel fromChannel, Channel toChannel)
{ {
var server = fromChannel.Server; var server = fromChannel.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -225,10 +225,10 @@ namespace NadekoBot.Modules.Permissions.Classes
to.CopyFrom(from); to.CopyFrom(from);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
internal static void CopyUserPermissions(User fromUser, User toUser) internal static async Task CopyUserPermissions(User fromUser, User toUser)
{ {
var server = fromUser.Server; var server = fromUser.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -243,10 +243,10 @@ namespace NadekoBot.Modules.Permissions.Classes
to.CopyFrom(from); to.CopyFrom(from);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetServerModulePermission(Server server, string moduleName, bool value) public static async Task SetServerModulePermission(Server server, string moduleName, bool value)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
@ -256,10 +256,10 @@ namespace NadekoBot.Modules.Permissions.Classes
modules[moduleName] = value; modules[moduleName] = value;
else else
modules.TryAdd(moduleName, value); modules.TryAdd(moduleName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetServerCommandPermission(Server server, string commandName, bool value) public static async Task SetServerCommandPermission(Server server, string commandName, bool value)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
@ -269,10 +269,10 @@ namespace NadekoBot.Modules.Permissions.Classes
commands[commandName] = value; commands[commandName] = value;
else else
commands.TryAdd(commandName, value); commands.TryAdd(commandName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetChannelModulePermission(Channel channel, string moduleName, bool value) public static async Task SetChannelModulePermission(Channel channel, string moduleName, bool value)
{ {
var server = channel.Server; var server = channel.Server;
@ -288,10 +288,10 @@ namespace NadekoBot.Modules.Permissions.Classes
modules[moduleName] = value; modules[moduleName] = value;
else else
modules.TryAdd(moduleName, value); modules.TryAdd(moduleName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetChannelCommandPermission(Channel channel, string commandName, bool value) public static async Task SetChannelCommandPermission(Channel channel, string commandName, bool value)
{ {
var server = channel.Server; var server = channel.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -306,10 +306,10 @@ namespace NadekoBot.Modules.Permissions.Classes
commands[commandName] = value; commands[commandName] = value;
else else
commands.TryAdd(commandName, value); commands.TryAdd(commandName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetRoleModulePermission(Role role, string moduleName, bool value) public static async Task SetRoleModulePermission(Role role, string moduleName, bool value)
{ {
var server = role.Server; var server = role.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -324,10 +324,10 @@ namespace NadekoBot.Modules.Permissions.Classes
modules[moduleName] = value; modules[moduleName] = value;
else else
modules.TryAdd(moduleName, value); modules.TryAdd(moduleName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetRoleCommandPermission(Role role, string commandName, bool value) public static async Task SetRoleCommandPermission(Role role, string commandName, bool value)
{ {
var server = role.Server; var server = role.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -342,10 +342,10 @@ namespace NadekoBot.Modules.Permissions.Classes
commands[commandName] = value; commands[commandName] = value;
else else
commands.TryAdd(commandName, value); commands.TryAdd(commandName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetUserModulePermission(User user, string moduleName, bool value) public static async Task SetUserModulePermission(User user, string moduleName, bool value)
{ {
var server = user.Server; var server = user.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -360,10 +360,10 @@ namespace NadekoBot.Modules.Permissions.Classes
modules[moduleName] = value; modules[moduleName] = value;
else else
modules.TryAdd(moduleName, value); modules.TryAdd(moduleName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetUserCommandPermission(User user, string commandName, bool value) public static async Task SetUserCommandPermission(User user, string commandName, bool value)
{ {
var server = user.Server; var server = user.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -377,19 +377,19 @@ namespace NadekoBot.Modules.Permissions.Classes
commands[commandName] = value; commands[commandName] = value;
else else
commands.TryAdd(commandName, value); commands.TryAdd(commandName, value);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetServerWordPermission(Server server, bool value) public static async Task SetServerWordPermission(Server server, bool value)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
serverPerms.Permissions.FilterWords = value; serverPerms.Permissions.FilterWords = value;
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetChannelWordPermission(Channel channel, bool value) public static async Task SetChannelWordPermission(Channel channel, bool value)
{ {
var server = channel.Server; var server = channel.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -399,19 +399,19 @@ namespace NadekoBot.Modules.Permissions.Classes
serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name));
serverPerms.ChannelPermissions[channel.Id].FilterWords = value; serverPerms.ChannelPermissions[channel.Id].FilterWords = value;
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetServerFilterInvitesPermission(Server server, bool value) public static async Task SetServerFilterInvitesPermission(Server server, bool value)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
serverPerms.Permissions.FilterInvites = value; serverPerms.Permissions.FilterInvites = value;
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetChannelFilterInvitesPermission(Channel channel, bool value) public static async Task SetChannelFilterInvitesPermission(Channel channel, bool value)
{ {
var server = channel.Server; var server = channel.Server;
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
@ -421,10 +421,10 @@ namespace NadekoBot.Modules.Permissions.Classes
serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name));
serverPerms.ChannelPermissions[channel.Id].FilterInvites = value; serverPerms.ChannelPermissions[channel.Id].FilterInvites = value;
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void SetCommandCooldown(Server server, string commandName, int value) public static async Task SetCommandCooldown(Server server, string commandName, int value)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
@ -436,26 +436,26 @@ namespace NadekoBot.Modules.Permissions.Classes
serverPerms.CommandCooldowns.AddOrUpdate(commandName, value, (str, v) => value); serverPerms.CommandCooldowns.AddOrUpdate(commandName, value, (str, v) => value);
} }
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void AddFilteredWord(Server server, string word) public static async Task AddFilteredWord(Server server, string word)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
if (serverPerms.Words.Contains(word)) if (serverPerms.Words.Contains(word))
throw new InvalidOperationException("That word is already banned."); throw new InvalidOperationException("That word is already banned.");
serverPerms.Words.Add(word); serverPerms.Words.Add(word);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
public static void RemoveFilteredWord(Server server, string word) public static async Task RemoveFilteredWord(Server server, string word)
{ {
var serverPerms = PermissionsDict.GetOrAdd(server.Id, var serverPerms = PermissionsDict.GetOrAdd(server.Id,
new ServerPermissions(server.Id, server.Name)); new ServerPermissions(server.Id, server.Name));
if (!serverPerms.Words.Contains(word)) if (!serverPerms.Words.Contains(word))
throw new InvalidOperationException("That word is not banned."); throw new InvalidOperationException("That word is not banned.");
serverPerms.Words.Remove(word); serverPerms.Words.Remove(word);
Task.Run(() => WriteServerToJson(serverPerms)); await WriteServerToJson(serverPerms).ConfigureAwait(false);
} }
} }
/// <summary> /// <summary>

View File

@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Permissions.Commands
var chan = string.IsNullOrWhiteSpace(chanStr) var chan = string.IsNullOrWhiteSpace(chanStr)
? e.Channel ? e.Channel
: PermissionHelper.ValidateChannel(e.Server, chanStr); : PermissionHelper.ValidateChannel(e.Server, chanStr);
PermissionsHandler.SetChannelFilterInvitesPermission(chan, state); await PermissionsHandler.SetChannelFilterInvitesPermission(chan, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.") await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.")
.ConfigureAwait(false); .ConfigureAwait(false);
return; return;
@ -80,7 +80,7 @@ namespace NadekoBot.Modules.Permissions.Commands
foreach (var curChannel in e.Server.TextChannels) foreach (var curChannel in e.Server.TextChannels)
{ {
PermissionsHandler.SetChannelFilterInvitesPermission(curChannel, state); await PermissionsHandler.SetChannelFilterInvitesPermission(curChannel, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.") await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.")
.ConfigureAwait(false); .ConfigureAwait(false);
@ -102,7 +102,7 @@ namespace NadekoBot.Modules.Permissions.Commands
try try
{ {
var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
PermissionsHandler.SetServerFilterInvitesPermission(e.Server, state); await PermissionsHandler.SetServerFilterInvitesPermission(e.Server, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for this server.") await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for this server.")
.ConfigureAwait(false); .ConfigureAwait(false);

View File

@ -68,7 +68,7 @@ namespace NadekoBot.Modules.Permissions.Commands
var chan = string.IsNullOrWhiteSpace(chanStr) var chan = string.IsNullOrWhiteSpace(chanStr)
? e.Channel ? e.Channel
: PermissionHelper.ValidateChannel(e.Server, chanStr); : PermissionHelper.ValidateChannel(e.Server, chanStr);
PermissionsHandler.SetChannelWordPermission(chan, state); await PermissionsHandler.SetChannelWordPermission(chan, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.").ConfigureAwait(false); await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.").ConfigureAwait(false);
return; return;
} }
@ -76,7 +76,7 @@ namespace NadekoBot.Modules.Permissions.Commands
foreach (var curChannel in e.Server.TextChannels) foreach (var curChannel in e.Server.TextChannels)
{ {
PermissionsHandler.SetChannelWordPermission(curChannel, state); await PermissionsHandler.SetChannelWordPermission(curChannel, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.").ConfigureAwait(false); await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.").ConfigureAwait(false);
} }
@ -98,7 +98,7 @@ namespace NadekoBot.Modules.Permissions.Commands
var word = e.GetArg("word"); var word = e.GetArg("word");
if (string.IsNullOrWhiteSpace(word)) if (string.IsNullOrWhiteSpace(word))
return; return;
PermissionsHandler.AddFilteredWord(e.Server, word.ToLowerInvariant().Trim()); await PermissionsHandler.AddFilteredWord(e.Server, word.ToLowerInvariant().Trim()).ConfigureAwait(false);
await e.Channel.SendMessage($"Successfully added new filtered word.").ConfigureAwait(false); await e.Channel.SendMessage($"Successfully added new filtered word.").ConfigureAwait(false);
} }
@ -120,7 +120,7 @@ namespace NadekoBot.Modules.Permissions.Commands
var word = e.GetArg("word"); var word = e.GetArg("word");
if (string.IsNullOrWhiteSpace(word)) if (string.IsNullOrWhiteSpace(word))
return; return;
PermissionsHandler.RemoveFilteredWord(e.Server, word.ToLowerInvariant().Trim()); await PermissionsHandler.RemoveFilteredWord(e.Server, word.ToLowerInvariant().Trim()).ConfigureAwait(false);
await e.Channel.SendMessage($"Successfully removed filtered word.").ConfigureAwait(false); await e.Channel.SendMessage($"Successfully removed filtered word.").ConfigureAwait(false);
} }
@ -159,7 +159,7 @@ namespace NadekoBot.Modules.Permissions.Commands
try try
{ {
var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
PermissionsHandler.SetServerWordPermission(e.Server, state); await PermissionsHandler.SetServerWordPermission(e.Server, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** on this server.") await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** on this server.")
.ConfigureAwait(false); .ConfigureAwait(false);

View File

@ -55,7 +55,7 @@ namespace NadekoBot.Modules.Permissions
await e.Channel.SendMessage($"Role `{arg}` probably doesn't exist. Create the role with that name first.").ConfigureAwait(false); await e.Channel.SendMessage($"Role `{arg}` probably doesn't exist. Create the role with that name first.").ConfigureAwait(false);
return; return;
} }
PermissionsHandler.SetPermissionsRole(e.Server, role.Name); await PermissionsHandler.SetPermissionsRole(e.Server, role.Name).ConfigureAwait(false);
await e.Channel.SendMessage($"Role `{role.Name}` is now required in order to change permissions.").ConfigureAwait(false); await e.Channel.SendMessage($"Role `{role.Name}` is now required in order to change permissions.").ConfigureAwait(false);
}); });
@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Permissions
var args = arg.Split('~').Select(a => a.Trim()).ToArray(); var args = arg.Split('~').Select(a => a.Trim()).ToArray();
if (args.Length > 2) if (args.Length > 2)
{ {
await e.Channel.SendMessage("💢Invalid number of '~'s in the argument."); await e.Channel.SendMessage("💢Invalid number of '~'s in the argument.").ConfigureAwait(false);
return; return;
} }
try try
@ -79,12 +79,12 @@ namespace NadekoBot.Modules.Permissions
var fromRole = PermissionHelper.ValidateRole(e.Server, args[0]); var fromRole = PermissionHelper.ValidateRole(e.Server, args[0]);
var toRole = PermissionHelper.ValidateRole(e.Server, args[1]); var toRole = PermissionHelper.ValidateRole(e.Server, args[1]);
PermissionsHandler.CopyRolePermissions(fromRole, toRole); await PermissionsHandler.CopyRolePermissions(fromRole, toRole).ConfigureAwait(false);
await e.Channel.SendMessage($"Copied permission settings from **{fromRole.Name}** to **{toRole.Name}**."); await e.Channel.SendMessage($"Copied permission settings from **{fromRole.Name}** to **{toRole.Name}**.").ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
await e.Channel.SendMessage($"💢{ex.Message}"); await e.Channel.SendMessage($"💢{ex.Message}").ConfigureAwait(false);
} }
}); });
cgb.CreateCommand(Prefix + "chnlpermscopy") cgb.CreateCommand(Prefix + "chnlpermscopy")
@ -107,8 +107,8 @@ namespace NadekoBot.Modules.Permissions
var fromChannel = PermissionHelper.ValidateChannel(e.Server, args[0]); var fromChannel = PermissionHelper.ValidateChannel(e.Server, args[0]);
var toChannel = PermissionHelper.ValidateChannel(e.Server, args[1]); var toChannel = PermissionHelper.ValidateChannel(e.Server, args[1]);
PermissionsHandler.CopyChannelPermissions(fromChannel, toChannel); await PermissionsHandler.CopyChannelPermissions(fromChannel, toChannel).ConfigureAwait(false);
await e.Channel.SendMessage($"Copied permission settings from **{fromChannel.Name}** to **{toChannel.Name}**."); await e.Channel.SendMessage($"Copied permission settings from **{fromChannel.Name}** to **{toChannel.Name}**.").ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -127,7 +127,7 @@ namespace NadekoBot.Modules.Permissions
var args = arg.Split('~').Select(a => a.Trim()).ToArray(); var args = arg.Split('~').Select(a => a.Trim()).ToArray();
if (args.Length > 2) if (args.Length > 2)
{ {
await e.Channel.SendMessage("💢Invalid number of '~'s in the argument."); await e.Channel.SendMessage("💢Invalid number of '~'s in the argument.").ConfigureAwait(false);
return; return;
} }
try try
@ -135,8 +135,8 @@ namespace NadekoBot.Modules.Permissions
var fromUser = PermissionHelper.ValidateUser(e.Server, args[0]); var fromUser = PermissionHelper.ValidateUser(e.Server, args[0]);
var toUser = PermissionHelper.ValidateUser(e.Server, args[1]); var toUser = PermissionHelper.ValidateUser(e.Server, args[1]);
PermissionsHandler.CopyUserPermissions(fromUser, toUser); await PermissionsHandler.CopyUserPermissions(fromUser, toUser).ConfigureAwait(false);
await e.Channel.SendMessage($"Copied permission settings from **{fromUser.ToString()}**to * *{toUser.ToString()}**."); await e.Channel.SendMessage($"Copied permission settings from **{fromUser.ToString()}**to * *{toUser.ToString()}**.").ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -152,7 +152,7 @@ namespace NadekoBot.Modules.Permissions
{ {
var arg = e.GetArg("arg"); var arg = e.GetArg("arg");
var val = PermissionHelper.ValidateBool(arg); var val = PermissionHelper.ValidateBool(arg);
PermissionsHandler.SetVerbosity(e.Server, val); await PermissionsHandler.SetVerbosity(e.Server, val).ConfigureAwait(false);
await e.Channel.SendMessage($"Verbosity set to {val}.").ConfigureAwait(false); await e.Channel.SendMessage($"Verbosity set to {val}.").ConfigureAwait(false);
}); });
@ -254,7 +254,7 @@ namespace NadekoBot.Modules.Permissions
var module = PermissionHelper.ValidateModule(e.GetArg("module")); var module = PermissionHelper.ValidateModule(e.GetArg("module"));
var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
PermissionsHandler.SetServerModulePermission(e.Server, module, state); await PermissionsHandler.SetServerModulePermission(e.Server, module, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false);
} }
catch (ArgumentException exArg) catch (ArgumentException exArg)
@ -278,7 +278,7 @@ namespace NadekoBot.Modules.Permissions
var command = PermissionHelper.ValidateCommand(e.GetArg("command")); var command = PermissionHelper.ValidateCommand(e.GetArg("command"));
var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
PermissionsHandler.SetServerCommandPermission(e.Server, command, state); await PermissionsHandler.SetServerCommandPermission(e.Server, command, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false);
} }
catch (ArgumentException exArg) catch (ArgumentException exArg)
@ -307,7 +307,7 @@ namespace NadekoBot.Modules.Permissions
{ {
foreach (var role in e.Server.Roles) foreach (var role in e.Server.Roles)
{ {
PermissionsHandler.SetRoleModulePermission(role, module, state); await PermissionsHandler.SetRoleModulePermission(role, module, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false);
} }
@ -315,7 +315,7 @@ namespace NadekoBot.Modules.Permissions
{ {
var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role"));
PermissionsHandler.SetRoleModulePermission(role, module, state); await PermissionsHandler.SetRoleModulePermission(role, module, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false);
} }
} }
@ -345,7 +345,7 @@ namespace NadekoBot.Modules.Permissions
{ {
foreach (var role in e.Server.Roles) foreach (var role in e.Server.Roles)
{ {
PermissionsHandler.SetRoleCommandPermission(role, command, state); await PermissionsHandler.SetRoleCommandPermission(role, command, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false);
} }
@ -353,7 +353,7 @@ namespace NadekoBot.Modules.Permissions
{ {
var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role"));
PermissionsHandler.SetRoleCommandPermission(role, command, state); await PermissionsHandler.SetRoleCommandPermission(role, command, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false);
} }
} }
@ -383,20 +383,20 @@ namespace NadekoBot.Modules.Permissions
{ {
foreach (var channel in e.Server.TextChannels) foreach (var channel in e.Server.TextChannels)
{ {
PermissionsHandler.SetChannelModulePermission(channel, module, state); await PermissionsHandler.SetChannelModulePermission(channel, module, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false);
} }
else if (string.IsNullOrWhiteSpace(channelArg)) else if (string.IsNullOrWhiteSpace(channelArg))
{ {
PermissionsHandler.SetChannelModulePermission(e.Channel, module, state); await PermissionsHandler.SetChannelModulePermission(e.Channel, module, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{e.Channel.Name}** channel.").ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{e.Channel.Name}** channel.").ConfigureAwait(false);
} }
else else
{ {
var channel = PermissionHelper.ValidateChannel(e.Server, channelArg); var channel = PermissionHelper.ValidateChannel(e.Server, channelArg);
PermissionsHandler.SetChannelModulePermission(channel, module, state); await PermissionsHandler.SetChannelModulePermission(channel, module, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false);
} }
} }
@ -426,7 +426,7 @@ namespace NadekoBot.Modules.Permissions
{ {
foreach (var channel in e.Server.TextChannels) foreach (var channel in e.Server.TextChannels)
{ {
PermissionsHandler.SetChannelCommandPermission(channel, command, state); await PermissionsHandler.SetChannelCommandPermission(channel, command, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false);
} }
@ -434,7 +434,7 @@ namespace NadekoBot.Modules.Permissions
{ {
var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel"));
PermissionsHandler.SetChannelCommandPermission(channel, command, state); await PermissionsHandler.SetChannelCommandPermission(channel, command, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false);
} }
} }
@ -461,7 +461,7 @@ namespace NadekoBot.Modules.Permissions
var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user")); var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user"));
PermissionsHandler.SetUserModulePermission(user, module, state); await PermissionsHandler.SetUserModulePermission(user, module, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for user **{user.Name}**.").ConfigureAwait(false); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for user **{user.Name}**.").ConfigureAwait(false);
} }
catch (ArgumentException exArg) catch (ArgumentException exArg)
@ -487,7 +487,7 @@ namespace NadekoBot.Modules.Permissions
var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user")); var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user"));
PermissionsHandler.SetUserCommandPermission(user, command, state); await PermissionsHandler.SetUserCommandPermission(user, command, state).ConfigureAwait(false);
await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for user **{user.Name}**.").ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for user **{user.Name}**.").ConfigureAwait(false);
} }
catch (ArgumentException exArg) catch (ArgumentException exArg)
@ -511,7 +511,7 @@ namespace NadekoBot.Modules.Permissions
foreach (var module in NadekoBot.Client.GetService<ModuleService>().Modules) foreach (var module in NadekoBot.Client.GetService<ModuleService>().Modules)
{ {
PermissionsHandler.SetServerModulePermission(e.Server, module.Name, state); await PermissionsHandler.SetServerModulePermission(e.Server, module.Name, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false);
} }
@ -538,7 +538,7 @@ namespace NadekoBot.Modules.Permissions
foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module)) foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module))
{ {
PermissionsHandler.SetServerCommandPermission(e.Server, command.Text, state); await PermissionsHandler.SetServerCommandPermission(e.Server, command.Text, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false); await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** on this server.").ConfigureAwait(false);
} }
@ -565,7 +565,7 @@ namespace NadekoBot.Modules.Permissions
var channel = string.IsNullOrWhiteSpace(chArg) ? e.Channel : PermissionHelper.ValidateChannel(e.Server, chArg); var channel = string.IsNullOrWhiteSpace(chArg) ? e.Channel : PermissionHelper.ValidateChannel(e.Server, chArg);
foreach (var module in NadekoBot.Client.GetService<ModuleService>().Modules) foreach (var module in NadekoBot.Client.GetService<ModuleService>().Modules)
{ {
PermissionsHandler.SetChannelModulePermission(channel, module.Name, state); await PermissionsHandler.SetChannelModulePermission(channel, module.Name, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false);
@ -594,7 +594,7 @@ namespace NadekoBot.Modules.Permissions
var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel"));
foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module)) foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module))
{ {
PermissionsHandler.SetChannelCommandPermission(channel, command.Text, state); await PermissionsHandler.SetChannelCommandPermission(channel, command.Text, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false);
} }
@ -620,7 +620,7 @@ namespace NadekoBot.Modules.Permissions
var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role"));
foreach (var module in NadekoBot.Client.GetService<ModuleService>().Modules) foreach (var module in NadekoBot.Client.GetService<ModuleService>().Modules)
{ {
PermissionsHandler.SetRoleModulePermission(role, module.Name, state); await PermissionsHandler.SetRoleModulePermission(role, module.Name, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); await e.Channel.SendMessage($"All modules have been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false);
@ -652,7 +652,7 @@ namespace NadekoBot.Modules.Permissions
{ {
foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module)) foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module))
{ {
PermissionsHandler.SetRoleCommandPermission(role, command.Text, state); await PermissionsHandler.SetRoleCommandPermission(role, command.Text, state).ConfigureAwait(false);
} }
} }
await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **all roles** role.").ConfigureAwait(false); await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **all roles** role.").ConfigureAwait(false);
@ -663,7 +663,7 @@ namespace NadekoBot.Modules.Permissions
foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module)) foreach (var command in NadekoBot.Client.GetService<CommandService>().AllCommands.Where(c => c.Category == module))
{ {
PermissionsHandler.SetRoleCommandPermission(role, command.Text, state); await PermissionsHandler.SetRoleCommandPermission(role, command.Text, state).ConfigureAwait(false);
} }
await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false); await e.Channel.SendMessage($"All commands from the **{module}** module have been **{(state ? "enabled" : "disabled")}** for **{role.Name}** role.").ConfigureAwait(false);
} }
@ -689,7 +689,7 @@ namespace NadekoBot.Modules.Permissions
if (!e.Message.MentionedUsers.Any()) return; if (!e.Message.MentionedUsers.Any()) return;
var usr = e.Message.MentionedUsers.First(); var usr = e.Message.MentionedUsers.First();
NadekoBot.Config.UserBlacklist.Add(usr.Id); NadekoBot.Config.UserBlacklist.Add(usr.Id);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage($"`Sucessfully blacklisted user {usr.Name}`").ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully blacklisted user {usr.Name}`").ConfigureAwait(false);
}).ConfigureAwait(false); }).ConfigureAwait(false);
}); });
@ -707,7 +707,7 @@ namespace NadekoBot.Modules.Permissions
if (NadekoBot.Config.UserBlacklist.Contains(usr.Id)) if (NadekoBot.Config.UserBlacklist.Contains(usr.Id))
{ {
NadekoBot.Config.UserBlacklist.Remove(usr.Id); NadekoBot.Config.UserBlacklist.Remove(usr.Id);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage($"`Sucessfully unblacklisted user {usr.Name}`").ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully unblacklisted user {usr.Name}`").ConfigureAwait(false);
} }
else else
@ -727,7 +727,7 @@ namespace NadekoBot.Modules.Permissions
if (!e.Message.MentionedChannels.Any()) return; if (!e.Message.MentionedChannels.Any()) return;
var ch = e.Message.MentionedChannels.First(); var ch = e.Message.MentionedChannels.First();
NadekoBot.Config.UserBlacklist.Add(ch.Id); NadekoBot.Config.UserBlacklist.Add(ch.Id);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false);
}).ConfigureAwait(false); }).ConfigureAwait(false);
}); });
@ -742,7 +742,7 @@ namespace NadekoBot.Modules.Permissions
if (!e.Message.MentionedChannels.Any()) return; if (!e.Message.MentionedChannels.Any()) return;
var ch = e.Message.MentionedChannels.First(); var ch = e.Message.MentionedChannels.First();
NadekoBot.Config.UserBlacklist.Remove(ch.Id); NadekoBot.Config.UserBlacklist.Remove(ch.Id);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false); await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false);
}).ConfigureAwait(false); }).ConfigureAwait(false);
}); });
@ -767,7 +767,7 @@ namespace NadekoBot.Modules.Permissions
} }
var serverId = server.Id; var serverId = server.Id;
NadekoBot.Config.ServerBlacklist.Add(serverId); NadekoBot.Config.ServerBlacklist.Add(serverId);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
//cleanup trivias and typeracing //cleanup trivias and typeracing
Modules.Games.Commands.Trivia.TriviaGame trivia; Modules.Games.Commands.Trivia.TriviaGame trivia;
TriviaCommands.RunningTrivias.TryRemove(serverId, out trivia); TriviaCommands.RunningTrivias.TryRemove(serverId, out trivia);
@ -795,7 +795,7 @@ namespace NadekoBot.Modules.Permissions
throw new ArgumentOutOfRangeException("secs", "Invalid second parameter. (Must be a number between 0 and 3600)"); throw new ArgumentOutOfRangeException("secs", "Invalid second parameter. (Must be a number between 0 and 3600)");
PermissionsHandler.SetCommandCooldown(e.Server, command, secs); await PermissionsHandler.SetCommandCooldown(e.Server, command, secs).ConfigureAwait(false);
if(secs == 0) if(secs == 0)
await e.Channel.SendMessage($"Command **{command}** has no coooldown now.").ConfigureAwait(false); await e.Channel.SendMessage($"Command **{command}** has no coooldown now.").ConfigureAwait(false);
else else

View File

@ -30,8 +30,6 @@ namespace NadekoBot.Modules.Searches.Commands
} }
private static Dictionary<string, CachedChampion> CachedChampionImages = new Dictionary<string, CachedChampion>(); private static Dictionary<string, CachedChampion> CachedChampionImages = new Dictionary<string, CachedChampion>();
private readonly object cacheLock = new object();
private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer(); private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer();
public LoLCommands(DiscordModule module) : base(module) public LoLCommands(DiscordModule module) : base(module)
@ -42,7 +40,6 @@ namespace NadekoBot.Modules.Searches.Commands
{ {
try try
{ {
lock (cacheLock)
CachedChampionImages = CachedChampionImages CachedChampionImages = CachedChampionImages
.Where(kvp => DateTime.Now - kvp.Value.AddedAt > new TimeSpan(1, 0, 0)) .Where(kvp => DateTime.Now - kvp.Value.AddedAt > new TimeSpan(1, 0, 0))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
@ -87,10 +84,8 @@ namespace NadekoBot.Modules.Searches.Commands
var resolvedRole = role; var resolvedRole = role;
var name = e.GetArg("champ").Replace(" ", "").ToLower(); var name = e.GetArg("champ").Replace(" ", "").ToLower();
CachedChampion champ = null; CachedChampion champ = null;
lock (cacheLock)
{ if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ))
CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ);
}
if (champ != null) if (champ != null)
{ {
champ.ImageStream.Position = 0; champ.ImageStream.Position = 0;
@ -121,13 +116,9 @@ namespace NadekoBot.Modules.Searches.Commands
role = allData[0]["role"].ToString(); role = allData[0]["role"].ToString();
resolvedRole = ResolvePos(role); resolvedRole = ResolvePos(role);
} }
lock (cacheLock) if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ))
{
CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ);
}
if (champ != null) if (champ != null)
{ {
Console.WriteLine("Sending lol image from cache.");
champ.ImageStream.Position = 0; champ.ImageStream.Position = 0;
await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false);
return; return;

View File

@ -68,7 +68,7 @@ namespace NadekoBot.Modules.Searches.Commands
} }
} }
catch { } catch { }
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
}; };
checkTimer.Start(); checkTimer.Start();
} }
@ -254,7 +254,7 @@ namespace NadekoBot.Modules.Searches.Commands
} }
config.ObservingStreams.Remove(toRemove); config.ObservingStreams.Remove(toRemove);
ConfigHandler.SaveConfig(); await ConfigHandler.SaveConfig().ConfigureAwait(false);
await e.Channel.SendMessage($":ok: Removed `{toRemove.Username}`'s stream from notifications.").ConfigureAwait(false); await e.Channel.SendMessage($":ok: Removed `{toRemove.Username}`'s stream from notifications.").ConfigureAwait(false);
}); });

View File

@ -157,6 +157,14 @@ $@"🌍 **Weather for** 【{obj["target"]}】
.ConfigureAwait(false); .ConfigureAwait(false);
}); });
cgb.CreateCommand(Prefix + "randomdog")
.Alias(Prefix + "woof")
.Description("Shows a random dog image.")
.Do(async e =>
{
await e.Channel.SendMessage("http://random.dog/" + await SearchHelper.GetResponseStringAsync("http://random.dog/woof").ConfigureAwait(false)).ConfigureAwait(false);
});
cgb.CreateCommand(Prefix + "i") cgb.CreateCommand(Prefix + "i")
.Description($"Pulls the first image found using a search parameter. Use ~ir for different results. | `{Prefix}i cute kitten`") .Description($"Pulls the first image found using a search parameter. Use ~ir for different results. | `{Prefix}i cute kitten`")
.Parameter("query", ParameterType.Unparsed) .Parameter("query", ParameterType.Unparsed)

View File

@ -52,16 +52,15 @@ namespace NadekoBot.Modules.Utility
}); });
cgb.CreateCommand(Prefix + "inrole") cgb.CreateCommand(Prefix + "inrole")
.Description($"Lists every person from the provided role or roles (separated by a ',') on this server. | `{Prefix}inrole Role`") .Description($"Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `{Prefix}inrole Role`")
.Parameter("roles", ParameterType.Unparsed) .Parameter("roles", ParameterType.Unparsed)
.Do(async e => .Do(async e =>
{ {
await Task.Run(async () => await Task.Run(async () =>
{ {
if (!e.User.ServerPermissions.MentionEveryone) return;
var arg = e.GetArg("roles").Split(',').Select(r => r.Trim()); var arg = e.GetArg("roles").Split(',').Select(r => r.Trim());
string send = $"`Here is a list of users in a specfic role:`"; string send = $"`Here is a list of users in a specfic role:`";
foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str))) foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str) && str != "@everyone" && str != "everyone"))
{ {
var role = e.Server.FindRoles(roleStr).FirstOrDefault(); var role = e.Server.FindRoles(roleStr).FirstOrDefault();
if (role == null) continue; if (role == null) continue;
@ -71,6 +70,11 @@ namespace NadekoBot.Modules.Utility
while (send.Length > 2000) while (send.Length > 2000)
{ {
if (!e.User.ServerPermissions.ManageMessages)
{
await e.Channel.SendMessage($"{e.User.Mention} you are not allowed to use this command on roles with a lot of users in them to prevent abuse.");
return;
}
var curstr = send.Substring(0, 2000); var curstr = send.Substring(0, 2000);
await await
e.Channel.Send(curstr.Substring(0, e.Channel.Send(curstr.Substring(0,
@ -144,6 +148,18 @@ namespace NadekoBot.Modules.Utility
} }
await e.Channel.SendMessage("`List of roles:` \n• " + string.Join("\n• ", e.Server.Roles)).ConfigureAwait(false); await e.Channel.SendMessage("`List of roles:` \n• " + string.Join("\n• ", e.Server.Roles)).ConfigureAwait(false);
}); });
cgb.CreateCommand(Prefix + "channeltopic")
.Alias(Prefix + "ct")
.Description($"Sends current channel's topic as a message. | `{Prefix}ct`")
.Do(async e =>
{
var topic = e.Channel.Topic;
if (string.IsNullOrWhiteSpace(topic))
return;
await e.Channel.SendMessage(topic).ConfigureAwait(false);
});
}); });
} }
} }

View File

@ -197,7 +197,7 @@ namespace NadekoBot
return; return;
} }
#if NADEKO_RELEASE #if NADEKO_RELEASE
await Task.Delay(90000).ConfigureAwait(false); await Task.Delay(100000).ConfigureAwait(false);
#else #else
await Task.Delay(1000).ConfigureAwait(false); await Task.Delay(1000).ConfigureAwait(false);
#endif #endif

View File

@ -46,6 +46,7 @@
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<NoWarn> <NoWarn>
</NoWarn> </NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -55,6 +56,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'PRIVATE|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'PRIVATE|AnyCPU'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -78,13 +80,14 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants> <DefineConstants>TRACE;DEBUG</DefineConstants>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<Optimize>false</Optimize>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath> <OutputPath>bin\x64\Release\</OutputPath>
@ -115,6 +118,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="libvideo, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="libvideo, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
@ -186,6 +190,7 @@
<Compile Include="Classes\FlowersHandler.cs" /> <Compile Include="Classes\FlowersHandler.cs" />
<Compile Include="Modules\Conversations\Commands\RipCommand.cs" /> <Compile Include="Modules\Conversations\Commands\RipCommand.cs" />
<Compile Include="Modules\CustomReactions\CustomReactions.cs" /> <Compile Include="Modules\CustomReactions\CustomReactions.cs" />
<Compile Include="Modules\Gambling\Commands\AnimalRacing.cs" />
<Compile Include="Modules\Music\Classes\PlaylistFullException.cs" /> <Compile Include="Modules\Music\Classes\PlaylistFullException.cs" />
<Compile Include="Modules\Programming\Commands\HaskellRepl.cs" /> <Compile Include="Modules\Programming\Commands\HaskellRepl.cs" />
<Compile Include="Modules\Programming\ProgrammingModule.cs" /> <Compile Include="Modules\Programming\ProgrammingModule.cs" />

View File

@ -4,6 +4,8 @@ using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Classes.JSONModels namespace NadekoBot.Classes.JSONModels
{ {
@ -88,6 +90,17 @@ namespace NadekoBot.Classes.JSONModels
public bool IsRotatingStatus { get; set; } = false; public bool IsRotatingStatus { get; set; } = false;
public int BufferSize { get; set; } = 4.MiB(); public int BufferSize { get; set; } = 4.MiB();
public string[] RaceAnimals { get; internal set; } = {
"🐼",
"🐻",
"🐧",
"🐨",
"🐬",
"🐞",
"🦀",
"🦄" };
[JsonIgnore]
public List<Quote> Quotes { get; set; } = new List<Quote>(); public List<Quote> Quotes { get; set; } = new List<Quote>();
[JsonIgnore] [JsonIgnore]
@ -187,13 +200,17 @@ Nadeko Support Server: <https://discord.gg/0ehQwTK2RBjAxzEY>";
public static class ConfigHandler public static class ConfigHandler
{ {
private static readonly object configLock = new object(); private static readonly SemaphoreSlim configLock = new SemaphoreSlim(1, 1);
public static void SaveConfig() public static async Task SaveConfig()
{ {
lock (configLock) await configLock.WaitAsync();
try
{ {
File.WriteAllText("data/config.json", JsonConvert.SerializeObject(NadekoBot.Config, Formatting.Indented)); File.WriteAllText("data/config.json", JsonConvert.SerializeObject(NadekoBot.Config, Formatting.Indented));
} }
finally {
configLock.Release();
}
} }
public static bool IsBlackListed(MessageEventArgs evArgs) => IsUserBlacklisted(evArgs.User.Id) || public static bool IsBlackListed(MessageEventArgs evArgs) => IsUserBlacklisted(evArgs.User.Id) ||

View File

@ -4,7 +4,16 @@
"ForwardToAllOwners": false, "ForwardToAllOwners": false,
"IsRotatingStatus": false, "IsRotatingStatus": false,
"BufferSize": 4194304, "BufferSize": 4194304,
"Quotes": [], "RaceAnimals": [
"🐼",
"🐻",
"🐧",
"🐨",
"🐬",
"🐞",
"🦀",
"🦄"
],
"RemindMessageFormat": "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗", "RemindMessageFormat": "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗",
"CustomReactions": { "CustomReactions": {
"\\o\\": [ "\\o\\": [

View File

@ -2,7 +2,7 @@
######You can donate on paypal: `nadekodiscordbot@gmail.com` ######You can donate on paypal: `nadekodiscordbot@gmail.com`
#NadekoBot List Of Commands #NadekoBot List Of Commands
Version: `NadekoBot v0.9.6048.2992` Version: `NadekoBot v0.9.6051.26856`
### Help ### Help
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
@ -57,38 +57,38 @@ Command and aliases | Description | Usage
`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. `.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read.
`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. `.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only.
`.restart` | Restarts the bot. Might not work. **Bot Owner Only** `.restart` | Restarts the bot. Might not work. **Bot Owner Only**
`.setrole`, `.sr` | Sets a role for a given user. | .sr @User Guest `.setrole`, `.sr` | Sets a role for a given user. | `.sr @User Guest`
`.removerole`, `.rr` | Removes a role from a given user. | .rr @User Admin `.removerole`, `.rr` | Removes a role from a given user. | `.rr @User Admin`
`.renamerole`, `.renr` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole` `.renamerole`, `.renr` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole`
`.removeallroles`, `.rar` | Removes all roles from a mentioned user. | .rar @User `.removeallroles`, `.rar` | Removes all roles from a mentioned user. | `.rar @User`
`.createrole`, `.cr` | Creates a role with a given name. | `.r Awesome Role` `.createrole`, `.cr` | Creates a role with a given name. | `.cr Awesome Role`
`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.color Admin 255 200 100` or `.color Admin ffba55` `.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.rc Admin 255 200 100` or `.rc Admin ffba55`
`.ban`, `.b` | Bans a user by id or name with an optional message. | .b "@some Guy" Your behaviour is toxic. `.ban`, `.b` | Bans a user by id or name with an optional message. | `.b "@some Guy" Your behaviour is toxic.`
`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | .sb "@some Guy" Your behaviour is toxic. `.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | `.sb "@some Guy" Your behaviour is toxic.`
`.kick`, `.k` | Kicks a mentioned user. `.kick`, `.k` | Kicks a mentioned user. | `.k "@some Guy" Your behaviour is toxic.`
`.mute` | Mutes mentioned user or users. `.mute` | Mutes mentioned user or users. | `.mute "@Someguy"` or `.mute "@Someguy" "@Someguy"`
`.unmute` | Unmutes mentioned user or users. `.unmute` | Unmutes mentioned user or users. | `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"`
`.deafen`, `.deaf` | Deafens mentioned user or users `.deafen`, `.deaf` | Deafens mentioned user or users | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"`
`.undeafen`, `.undef` | Undeafens mentioned user or users `.undeafen`, `.undef` | Undeafens mentioned user or users | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"`
`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. `.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. | `.dvch VoiceChannelName`
`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. `.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. | `.cvch VoiceChannelName`
`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. `.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. | `.dtch TextChannelName`
`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. `.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. | `.ctch TextChannelName`
`.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic` `.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic`
`.setchanlname`, `.schn` | Changed the name of the current channel. `.setchanlname`, `.schn` | Changed the name of the current channel.| `.schn NewName`
`.heap` | Shows allocated memory - **Bot Owner Only!** `.heap` | Shows allocated memory - **Bot Owner Only!**
`.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X` `.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!** `.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!**
`.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!** `.setname`, `.newnm` | Give the bot a new name. **Bot Owner Only!** | .newnm BotName
`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg` `.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `.setavatar https://i.ytimg.com/vi/WDudkR1eTMM/maxresdefault.jpg`
`.setgame` | Sets the bots game. **Bot Owner Only!** `.setgame` | Sets the bots game. **Bot Owner Only!** | `.setgame Playing with kwoth`
`.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!` `.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!`
`.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. `.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. | `.menro RoleName`
`.unstuck` | Clears the message queue. **Bot Owner Only!** `.unstuck` | Clears the message queue. **Bot Owner Only!**
`.donators` | List of lovely people who donated to keep this project alive. `.donators` | List of lovely people who donated to keep this project alive.
`.donadd` | Add a donator to the database. `.donadd` | Add a donator to the database. | `.donadd Donate Amount`
`.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | .announce Useless spam `.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | `.announce Useless spam`
`.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.chatsave 150` `.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.savechat 150`
### Utility ### Utility
Command and aliases | Description | Usage Command and aliases | Description | Usage
@ -98,15 +98,16 @@ Command and aliases | Description | Usage
`.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server `.serverinfo`, `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server
`.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel `.channelinfo`, `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel
`.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser `.userinfo`, `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser
`.whoplays` | Shows a list of users who are playing the specified game. `.whoplays` | Shows a list of users who are playing the specified game. | `.whoplays Overwatch`
`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. `.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission. | `.inrole Role`
`.checkmyperms` | Checks your userspecific permissions on this channel. `.checkmyperms` | Checks your userspecific permissions on this channel.
`.stats` | Shows some basic stats for Nadeko. `.stats` | Shows some basic stats for Nadeko.
`.dysyd` | Shows some basic stats for Nadeko. `.dysyd` | Shows some basic stats for Nadeko.
`.userid`, `.uid` | Shows user ID. `.userid`, `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"
`.channelid`, `.cid` | Shows current channel ID. `.channelid`, `.cid` | Shows current channel ID. | `.cid`
`.serverid`, `.sid` | Shows current server ID. `.serverid`, `.sid` | Shows current server ID. | `.sid`
`.roles` | List all roles on this server or a single user if specified. `.roles` | List all roles on this server or a single user if specified.
`.channeltopic`, `.ct` | Sends current channel's topic as a message. | `.ct`
### Permissions ### Permissions
Command and aliases | Description | Usage Command and aliases | Description | Usage
@ -122,38 +123,38 @@ Command and aliases | Description | Usage
`;rolepermscopy`, `;rpc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;rpc Some Role ~ Some other role` `;rolepermscopy`, `;rpc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;rpc Some Role ~ Some other role`
`;chnlpermscopy`, `;cpc` | Copies BOT PERMISSIONS (not discord permissions) from one channel to another. | `;cpc Some Channel ~ Some other channel` `;chnlpermscopy`, `;cpc` | Copies BOT PERMISSIONS (not discord permissions) from one channel to another. | `;cpc Some Channel ~ Some other channel`
`;usrpermscopy`, `;upc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;upc @SomeUser ~ @SomeOtherUser` `;usrpermscopy`, `;upc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;upc @SomeUser ~ @SomeOtherUser`
`;verbose`, `;v` | Sets whether to show when a command/module is blocked. | ;verbose true `;verbose`, `;v` | Sets whether to show when a command/module is blocked. | `;verbose true`
`;srvrperms`, `;sp` | Shows banned permissions for this server. `;srvrperms`, `;sp` | Shows banned permissions for this server.
`;roleperms`, `;rp` | Shows banned permissions for a certain role. No argument means for everyone. | ;rp AwesomeRole `;roleperms`, `;rp` | Shows banned permissions for a certain role. No argument means for everyone. | `;rp AwesomeRole`
`;chnlperms`, `;cp` | Shows banned permissions for a certain channel. No argument means for this channel. | ;cp #dev `;chnlperms`, `;cp` | Shows banned permissions for a certain channel. No argument means for this channel. | `;cp #dev`
`;userperms`, `;up` | Shows banned permissions for a certain user. No argument means for yourself. | ;up Kwoth `;userperms`, `;up` | Shows banned permissions for a certain user. No argument means for yourself. | `;up Kwoth`
`;srvrmdl`, `;sm` | Sets a module's permission at the server level. | ;sm "module name" enable `;srvrmdl`, `;sm` | Sets a module's permission at the server level. | `;sm "module name" enable`
`;srvrcmd`, `;sc` | Sets a command's permission at the server level. | ;sc "command name" disable `;srvrcmd`, `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable`
`;rolemdl`, `;rm` | Sets a module's permission at the role level. | ;rm "module name" enable MyRole `;rolemdl`, `;rm` | Sets a module's permission at the role level. | `;rm "module name" enable MyRole`
`;rolecmd`, `;rc` | Sets a command's permission at the role level. | ;rc "command name" disable MyRole `;rolecmd`, `;rc` | Sets a command's permission at the role level. | `;rc "command name" disable MyRole`
`;chnlmdl`, `;cm` | Sets a module's permission at the channel level. | ;cm "module name" enable SomeChannel `;chnlmdl`, `;cm` | Sets a module's permission at the channel level. | `;cm "module name" enable SomeChannel`
`;chnlcmd`, `;cc` | Sets a command's permission at the channel level. | ;cc "command name" enable SomeChannel `;chnlcmd`, `;cc` | Sets a command's permission at the channel level. | `;cc "command name" enable SomeChannel`
`;usrmdl`, `;um` | Sets a module's permission at the user level. | ;um "module name" enable SomeUsername `;usrmdl`, `;um` | Sets a module's permission at the user level. | `;um "module name" enable SomeUsername`
`;usrcmd`, `;uc` | Sets a command's permission at the user level. | ;uc "command name" enable SomeUsername `;usrcmd`, `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername`
`;allsrvrmdls`, `;asm` | Sets permissions for all modules at the server level. | ;asm [enable/disable] `;allsrvrmdls`, `;asm` | Sets permissions for all modules at the server level. | `;asm [enable/disable]`
`;allsrvrcmds`, `;asc` | Sets permissions for all commands from a certain module at the server level. | ;asc "module name" [enable/disable] `;allsrvrcmds`, `;asc` | Sets permissions for all commands from a certain module at the server level. | `;asc "module name" [enable/disable]`
`;allchnlmdls`, `;acm` | Sets permissions for all modules at the channel level. | ;acm [enable/disable] SomeChannel `;allchnlmdls`, `;acm` | Sets permissions for all modules at the channel level. | `;acm [enable/disable] SomeChannel`
`;allchnlcmds`, `;acc` | Sets permissions for all commands from a certain module at the channel level. | ;acc "module name" [enable/disable] SomeChannel `;allchnlcmds`, `;acc` | Sets permissions for all commands from a certain module at the channel level. | `;acc "module name" [enable/disable] SomeChannel`
`;allrolemdls`, `;arm` | Sets permissions for all modules at the role level. | ;arm [enable/disable] MyRole `;allrolemdls`, `;arm` | Sets permissions for all modules at the role level. | `;arm [enable/disable] MyRole`
`;allrolecmds`, `;arc` | Sets permissions for all commands from a certain module at the role level. | ;arc "module name" [enable/disable] MyRole `;allrolecmds`, `;arc` | Sets permissions for all commands from a certain module at the role level. | `;arc "module name" [enable/disable] MyRole`
`;ubl` | Blacklists a mentioned user. | ;ubl [user_mention] `;ubl` | Blacklists a mentioned user. | `;ubl [user_mention]`
`;uubl` | Unblacklists a mentioned user. | ;uubl [user_mention] `;uubl` | Unblacklists a mentioned user. | `;uubl [user_mention]`
`;cbl` | Blacklists a mentioned channel (#general for example). | ;cbl #some_channel `;cbl` | Blacklists a mentioned channel (#general for example). | `;cbl #some_channel`
`;cubl` | Unblacklists a mentioned channel (#general for example). | ;cubl #some_channel `;cubl` | Unblacklists a mentioned channel (#general for example). | `;cubl #some_channel`
`;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | ;sbl [servername/serverid] `;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | `;sbl [servername/serverid]`
`;cmdcooldown`, `;cmdcd` | Sets a cooldown per user for a command. Set 0 to clear. | `;cmdcd "some cmd" 5` `;cmdcooldown`, `;cmdcd` | Sets a cooldown per user for a command. Set 0 to clear. | `;cmdcd "some cmd" 5`
`;allcmdcooldowns`, `;acmdcds` | Shows a list of all commands and their respective cooldowns. `;allcmdcooldowns`, `;acmdcds` | Shows a list of all commands and their respective cooldowns.
### Conversations ### Conversations
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`..` | Adds a new quote with the specified name (single word) and message (no limit). | .. abc My message `..` | Adds a new quote with the specified name (single word) and message (no limit). | `.. abc My message`
`...` | Shows a random quote with a specified name. | .. abc `...` | Shows a random quote with a specified name. | `... abc`
`..qdel`, `..quotedelete` | Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it. | `..qdel abc` `..qdel`, `..quotedelete` | Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it. | `..qdel abc`
`@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000 `@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000
`@BotName die` | Works only for the owner. Shuts the bot down. `@BotName die` | Works only for the owner. Shuts the bot down.
@ -173,11 +174,13 @@ Command and aliases | Description | Usage
`$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5 `$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5
`$rolluo` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice (unordered). If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5 `$rolluo` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice (unordered). If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5
`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` `$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15`
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. `$race` | Starts a new animal race.
`$joinrace`, `$jr` | Joins a new race. You can specify an amount of flowers for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5`
`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName
`$$$` | Check how much NadekoFlowers a person has. (Defaults to yourself) | `$$$` or `$$$ @Someone` `$$$` | Check how much NadekoFlowers a person has. (Defaults to yourself) | `$$$` or `$$$ @Someone`
`$give` | Give someone a certain amount of NadekoFlowers `$give` | Give someone a certain amount of NadekoFlowers
`$award` | Gives someone a certain amount of flowers. **Bot Owner Only!** | `$award 100 @person` `$award` | Gives someone a certain amount of flowers. **Bot Owner Only!** | `$award 100 @person`
`$take` | Takes a certain amount of flowers from someone. **Bot Owner Only!** `$take` | Takes a certain amount of flowers from someone. **Bot Owner Only!** | `$take 1 "@someguy"`
`$betroll`, `$br` | Bets a certain amount of NadekoFlowers and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | $br 5 `$betroll`, `$br` | Bets a certain amount of NadekoFlowers and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | $br 5
`$leaderboard`, `$lb` | `$leaderboard`, `$lb` |
@ -196,9 +199,9 @@ Command and aliases | Description | Usage
`>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) `>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost)
`>gencurrency`, `>gc` | Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a NadekoFlower. Optional parameter cooldown time in minutes, 5 minutes by default. Requires Manage Messages permission. | `>gc` or `>gc 60` `>gencurrency`, `>gc` | Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a NadekoFlower. Optional parameter cooldown time in minutes, 5 minutes by default. Requires Manage Messages permission. | `>gc` or `>gc 60`
`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | >leet 3 Hello `>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | >leet 3 Hello
`>choose` | Chooses a thing from a list of things | >choose Get up;Sleep;Sleep more `>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more`
`>8ball` | Ask the 8ball a yes/no question. `>8ball` | Ask the 8ball a yes/no question. | `>8ball should i do something`
`>rps` | Play a game of rocket paperclip scissors with Nadeko. | >rps scissors `>rps` | Play a game of rocket paperclip scissors with Nadeko. | `>rps scissors`
`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` `>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows`
### Music ### Music
@ -234,7 +237,7 @@ Command and aliases | Description | Usage
`!!load` | Loads a playlist under a certain name. | `!!load classical-1` `!!load` | Loads a playlist under a certain name. | `!!load classical-1`
`!!playlists`, `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 1` `!!playlists`, `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 1`
`!!deleteplaylist`, `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5` `!!deleteplaylist`, `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5`
`!!goto` | Goes to a specific time in seconds in a song. `!!goto` | Goes to a specific time in seconds in a song. | !!goto 30
`!!getlink`, `!!gl` | Shows a link to the currently playing song. `!!getlink`, `!!gl` | Shows a link to the currently playing song.
`!!autoplay`, `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) `!!autoplay`, `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty)
@ -262,41 +265,42 @@ Command and aliases | Description | Usage
`~pokemonability`, `~pokeab` | Searches for a pokemon ability. `~pokemonability`, `~pokeab` | Searches for a pokemon ability.
`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ `~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/
`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"` `~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"`
`~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | ~we Moscow RF `~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `~we Moscow RF`
`~yt` | Searches youtubes and shows the first result `~yt` | Searches youtubes and shows the first result | `~yt query`
`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result.
`~imdb` | Queries imdb for movies or series, show first result. `~imdb` | Queries imdb for movies or series, show first result. | `~imdb query`
`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. `~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. | `~mq query`
`~randomcat`, `~meow` | Shows a random cat image. `~randomcat`, `~meow` | Shows a random cat image.
`~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | ~i cute kitten `~randomdog`, `~woof` | Shows a random dog image.
`~ir` | Pulls a random image using a search parameter. | ~ir cute kitten `~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten`
`~lmgtfy` | Google something for an idiot. `~ir` | Pulls a random image using a search parameter. | `~ir cute kitten`
`~google`, `~g` | Get a google search link for some terms. `~lmgtfy` | Google something for an idiot. | `~lmgtfy query`
`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | ~hs Ysera `~google`, `~g` | Get a google search link for some terms. | `~google query`
`~ud` | Searches Urban Dictionary for a word. | ~ud Pineapple `~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera`
`~#` | Searches Tagdef.com for a hashtag. | ~# ff `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple`
`~#` | Searches Tagdef.com for a hashtag. | `~# ff`
`~quote` | Shows a random quote. `~quote` | Shows a random quote.
`~catfact` | Shows a random catfact from <http://catfacts-api.appspot.com/api/facts> `~catfact` | Shows a random catfact from <http://catfacts-api.appspot.com/api/facts>
`~yomama`, `~ym` | Shows a random joke from <http://api.yomomma.info/> `~yomama`, `~ym` | Shows a random joke from <http://api.yomomma.info/>
`~randjoke`, `~rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random> `~randjoke`, `~rj` | Shows a random joke from <http://tambal.azurewebsites.net/joke/random>
`~chucknorris`, `~cn` | Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random> `~chucknorris`, `~cn` | Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random>
`~magicitem`, `~mi` | Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> `~magicitem`, `~mi` | Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items>
`~revav` | Returns a google reverse image search for someone's avatar. `~revav` | Returns a google reverse image search for someone's avatar. | `~revav "@SomeGuy"
`~revimg` | Returns a google reverse image search for an image from a link. `~revimg` | Returns a google reverse image search for an image from a link. | `~revav Image link`
`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~safebooru yuri+kissing `~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~safebooru yuri+kissing`
`~wiki` | Gives you back a wikipedia link `~wiki` | Gives you back a wikipedia link | `~wiki query`
`~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00`
`~videocall` | Creates a private <http://www.appear.in> video call link for you and other mentioned people. The link is sent to mentioned people via a private message. `~videocall` | Creates a private <http://www.appear.in> video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `~videocall "@SomeGuy"`
`~av`, `~avatar` | Shows a mentioned person's avatar. | ~av @X `~av`, `~avatar` | Shows a mentioned person's avatar. | `~av @X`
### NSFW ### NSFW
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`~hentai` | Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~hentai yuri+kissing `~hentai` | Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~hentai yuri+kissing`
`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~danbooru yuri+kissing `~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~danbooru yuri+kissing`
`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~gelbooru yuri+kissing `~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~gelbooru yuri+kissing`
`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~rule34 yuri+kissing `~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `~rule34 yuri+kissing`
`~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | ~e621 yuri kissing `~e621` | Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | `~e621 yuri kissing`
`~cp` | We all know where this will lead you to. `~cp` | We all know where this will lead you to.
`~boobs` | Real adult content. `~boobs` | Real adult content.
`~butts`, `~ass`, `~butt` | Real adult content. `~butts`, `~ass`, `~butt` | Real adult content.
@ -305,13 +309,13 @@ Command and aliases | Description | Usage
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`,createwar`, `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | ,cw 15 The Enemy Clan `,createwar`, `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | ,cw 15 The Enemy Clan
`,startwar`, `,sw` | Starts a war with a given number. `,startwar`, `,sw` | Starts a war with a given number. | `,sw 1`
`,listwar`, `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | ,lw [war_number] or ,lw `,listwar`, `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | ,lw [war_number] or ,lw
`,claim`, `,call`, `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | ,call [war_number] [base_number] [optional_other_name] `,claim`, `,call`, `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | ,call [war_number] [base_number] [optional_other_name]
`,claimfinish`, `,cf`, `,cf3`, `,claimfinish3` | Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] `,claimfinish`, `,cf`, `,cf3`, `,claimfinish3` | Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name]
`,claimfinish2`, `,cf2` | Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] `,claimfinish2`, `,cf2` | Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name]
`,claimfinish1`, `,cf1` | Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] `,claimfinish1`, `,cf1` | Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name]
`,unclaim`, `,uncall`, `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whos place to unclaim | ,uc [war_number] [optional_other_name] `,unclaim`, `,uncall`, `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | ,uc [war_number] [optional_other_name]
`,endwar`, `,ew` | Ends the war with a given index. | ,ew [war_number] `,endwar`, `,ew` | Ends the war with a given index. | ,ew [war_number]
### Pokegame ### Pokegame
@ -326,7 +330,7 @@ Command and aliases | Description | Usage
### Translator ### Translator
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`~translate`, `~trans` | Translates from>to text. From the given language to the destiation language. | ~trans en>fr Hello `~translate`, `~trans` | Translates from>to text. From the given language to the destiation language. | `~trans en>fr Hello`
`~translangs` | List the valid languages for translation. `~translangs` | List the valid languages for translation.
### Customreactions ### Customreactions
@ -352,7 +356,7 @@ Command and aliases | Description | Usage
### Trello ### Trello
Command and aliases | Description | Usage Command and aliases | Description | Usage
----------------|--------------|------- ----------------|--------------|-------
`trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. | bind [board_id] `trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. | `trello bind [board_id]`
`trello unbind` | Unbinds a bot from the channel and board. `trello unbind` | Unbinds a bot from the channel and board.
`trello lists`, `trello list` | Lists all lists yo ;) `trello lists`, `trello list` | Lists all lists yo ;)
`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. `trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. | `trello cards index`

@ -1 +1 @@
Subproject commit 3e519b5e0b33175e5a5ca247322b7082de484e15 Subproject commit 3a33731135f1b7dd2efdb51b16158c84bb22da66

1
ffmpeg-installer Submodule

@ -0,0 +1 @@
Subproject commit d593fe3a86be7da9e4177865446f2f5ca58b6be4